一、COM简介1
1.COM是微软组件对象模型检测,由于COM具有二进制代码共享特性,所以它具备
高可开发性,高度可维护性,和高度可移植性,以至于Winows很多软件都采用COM
做整体架构,比如微软DirectX等,COM流行于2000-2004,由于它的普及面广,
应用繁多,加上Windows对齐默认支持,开发出的软件无需依赖其他开发包.
所以被很多开发公司采用, 坐位一个VC++程序员,是否掌握COM用法
称为是否合格的表格指标.
2.COM是一门深邃的思想和技术.要想完全掌握的话,没有半年是很难做到的.
3.优点:采用COM组件架构我们的软件,可以使已编写好的功能模块可以
很方便的移植到其他平台比如C++的MFC平台移植到C#的WinForm平台
因为COM组件是夸应用的,可以被C++调用,也可以被C#调用
二、COM接口与QueryInterface
1.现实中的组件和接口
a) 一个手机,一个MP3,一个充电宝,那么充电宝的作用就是给手机充电,
充电宝上有一个接口的USB的,有一根数据线连接到手机上,给手机充电
b) 手机充完电后, 把线接到MP3上,这时候就给MP3充电了.
c) 这3个物件中:手机本身是一个组件,MP3也是一个组件,充电宝也是组件。
因为他们都具备可独立性,
组件和组件通过什么连接呢? 就是通过一个公用的USB接口.进行沟通.
d) 接口的定义:接口是组件和组件之间进行交换的,
CPU内存条这些有固定的接口,因此你更换CPU的时候也必须要相同接口的CPU.
主板不直接识别CPU,而是通过那个接口来识别CPU。
e) 接口的协议确定好后往往是不能再改变了,比如五号电池不能做的大,也不能小.
2.C++程序中的组件与接口
定义接口:
#define interface struct
在c++中interface其实就是struct
定义两个接口
interface OPEN
{
virtual void OPENOK()=0;
virtual voud OPENERROR()=0;
}
interface CLOSE
{
virtual void CLOSEOK()=0;
virtual voud CLOSEERROR()=0;
}
定义组件 组件是派生与接口的,
class BES : public OPEN,public CLOSE
{
public:
//实现接口....
};
BES* b = new BES();//组件实例
OPEN* t = b;//获取接口
t->OPENOK();//调用接口
//组件第二个接口
CLOSE* c = b;//获取接口
t->CLOSEOK();//调用接口
组件可以有多个接口, 这些接口又有不同的功能
销毁组件 delete
3.COM组件与接口
a) COM组件通过二进制代码共享实现跨应用,比如应用A的COM组件
的可以直接把这个COM组件拿到应用B来用(开发)
b)class类文件源码这些就是普通的源码共享,COM是二进制共享方式.
c) COM组件完全与变成语言无关的,可以被VC调用,可被网页调用,可被.net调用
d) COM组件只能运行在Windows平台,不能再Linux和苹果机上面运行.
e)
IUnknow接口 声明
interface IUnknown
{
virtual HRESULT QueryInterface(const IID&iid,void** ppv)=0;
virtual ULONG AddRef()=0;
virtual ULONG Release()=0;
}
5.QueryInterface函数,与HRESULT类型,IID类型,数据类型转换
----- QueryInterface函数
a) 查询某个组件是否支持特定的接口,比如说上面的BES组件他有两个接口
我想要知道BES组件是否支持其他的接口,我在外部是不知道你支持还是
不支持的,所有通过QueryInterface来查询接口知道你是否支持这个接口.
b) 返回结果是通过第二个参数输出的,你把组件指针传进去,不为NULL就是找到接口.
c)返回值HRESULT
S_OK 成功
S_FALSE 函数成功执行完成,但返回出现错位
E_INVALIDARG 参数有错误
E_OUTOFMEMORY 内存申请粗我
E_UNEXPECTED 未知异常
E_NOTIMPL 未实现的功能
E_FAIT 没有详细说明的粗我
E_POINTER 无效的指针
E_HANDLE 无效的句柄
E_ABOUT 终止操作
E_ACCESSDENIED 访问被拒绝
E_NOINTERFACE 不支持接口
-----IID
IID是接口表示符,Interface ID ,每个接口都可以设置一个接口标识符
用来标志这个接口,标志了某个接口之后IID的值是不能再修改的,
GUID定义
typedef struct _GUID
{
DWORD D1;//随机数
WORD D2; //和时间有关
WORD D3; //和时间有关
BYTE D4[8]; //和网关MAC相关
}GUID
GUID生成方法 在VS工具里有一个创建GUID
理论上将GUID是不可能重复的,重复可能性非常小,每秒钟一万亿个GUID的情况下
即使太阳变成白矮星,他也是唯一的.
("40125064-D4C6-4165-8535-72D35FE44D21")
GUID这样定义的 用逗号分隔
static const GUID guid =
{
0x40125064,
0xD4C6,
0x4165,
{
0x85,
0x35,
0x72,
0xD3,
0x5F,
0xe4,
0x4d,
0x21
}
}
在编辑器里这个就表示一个接口标识符,它是固定的.
注意我说的不是图片里的固定,
而是你自己去生成的, 我这个只是一个示范.
6.接口继承自IUnknown
然后组件实现IUnknown接口里的函数
QueryInterface实现
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid,void**ppv) { //ppv返回的接口指针 0就是没有找到该接口 if(iid == IID_IUnknown) { //即使CPD 继承两个IUnknown接口,其中一个是OPEN //另一个是CLOSE //直接防护当前这个指针 *ppv = (IID_OPENS*)this; } else if(iid == IID_OPEN_KEY) //对比唯一的GUID接口标识符 { *ppv = (IID_OPENS*)this; } else if(iid == IID_CLOSE_KEY) { *ppv = (IID_CLOSE*)this; } else { //查询不到IID *ppv返回NULL *ppv = NULL; //返回E_NOINTERFACE 表示组件没有该接口(不支持这个接口) return E_NOINTERFACE; } return S_OK; }
组件调用该函数,查询这个组件是否支持该接口
//实例化组件 CPD* p = new CPD(); IUnknown* pkonw = NULL; HRESULT hr; //从组件查询UInknow组件 hr = p->QueryInterface(IID_IUnknown,(void**)&pkonw); if(SUCCEEDED(hr)) //对HRESULT返回值判断 一般用SUCCEEDED { //然后从IUnknown查询 OPEN接口 IID_OPENS* op = NULL; hr = pkonw->QueryInterface(IID_OPEN_KEY,(void**)&op); if(SUCCEEDED(hr)) { //调用接口的方法 op->Open(); } } delete p;
判断两个接口是否相同
if((void*)接口指针1 != (void*)接口指针2)
{
两个接口不同
}
三、引用计数 AddRef和Release
1.内存资源何时释放
组件 = nwe 组件
然后delete 组件,这样就销毁一个组件了
但是把接口传给组件成员变量
组件 = new 组件(接口指针变量)
怎么才能知道什么时候不用这个组件
在main主函数最后面执行delete组件,销毁???
主函数最后面销毁组件实例,可行,但不是一个好办法
因为这样最终是释放组件实例的内存资源,不过却不是
即时(在实例所指组件不用时)地释放内存资源,
这个程序在运行中,占内存将是巨大的
解决这个问题需要用到引用计数技术.
2.引用计数原理
a) 引用计数就是用啦管理对象声明周期的一种技术
b) 对象O可能被外界A,外界B,外界C引用
也就是说A,B,C都在使用这个对象O
c) 每次外界对象引用这个对象,计数器就+1
每次外界不用对象时,计数器减1
d) 在计数值为0时,执行delete this销毁自己的资源.
e) 引用计数使得对象通过引用计数能够知道何时对象
不再被使用,然后及时地删除自身所占的内存资源
f) IUnknown接口的AddRef与Release就是引用计数实现方式
3.AddRef与Release实现与使用
类里有一int类型的变量在构造函数里初始化为0;
他就是引用计数器,然后构造函数调用AddRef引用计数+1
virtual ULONG STDMETHODCALLTYPE AddRef(){return m_Icount++; }
Release实现
virtual ULONG STDMETHODCALLTYPE Release()
{
if(--m_Icount == 0)
{
delete this;
return 0;
}
return m_Icount;
};
如果接口不再调用必须手动调用Release
再赋值后也要调用AddRef()