分类: 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因为 ATL 利用 Visual C 中的__declspec(novtable) 优化,所以在很大程度上依赖两层构造。declspec 取消掉了在抽象基类的构造函数中必须对 vptr 进行的初始化,因为抽象基类中的任何的 vptr 会在派生类中被重写。之所以要进行这种优化,是因为初始化从未被使用过的 vptr 毫无意义。另外,因为不需要为抽象基类分配vtable,从而减少了代码的大小。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; } };
DECLARE_PROTECT_FINAL_CONSTRUCT()这一行代码重新定义了类的 InternalFinalConstructAddRef 和 InternalFinalConstructRelease 来增减引用计数,从而安全地传递可能调用 QueryInterface 的对象指针。
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运行时必须用来构造全局和静态变量。)
class CPager : public CComObjectRootEx一旦你从CComCoClass派生,你的类就已经被添加到ATL Object Map中。ATL所提供的用来简化建立对象映射的宏很像接口映射宏。下面就是为多CLSID服务器建立的一个对象映射。, public CComCoClass , public IPager { public: BEGIN_COM_MAP(CPager) COM_INTERFACE_ENTRY(IPager) END_INTERFACE_MAP() STDMETHODIMP SendMessage(const OLECHAR * pwsz); };
BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_Pager, CPager) OBJECT_ENTRY(CLSID_Laptop, CLaptop) END_OBJECT_MAP()这个代码建立了一个叫 ObjectMap 的 _ATL_OBJMAP_ENTRY 数组,初始化如下:
静态成员函数从 CComCoClass 派生,被隐含式定义。以上定义的对象映射一般通过使用 CComModule 的 Init 方法被传递到ATL:
_Module.Init(ObjectMap, hInstance);这个方法根据创建的服务器类型,在 DllMain 或 WinMain 中被调用。
DECLARE_NOT_AGGREGATABLE(CPager) DECLARE_ONLY_AGGREGATABLE(CPager) DECLARE_POLY_AGGREGATABLE(CPager)这些宏只是将 ATL Creator 定义成一个将被用于初始化对象映射的嵌套类型(CreatorClass)。前面两个宏是自解释的(它们禁止或需要聚合)。 第三个宏需要解释一下。缺省情况下,CComCoClass 使用 ATL 类创建机制,根据是否需要使用聚合来创建两个不同的类之一。如果不需要聚合,则创建新的 CComObject