COM学习笔记(一)

  COM对象跟C++对象比,它只提供接口给外部调用,具有完全严格的封装性;再者C++只是源码层上的重用,COM则在二进制层上建立了统一规范,把接口定义(IDL语言描述,可用工具转换成C/C++头文件或其他语言支持的格式)与接口实现分离,真正地实现了与编程语言无关的程序重用。COM的具体实现可以是DLL、OCX,也可以是EXE程序。COM的调用可以在进程内、进程间或网络主机间,无论以何种方式对于客户端来说都是透明的。


  COM相关的一些概念:
  CLSID:COM对象标识,即128位的GUID(从概率上基本可以保证唯一性)。
  ProgID:COM组件标识符,字符串表示,与 CLSID有对应关系,函数CLSIDFromProgID、ProgIDFromCLSID用于互相转换。
  IID:接口标识,也是GUID,接口定义表现为一组函数的数据结构定义,这组函数表称为虚函数表,每个接口有都一个虚函数表指针。
  入口函数:HRESULT DllGetClassObject(const CLSID &clsid,const IID &iid,void **ppv),一般用来创建类厂对象。
  卸载函数:HRESULT DllCanUnloadNow()。


  接口的继承只继承函数说明,每个接口都从IUnknown接口派生,直接的或间接的。IUnknown接口定义(C++):

  class IUnknown {
  public:
   // 查询COM对象的其他接口指针
   // S_OK成功、E_NOINTERFACE找不到接口(ppv返回NULL)、E_UNEXPECTED发生意外(ppv返回NULL)
   virtual HRESULT _stdcall QueryInterface(const IID &iid,void **ppv) = 0;
   // 引用计数操作
   virtual ULONG _stdcall AddRef() = 0;
   virtual ULONG _stdcall Release() = 0;
   }
  每个COM对象类都有一个类厂接口,C++定义:
  class IClassFactory:public IUnknown {
  public:
   virtual HRESULT _stdcall CreateInstance(IUnknown *pUnknownOuter,const IID &iid, void **ppv) = 0;
   virtual HRESULT _stdcall LockServer(BOOL bLock) = 0;
  }

  COM注册表信息:
  HEKY_CLASSES_ROOT->CLSID下,对于进程内组件,有子键InprocServer32,缺省字符串为文件路径,进程外组件对应的是LocalServer32子键。如果有ProgID子键,说明此COM具有字符串别称,另有HEKY_CLASSES_ROOT->CLSID->别称->CLSID项,能通过别称找到对应CLSID后获取COM组件信息。


  COM注册:
  RegSvr32 文件,调用导出函数DllRegisterServer来完成注册信息。
  RegSvr32 /u 文件,调用导出函数DllUnregisterServer来移除注册信息。


  CoGetClassObject与CoCreateInstance:

  HRESULT CoGetClassObject(const CLSID &clsid,DWORD dwClsContext,COSERVERINFO *pServerInfo,const IID &iid,void **ppv);
  HRESULT CoCreateInstance(const CLSID &clsid,IUnknown *pUnknownOuter,DWORD dwClsContext,const IID &iid,void **ppv);
  COM组件的创建一般不由客户程序直接调用导出函数创建,通常是利用COM库完成组件初始化工具,一般的过程如下:
  1.CoInitialize(NULL)初始化COM库。返回S_OK成功,S_FALSE之前已初始化过,E_UNEXPECTED初始化错误。
  2.CoCreateInstance(),CoCreateInstance函数先传入类厂接口标识调用CoGetClassObject函数,CoGetClassObject内部又会调用导出函数DllGetClassObject创建类厂对象,得到返回的类厂接口指针,接着再调用类厂接口的CreateInstance函数创建COM对象,最后释放类厂对象。
  3.CoFreeUnusedLibraries()卸载COM组件,函数会检测当前进程中的所有组件,当发现某个组件导出函数DllCanUnloadNow返回TRUE时卸载该组件。
  4.CoUninitialize()释放COM库。

  COM库提供的内存管理接口IMalloc:

  // IID_IMaloc:{00000002-0000-0000-c000-000000000046}
  class IMalloc:public IUnknown {
   virtual void* Alloc(ULONG cb) = 0;
   virtual void* Realloc(void *pv,ULONG cb) = 0;
   virtual void Free(void *pv) = 0;
   virtual ULONG GetSize(void *pv) = 0;
   // pv是否由内存管理接口所分配
   virtual int DidAlloc(void *pv) = 0;
   // 使堆内存尽可能减小
   virtual void HeapMinimize() = 0;
   }

  获取IMalloc指针:CoGetMalloc(MEMCTX_TASK,IMalloc **ppMalloc),使用完成后应调用pMalloc->Release()函数释放。
  COM库封装好的内存操作函数:CoTaskMemAlloc(ULONG cb),CoTaskMemFree(void *pv),CoTaskMemRealloc(void *pv,ULONG cb)。


包容与聚合:
  COM的重用有包容与聚合这2种模型,包容是指COM在重用另一个组件接口时,本组件只是另一个组件接口的代理存根,该组件只不过转发调用了已有的接口实现,但是对客户端来说是透明的,客户端并不知道组件的嵌套关系。
  聚合就比较麻烦一点,首先在聚合的外部对象中,只有聚合内部对象接口的定义而没有具体实现(连代理存根都没有),外部对象初始化过程中创建内部对象并保存其IUnknown指针。在客户端查询接口时,外部对象的QueryInterface方法根据IID返回本对象接口或内部对象接口(使用保存的内部IUnknown查询)。根据COM规范,一个COM对象的IUnknown接口值必须是唯一的且可以通过任何一个接口查询到所有的接口,所以要实现聚合内部对象也要作一些配合。内部对象在创建时会判断CoCreateInstance第二个参数IUnknown*的值,如果是NULL则表明未聚合状态创建,否则此参数是外部对象的IUnknown指针表明聚合状态下创建,内部对象QueryInterface处理中如果是聚合状态则转发调用外部对象的QueryInterface(使用保存的外部IUnknown指针)。另外对引用、释放计数也要做到合理对应,避免递归析构等情况。

你可能感兴趣的:(学习笔记)