COM学习笔记(二)

来源:互联网 发布:mac在线是什么意思 编辑:程序博客网 时间:2024/05/17 03:43
  扯点闲话,上一篇COM学习笔记(一)是基于爱民大师的《COM原理与应用》,学到中间顿感山路有点弯。经网友推荐转看世界大作《COM技术内幕》(Dale Rogerson著,杨秀章译),引用网友的话大概是说如果所有技术方面的书籍都像《COM技术内幕》这样写,那么掌握它们就没有那么难了。爱民大师除了翻译外,自著书越看越像概括整理的实验论文,国内顶尖大师尚且如此,叫我不得不崇洋媚外了。至于是大家没有能力水平写不出,还是其他原因不愿那样写,没心思去追究了,好好做我的学习笔记吧。

    interface IUnknown    {    public:        virtual HRESULT STDMETHODCALLTYPE QueryInterface(             /* [in] */ REFIID riid,            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) = 0;                virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;                virtual ULONG STDMETHODCALLTYPE Release(void) = 0;    }
  用C++实现COM组件:一个DLL可以实现多个组件,一个组件可以包含多个接口,每个接口对应一个抽象类,在内存结构里表示就是对应一个虚函数表指针,COM规定任何接口都要继承于IUnknown接口(UNKNWN.H中定义),因此每个接口的虚函数表前三项分别为QueryInterface、AddRef、Release这三个成员函数。QueryInterface函数是用来查询组件接口的,返回值S_OK表示成功,E_NOINTERFACE查询失败,从QueryInterface查询到的IUnknown指针必须是同一值。AddRef、Release函数控制接口引用计数,当引用计数为0时便释放此COM对象。一般在创建一个接口或查询一个接口(得到其指针)的函数代码内部,返回前应当调用AddRef自动增加引用计数,客户端获得接口后当不再需要时显式调用Release解除引用。客户端一般不会主动调用AddRef,除了一些特殊情况,如将接口指针赋值给另一个指针变量前客户端应显式调用AddRef,合理对称使用AddRef、Release就能控制COM对象的生命周期。
  内存管理客户端和组件能由两种不同语言所编写,试想在组件接口中分配的内存(通过参数返回给客户程序)该如何管理?好在COM库已提供有现成的机制,不必我们自己去实现了。使用CoGetMalloc库函数得到IMalloc接口指针,IMalloc接口成员函数Alloc、Free用来分配和释放内存。还能再方便一点,直接使用COM库的封装函数CoTaskMemAlloc和CoTaskMemFree。

  其他信息参考COM学习笔记(一),下面举一个用DLL实现组件的实列。首先在DLL文件中以C命名方式导出四个函数,函数原型如下:
    // 注册函数    HRESULT __stdcall DllRegisterServer();    // 反注册函数    HRESULT __stdcall DllUnregisterServer();    // 获取类厂接口指针    HRESULT __stdcall DllGetClassObject(REFCLSID clsid,REFIID iid,LPVOID *ppv);    // 卸载询问函数    HRESULT __stdcall DllCanUnloadNow();
  前两个函数DllRegisterServer、DllUnregisterServer向注册表里写入或删除组件信息,组件是使用CLSID识别载入的,装载过程中会查找注册表"HKEY_CLASSES_ROOT\CLSID\组件GUID字符串\InProcServer32"项的默认值,此值是我们DLL的文件路径名。使用GUID去标识组件信息是COM约定,虽然看似绕了一点,慢慢会发现带来的好处无穷。写到这里大家应该明白了为什么使用组件前必须先注册,System32目录下RegSvr32.exe程序就是调用这两个函数来注册/反注册组件的。
  继续往下走,先来几个COM库函数原型:
    HRESULT CoInitialize(LPVOID pvReserved);    void CoUninitialize();    STDAPI CoCreateInstance(      REFCLSID rclsid,     //Class identifier (CLSID) of the object      LPUNKNOWN pUnkOuter, //Pointer to controlling IUnknown      DWORD dwClsContext,  //Context for running executable code      REFIID riid,         //Reference to the identifier of the interface      LPVOID * ppv         //Address of output variable that receives                            // the interface pointer requested in riid);    STDAPI CoGetClassObject(      REFCLSID rclsid,  //CLSID associated with the class object      DWORD dwClsContext,                    //Context for running executable code      COSERVERINFO * pServerInfo,                        //Pointer to machine on which the object is to                         // be instantiated      REFIID riid,      //Reference to the identifier of the interface      LPVOID * ppv      //Address of output variable that receives the                         // interface pointer requested in riid);    void CoFreeUnusedLibraries();
  典型的客户程序调用组件一般过程是这样的:CoInitialize(NULL)初始化COM库环境,CoCreateInstance函数创建组件对象并返回接口,程序使用接口进行一些操作处理,结束后CoFreeUnusedLibraries()卸载组件程序,CoUninitialize()清除COM库环境。
  重点说一下CoCreateInstance函数,这是个封装函数,其内部先调用CoGetClassObject函数,不难发现它俩有4个参数是一样的,对于第三个参数pServerInfo传入NULL,意思是本机组件而非远程的。CoGetClassObject函数根据CLSID从注册表里找到DLL文件路径,LoadLibrary之,再调用导出函数DllGetClassObject获得类厂接口指针。然后调用类厂接口成员函数CreateInstance创建组件对象,返回组件riid接口的指针,最后使用Release()释放类厂对象。
   类厂接口原型(在UNKNWN.H中定义):
    interface IClassFactory : public IUnknown    {    public:        virtual /* [local] */ HRESULT STDMETHODCALLTYPE CreateInstance(             /* [unique][in] */ IUnknown __RPC_FAR *pUnkOuter,            /* [in] */ REFIID riid,            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) = 0;                virtual /* [local] */ HRESULT STDMETHODCALLTYPE LockServer(             /* [in] */ BOOL fLock) = 0;            };
  由上面的调用过程可知DLL的第三个导出函数DllGetClassObject需要创建一个类厂接口对象,在C++里用抽象类来实现接口,实际上就是创建一个继承于IClassFactory的抽象类对象,其中这个类中的CreateInstance函数创建rclsid组件的接口类(IUnknown的派生类)对象返回类指针。LockServer内部维护一个计数值,用来控制DLL文件生命周期。
  当使用完毕调用CoFreeUnusedLibraries卸载组件的时候,CoFreeUnusedLibraries会遍历询问进程载入的每一个组件DLL文件是否可以卸载。也许你猜到了,DllCanUnloadNow就是CoFreeUnusedLibraries询问的函数,如果返回S_OK,便调用FreeLibrary卸载此DLL清除内存空间。

  系列二结束语:源码就不贴了,最近十分厌恶Script Kid,拿个不恰当的比喻Script Kid好比微博写个几十个字都是COPY党,是残渣是垃圾;而程序员们好比博客,每一个原创博文写出来都是很累的,要技术还要时间,都是精华。另外内核大神离我太远,只有崇拜的份,MJ0011、PJF、安焦牛人等等;远的不说说近的,如今程序员们真的快成码农了,Script Kid还嫌不够,连张三李四都挤进队伍,我真的觉得程序员的门槛应该要高一点。


0 0
原创粉丝点击