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

时间:2021-12-05 23:19:57

第一部分:为什么要使用ATL。

第二部分:起步篇。

第三部分:实现IUnknown。

第四部分:实现接口。

第五部分:不要过分抽象。

输出你的类

实现了 CComObject ,你就有足够的条件用 C++ new 操作符创建 COM 对象。不过这样做没有什么实用价值,因为毕竟外部客户端使用 CoCreateInstance 或 CoGetClassObject 创建类实例。也就是说,你必须为每个外部类输出类对象。幸运的是ATL分别在它的 CComClassFactory 和 CComClassFactory2 类中提供了缺省的 IClassFactory 和 IClassFactory2接口实现。

CComClassFactory 不是模板驱动类,但其中有一个函数指针作为数据成员,使用这个函数可以创建对象。ATL提供了一个类模板家族,它们都有一个单独的静态方法 CreateInstance,由 Creators 调用,Creators 提供正确的语义来从 CComClassFactory 创建基于 CComObjectRoot 的对象。下面的这段代码展示了缺省的创建机制:CComCreator,它产生一个模板化的类实例,并用 ATL 中标准的 FinalConstruct 来顺序初始化对象。

01.ATL Creator02. 03. 04.template  class CComCreator {05.public:06.static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv) {07.HRESULT hRes = E_OUTOFMEMORY;08.T1* p = NULL;09.ATLTRY(p = new T1(pv))10.if (p != NULL) {11.p->SetVoid(pv);12.p->InternalFinalConstructAddRef();13.hRes = p->FinalConstruct();14.p->InternalFinalConstructRelease();15.if (hRes == S_OK)16.hRes = p->QueryInterface(riid, ppv);17.if (hRes != S_OK)18.delete p;19.}20.return hRes;21.}22.};23. 24.template  class CComFailCreator {25.public:26.static HRESULT WINAPI CreateInstance(void*, REFIID,27.LPVOID*)28.return hr; }29.};30. 31.template  class CComCreator2 {32.public:33.static HRESULT WINAPI CreateInstance(void* pv, REFIID riid,34.LPVOID* ppv) {35.HRESULT hRes = E_OUTOFMEMORY;36.if (pv == NULL)37.hRes = T1::CreateInstance(NULL, riid, ppv);38.else39.hRes = T2::CreateInstance(pv, riid, ppv);40.return hRes;41.}42.};    

 因为 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时)。你能通过添加下面的类定义行进行自我保护:

1.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结构数组:

01.struct _ATL_OBJMAP_ENTRY {02.const CLSID* pclsid;03.HRESULT (*pfnUpdateRegistry)(BOOL bRegister);04.HRESULT (*pfnGetClassObject)(void* pv,05.REFIID riid, LPVOID* ppv);06.HRESULT (*pfnCreateInstance)(void* pv,07.REFIID riid, LPVOID* ppv);08.IUnknown* pCF;09.DWORD dwRegister;10.LPCTSTR  (* pfnGetObjectDescription)(void);11.};

pfnGetClassObject成员的调用是在第一次需要创建新的类对象时。这个函数被作为 Creator 函数(pfnCreateInstance)的第一个参数传递,并且返回的结果接口指针被缓存在pCF成员中。通过按需要创建类对象,而不是静态地实例化变量,就不再需要使用带虚函数的全局对象,使得基于 ATL 的工程不用C运行库就能进行链接。(在 DllMain / WinMain 以前,C运行时必须用来构造全局和静态变量。)

虽然你可以显式地定义用于对象映射的各种函数,通常的方法是将 CComCoClass 添加到你自己类的基类列表中。CComCoClass 是一个模板类,它有两个模板参数:你自己的类名和对应的 CLSID 指针。它添加适当的类型定义和静态成员函数来提供对象映射必须的功能。下面的代码示范了 CComCoClass 的使用:

01.class CPager02.public CComObjectRootEx,03.public CComCoClass,04.public IPager05.{06.public:07.BEGIN_COM_MAP(CPager)08.COM_INTERFACE_ENTRY(IPager)09.END_INTERFACE_MAP()10.STDMETHODIMP SendMessage(const OLECHAR * pwsz);11.};

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

1.BEGIN_OBJECT_MAP(ObjectMap)2.OBJECT_ENTRY(CLSID_Pager, CPager)3.OBJECT_ENTRY(CLSID_Laptop, CLaptop)4.END_OBJECT_MAP()

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

01.static _ATL_OBJMAP_ENTRY ObjectMap[] = {02.{  &CLSID_Pager, &CPager::UpdateRegistry,    03.&CPager::_ClassFactoryCreatorClass::CreateInstance,04.&CPager::_CreatorClass::CreateInstance, NULL, 0,05.&CPager::GetObjectDescription06.},07.{  &CLSID_Laptop, &CLaptop::UpdateRegistry,    08.&CLaptop::_ClassFactoryCreatorClass::CreateInstance,09.&CLaptop::_CreatorClass::CreateInstance, NULL, 0,10.&CLaptop::GetObjectDescription11.},12.{ 0, 0, 0, 0 } };

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

1._Module.Init(ObjectMap, hInstance);

这个方法根据创建的服务器类型,在 DllMain 或 WinMain 中被调用。

缺省情况下,CcomCoClass 为你的类提供了一个标准的类工厂,允许客户端聚合你的对象。你可以通过添加下面的类定义代码行来改变缺省的聚合行为:

1.DECLARE_NOT_AGGREGATABLE(CPager)2.DECLARE_ONLY_AGGREGATABLE(CPager)3.DECLARE_POLY_AGGREGATABLE(CPager)

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


http://www.vckbase.com/index.php/wv/189.html