Com技术入门教程

来源:互联网 发布:gtx1060 淘宝 编辑:程序博客网 时间:2024/04/23 19:19

本文假设读者已经具备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组件对象的引用计数的变化;代码结束后,引用计数是否回复为零了?谁是接口提供者,谁是接口使用者 。       

     

     “做为一个环保人士,我们一定时刻留意我们制造的东西、我们使用过的东西是如何被回收的! ”--叉叉语录