用ATL建立轻量级的COM对象(六)

输出你的类

   实现了 CComObject ,你就有足够的条件用 C++ new 操作符创建 COM 对象。不过这样做没有什么实用价值,因为毕竟外部客户端使用 CoCreateInstance 或 CoGetClassObject 创建类实例。也就是说,你必须为每个外部类输出类对象。幸运的是ATL分别在它的 CComClassFactory 和 CComClassFactory2 类中提供了缺省的 IClassFactory 和 IClassFactory2接口实现。
    CComClassFactory 不是模板驱动类,但其中有一个函数指针作为数据成员,使用这个函数可以创建对象。ATL提供了一个类模板家族,它们都有一个单独的静态方法 CreateInstance,由 Creators 调用,Creators 提供正确的语义来从 CComClassFactory 创建基于 CComObjectRoot 的对象。下面的这段代码展示了缺省的创建机制:CComCreator,它产生一个模板化的类实例,并用 ATL 中标准的 FinalConstruct 来顺序初始化对象。

 ATL Creator 


 template 
   class CComCreator {
 public:
     static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv) {
                 HRESULT hRes = E_OUTOFMEMORY;
                 T1* p = NULL;
                 ATLTRY(p = new T1(pv))
                 if (p != NULL) {
                         p->SetVoid(pv);
                         p->InternalFinalConstructAddRef();
                         hRes = p->FinalConstruct();
                         p->InternalFinalConstructRelease();
                         if (hRes == S_OK)
                                 hRes = p->QueryInterface(riid, ppv);
                         if (hRes != S_OK)
                                 delete p;
                 }
                 return hRes;
         }
 };
 
 template 
   class CComFailCreator {
 public:
         static HRESULT WINAPI CreateInstance(void*, REFIID, 
                                              LPVOID*)
     { return hr; }
 };
 
 template 
   class CComCreator2 {
 public:
         static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, 
                                              LPVOID* ppv) {
                 HRESULT hRes = E_OUTOFMEMORY;
                 if (pv == NULL)
                         hRes = T1::CreateInstance(NULL, riid, ppv);
                 else
                         hRes = T2::CreateInstance(pv, riid, ppv);
                 return hRes;
         }
 };     
      

    因为 ATL 利用 Visual C++ 中的__declspec(novtable) 优化,所以在很大程度上依赖两层构造。declspec 取消掉了在抽象基类的构造函数中必须对 vptr 进行的初始化,因为抽象基类中的任何的 vptr 会在派生类中被重写。之所以要进行这种优化,是因为初始化从未被使用过的 vptr 毫无意义。另外,因为不需要为抽象基类分配vtable,从而减少了代码的大小。
    使用这种技术的类(包括大多数 ATL 基类)需要当心,不要调用构造器中的虚函数。但是,为了在初始化时允许对虚函数的调用,ATL 的 Creators 调用 FinalConstruct 方法,在这个方法中进行所有重要的初始化工作。在 FinalConstuct 中,从C++的角度看,你的类已经完全构造好了,也就是说你的所有对象的 vptr 完全被派生化。同时,基于 CComObject 的打包器也同时构造好了,允许你存取在 COM 聚合或 tear-off 情况下无法知道的控制。
    如果在调试器中单步顺序执行 Creator 调用,你将注意到在缺省情况下对 InternalFinalConstructAddRef 和 InternalFinalConstructRelease 的调用什么也没做,但是,如果你打算在你的 FinalConstruct 实现中创建 COM 聚合,你可能会临时增加一次对象的引用计数,以防止它过早销毁(这发生在某个聚合对象调用 QueryInterface时)。你能通过添加下面的类定义行进行自我保护:

 DECLARE_PROTECT_FINAL_CONSTRUCT()
      

这一行代码重新定义了类的 InternalFinalConstructAddRef 和 InternalFinalConstructRelease 来增减引用计数,从而安全地传递可能调用 QueryInterface 的对象指针。
    每一个基于ATL的工程都包含着一个 CComModule 派生类的实例。除了实现前面提到过的服务器生命期行为外,CComModule 还维持着一个 CLSID 到 ClassObject 的映射(叫做对象映射 Object Map)向量来提供所有外部可创建类。这个对象映射被用于实现进程内服务器的 DllGetClassObject,并且它为进程外服务器每次调用 CoRegisterClassObject 提供参数。虽然能直接显式地使用 CComClassFactory 和 Creator 类,但通常都是在 ATL 对象映射基础的上下文中使用。 ATL Object Map 是一个_ATL_OBJMAP_ ENTRY结构数组:

   
 struct _ATL_OBJMAP_ENTRY {
   const CLSID* pclsid;
   HRESULT (*pfnUpdateRegistry)(BOOL bRegister);
   HRESULT (*pfnGetClassObject)(void* pv, 
                       REFIID riid, LPVOID* ppv);
   HRESULT (*pfnCreateInstance)(void* pv, 
                       REFIID riid, LPVOID* ppv);
   IUnknown* pCF;
   DWORD dwRegister;
   LPCTSTR  (* pfnGetObjectDescription)(void);
 };   

pfnGetClassObject成员的调用是在第一次需要创建新的类对象时。这个函数被作为 Creator 函数(pfnCreateInstance)的第一个参数传递,并且返回的结果接口指针被缓存在pCF成员中。通过按需要创建类对象,而不是静态地实例化变量,就不再需要使用带虚函数的全局对象,使得基于 ATL 的工程不用C运行库就能进行链接。(在 DllMain / WinMain 以前,C运行时必须用来构造全局和静态变量。)
    虽然你可以显式地定义用于对象映射的各种函数,通常的方法是将 CComCoClass 添加到你自己类的基类列表中。CComCoClass 是一个模板类,它有两个模板参数:你自己的类名和对应的 CLSID 指针。它添加适当的类型定义和静态成员函数来提供对象映射必须的功能。下面的代码示范了 CComCoClass 的使用:

    
class CPager 
  : public CComObjectRootEx
  ,
    public CComCoClass,
    public IPager 
 {
 public:
 BEGIN_COM_MAP(CPager)
   COM_INTERFACE_ENTRY(IPager)
 END_INTERFACE_MAP()
   STDMETHODIMP SendMessage(const OLECHAR * pwsz);
 };   

一旦你从CComCoClass派生,你的类就已经被添加到ATL Object Map中。ATL所提供的用来简化建立对象映射的宏很像接口映射宏。下面就是为多CLSID服务器建立的一个对象映射。

BEGIN_OBJECT_MAP(ObjectMap)
   OBJECT_ENTRY(CLSID_Pager, CPager)
   OBJECT_ENTRY(CLSID_Laptop, CLaptop)
 END_OBJECT_MAP()
      

这个代码建立了一个叫 ObjectMap 的 _ATL_OBJMAP_ENTRY 数组,初始化如下:

 static _ATL_OBJMAP_ENTRY ObjectMap[] = {
 {  &CLSID_Pager, &CPager::UpdateRegistry,     
    &CPager::_ClassFactoryCreatorClass::CreateInstance, 
    &CPager::_CreatorClass::CreateInstance, NULL, 0, 
    &CPager::GetObjectDescription 
 },
 {  &CLSID_Laptop, &CLaptop::UpdateRegistry,     
    &CLaptop::_ClassFactoryCreatorClass::CreateInstance, 
    &CLaptop::_CreatorClass::CreateInstance, NULL, 0, 
    &CLaptop::GetObjectDescription 
 },
 { 0, 0, 0, 0 } };      

静态成员函数从 CComCoClass 派生,被隐含式定义。以上定义的对象映射一般通过使用 CComModule 的 Init 方法被传递到ATL:

     
 _Module.Init(ObjectMap, hInstance);
      

这个方法根据创建的服务器类型,在 DllMain 或 WinMain 中被调用。
    缺省情况下,CcomCoClass 为你的类提供了一个标准的类工厂,允许客户端聚合你的对象。你可以通过添加下面的类定义代码行来改变缺省的聚合行为:

     
      DECLARE_NOT_AGGREGATABLE(CPager)
      DECLARE_ONLY_AGGREGATABLE(CPager)
      DECLARE_POLY_AGGREGATABLE(CPager)
      

    这些宏只是将 ATL Creator 定义成一个将被用于初始化对象映射的嵌套类型(CreatorClass)。前面两个宏是自解释的(它们禁止或需要聚合)。 第三个宏需要解释一下。缺省情况下,CComCoClass 使用 ATL 类创建机制,根据是否需要使用聚合来创建两个不同的类之一。如果不需要聚合,则创建新的 CComObject 实例。如果需要聚合,则创建新的CComAggObject 实例。也就是说两个不同的 vtables 必须在可执行文件中出现。对照之下,DECLARE_POLY_ AGGREGATABLE 总是创建一个 CComPolyObject 实例,并根据对象是否聚合来初始化这个外部控制指针。亦即只要定义一个C++类,只需一个 vtable。这个技术的不足之处是:非聚合对象的每个实例必须为非代理 IUnknown 指针多用4个字节。不论哪种情况,支持聚合都不需要实际的编码,而只是在实例和代码大小之间作出取舍。

你可能感兴趣的:(职场,休闲)