Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9488223
  • 博文数量: 1227
  • 博客积分: 10026
  • 博客等级: 上将
  • 技术积分: 20273
  • 用 户 组: 普通用户
  • 注册时间: 2008-01-16 12:40
文章分类

全部博文(1227)

文章存档

2010年(1)

2008年(1226)

我的朋友

分类: C/C++

2008-04-23 21:35:36

用ATL建立轻量级的COM对象

第六部分

作者:

第一部分:为什么要使用ATL。
第二部分:起步篇。
第三部分:实现IUnknown。
第四部分:实现接口。
第五部分:不要过分抽象。

输出你的类

实现了 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个字节。不论哪种情况,支持聚合都不需要实际的编码,而只是在实例和代码大小之间作出取舍。(待续)
阅读(242) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~