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

来源:互联网 发布:mac foxmail 导入邮件 编辑:程序博客网 时间:2024/05/20 13:07

第一部分:为什么要使用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个字节。不论哪种情况,支持聚合都不需要实际的编码,而只是在实例和代码大小之间作出取舍。(待续)
0 0
原创粉丝点击