Com技术入门知识
Com技术入门知识
本文假设读者已经具备C++开发技能。
目录:
1、背单词
2、Com技术两大核心思想
3、开发自己的Com组件
4、Com技术三板斧
1、背单词:CoCreateInstance(创建组件对象);QueryInterface(查询接口);ComPtr(安全指针); HRESULT(接口函数返回值类型); CLSID(唯一编号)
2、Com技术的两大核心思想:
-
接口: 为什么要搞个接口这玩意出来?C++的类把数据和操作数据的函数组织到了一起,形成了“对象”这个概念;但在使用中,懒惰的人们又发现我们调用一个对象时,往往只关心对象的一小部分功能; 说是我们在调用一个对象的功能,其实我们只关心我们需要调用的功能。C++时代,在编写大型程序时,其实已经有技艺高超的架构设计师使用了自己的解决办法,即设计适量的“纯虚基类”做为模块间调用的协议。在Com体系中,则强化了这种做法,Com组件对于C++的对象,Com接口则对于C++的纯虚类;Com系统要求,组件之间的调用只能通过接口实现。一个接口往往是一组相关的功能函数的集合, 一个组件可以有支持多个接口,不同组件可以支持同一个接口,如,飞机组件支持IFLy接口,鸟儿组件也支持IFLy接口,则代码这两个组件对象都拥有飞的能力。 我们利用IFLy接口控制飞机对象时,只需要知道飞机飞行的功能(即IFLy中的接口函数),而不需要知道飞机对象的其他复杂功能(加油、维护等等),而且这些功能你要不懂最好不要操作,弄不好会机毁人亡的;
“你知道的越少越安全!--叉叉大佬语录”。
-
CLSID :CLSID是个128位的数值,利用了时间、硬件信息和随机算法,保证生成的每个CLSID都是全世界唯一的。Com充分利用了这种唯一特性,Com技术中给每个Com组件捆绑了一个CLSID,给每个接口捆绑了一个CLSID;这样,我们只要知道一个组件的CLSID就可以创建这个组件对象,只要知道了一个接口的CLSID,就可以拥有访问这个接口的能力。想想这会来什么样的变革? 组件ID,接口ID加接口(二进制通讯协议)就是我们和一个组件交互所需所有条件。发现没有,这三个条件和语言无关、和组件位置无关;这就是为什么Com可以实现多编程语言相互调用;为什么Com可以构建分布式系统。
“你想给我介绍个女朋友?好啊,给我她的手机号就可以了,其他我自己搞定!”--叉叉语录
3、 开发自己的Com组件:
用VC的向导做一个组件示例(具体怎么做,随便找本Com入门书照着点就可以了):
组件名MathCom,组件CLSID为 CLSID_MathCom; A支持接口IMathCom,接口CLSID为IID_IMathCom; 支持接口IMathCom2,接口CLSID为IID_IMathCom2;
为IMathCom增加一个函数func(); 为IMathCom2增加一个函数func2();
下面的组件创建和调用都会以该组件为例。
4、Com技术三板斧:
-
第一斧:创建Com组件对象;
常用的组件创建函数 CoCreateInstance的定义:
STDAPI CoCreateInstance( REFCLSID rclsid,//所创建的组件的CLSID(组件编号,128位全球唯一) LPUNKNOWN pUnkOuter,//=NULL(只有在组件聚合时,该参数才被利用,一般开发中很少使用聚合技术) DWORD dwClsContext,//=CLSCTX_INPROC_SERVER, (指定COM对象的运行环境,一般使用CLSCTX_INPROC_SERVER即可,表明是组件和调用者在一个进程内) REFIID riid,//希望获得到ppv接口指针的接口CLSID。 LPVOID * ppv//获得创建组件对象的某个接口的指针; );
创建MathCom组件代码:
IMathCom *pMath; HRESULT hr; hr = CoCreateInstance(CLSID_MathCom, NULL, CLSCTX_INPROC_SERVER, IID_IMathCom, (LPVOID*)&pMath);
看CoCreateInstance参数定义,第二第三个参数用那两个固定值就不管了,第一个参数就是组件的CLSID,第四个参数就是接口的CLSID,第五个是输出参数,用来传出第四个参数指定的接口的指针的;正如上面介绍CLSID时说的一样,创建一个组件只要知道组件的CLSID就行了,第四第五个参数是因为创建了一个组件以后总是要使用的,而使用组件的唯一办法就是通过它的某一个接口。所以,如果创建的组件有多个接口,你可以选择任何一个你用起来方便的接口的CLSID做为第四个参数。
简化组件的创建? CComPtr的CoCreateInstance函数:
CComPtr
<IMathCom> pMathCom; pMathCom.CoCreateInstance( CLSID_MathCom ); CComPtr的CoCreateInstance的定义:
HRESULT CoCreateInstance( REFCLSID rclsid, LPUNKNOWN pUnkOuter = NULL, DWORD dwClsContext = CLSCTX_ALL )
考虑一下,为什么CComPtr的函数可以省掉两个第四第五个函数。
-
第二斧:调用Com组件的接口功能;
用VC给IMathCom接口增加函数:func1(); 给IMathCom2接口增加函数func2();
要调用Com组件的接口,首先要掌握如何获取接口;在创建组件时,我们会获得一个组件的接口。通过这个接口当然可以访问该接口内的功能函数了。
CComPtr
<IMathCom> pMathCom; pMathCom->CoCreateInstance( CLSID_MathCom ); pMathCom->func(); 如何调用另一个接口的函数?QueryInterface可以让我们从一个接口查询到同组件的其他任意接口。
CComPtr
<IMathCom2> pMathCom2; HRESULT hr; hr = pMathCom->QueryInterface(IID_IMathCom2,(LPVOID*)&pMathCom2); //或者简写为:hr = pMathCom->QueryInterface(&pMathCom2); 思考:为什么可以这样写? if( SUCCEED(hr) ) pMathCom2->func2(); QueryInterface函数哪里来的?所有的接口都继承于IUnknown ;
class IUnknown { public: virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0; virtual ULONG AddRef() = 0; virtual ULONG Release() = 0; };
-
第 三斧:组件释放控制
组件使用了“引用计数”来实现组件释放的控制;引用计数记录了当前有几个使用者,当引用计数为0时,组件自动释放。
接口提供者负责给引用计数加1,接口使用者在释放接口变量前负责给引用计数减1;
IUnknown:AddRef()让组件的引用计数加一, Release() 让组件的引用计数减一。
我们举个例子来分析引用计数是如何变化的:
//调用前面做的组件为例:组件名MathCom,组件CLSID为 CLSID_MathCom; A支持接口IMathCom,接口CLSID为IID_IMathCom; 支持接口IMathCom2,接口CLSID为IID_IMathCom2 //IMathCom有函数func(); IMathCom2有函数func2(); IMathCom *pMath = NULL; HRESULT hr; hr = CoCreateInstance(CLSID_MathCom, NULL, CLSCTX_INPROC_SERVER, IID_IMathCom, (LPVOID*)&pMath); if(FAILED(hr)) return E_FAIL; pMath->func(); IMathCom *pMathSame = NULL; pMathSame = pMath; pMathSame->AddRef(); pMathSame->func(); //... pMathSame->Release(); pMathSame = NULL; IMathCom2 *pMath2 = NULL; hr = pMath->QueryInterface( IID_IMathCom2,(LPVOID*)&pMath2); if(FAILED(hr)) return E_FAIL; pMath2->func2(); //.... pMath2->Release(); pMath->Release();
CComPtr: 通过析构函数和重载"=" 操作符,CComPtr较好的封装了引用计数的处理。
下面看一下使用CComPtr写代码:
CComPtr<IMathCom> pMath; HRESULT hr; hr = pMath->CoCreateInstance(CLSID_MathCom); if(FAILED(hr)) return E_FAIL; pMath->func(); CComPtr<IMathCom> pMathSame; pMathSame = pMath; pMathSame->func(); CComPtr<IMathCom2> pMath2; hr = pMath->QueryInterface(&pMath2); if(FAILED(hr)) return E_FAIL; pMath2->func2();
最后,我们再看一下接口指针做为函数参数传递时,它的引用计数的控制。
HRESULT CreateCom( IMathCom **ppMathOut ) { CComPtr<IMathCom> pMath; HRESULT hr; hr = pMath->CoCreateInstance(CLSID_MathCom); if(FAILED(hr)) return E_FAIL; *ppMathOut = pMath; } //调用上面函数的代码 CComPtr<IMathCom> pMath; HRESULT hr; hr = CreateCom( &pMath ); if(FAILED(hr)) return hr; pMath->func();
仔细分析以上三块代码每条语句执行后,MathCom组件对象的引用计数的变化;代码结束后,引用计数是否回复为零了?谁是接口提供者,谁是接口使用者。
“做为一个环保人士,我们一定时刻留意我们制造的东西、我们使用过的东西是如何被回收的! ”--叉叉语录
-