CComObjectRootObjectBase中以表驱动的方式对接口的查询作了一个内部的实现,即InternalQueryInterface()。所以在创建基于ATL的COM类时,需要创建一个包含所有实现接口的映射表。

1. BEGIN_COM_MAP、END_COM_MAP、COM_INTERFACE_ENTRY与COM_INTERFACE_ENTRY2宏

 

ATL提供了

BEGIN_COM_MAP、

END_COM_MAP、

COM_INTERFACE_ENTRY

COM_INTERFACE_ENTRY2

这4个宏来创建接口映射表。

 

假设一个类CClassA继承了接口IIntA和IIntB,则该类的接口映射表创建如下:

class CClassA : public CComObjectRootEx<CComSingleThreadMode>
{
    BEGIN_COM_MAP(CClassA)
        COM_INTERFACE_ENTRY(IIntA)
        COM_INTERFACE_ENTRY(IIntB)
    END_COM_MAP()
    ......
};

而当CClassB继承了IIntC和IIntD,并且IIntC和IIntD都继承自IDispatch接口。此时,如果客户程序在查询IDispatch接口,QueryInterface所返回的IDispatch接口指针将无法确定其属于IIntC还是IIntD。在这种情况下,需要指定IDispatch接口指针的默认指向。 COM_INTERFACE_ENTRY2()宏即是用于完成该功能。下面代码将对IDispatch接口的请求默认指向属于IIntD的IDispatch接口指针。

class CClassB : public CComObjectRootEx<CComSingleThreadMode>
{
    BEGIN_COM_MAP(CClassA)
        COM_INTERFACE_ENTRY(IIntC)
        COM_INTERFACE_ENTRY(IIntD)
        COM_INTERFACE_ENTRY2(IDispatch, IIntD)
    END_COM_MAP()
    ......
};

 

2. 接口映射表的实现与所提供的功能

将CClassB中的宏扩展可以得到以下代码并稍作精简得:
class CClassB : public CComObjectRootEx<CComSingleThreadMode>
{
// BEGIN_COM_MAP(CClassB)
public:
    typedef CClassB _ComMapClass;
    static HRESULT _Cache(void* pv, REFIID iid, void* ppvObject, DWORD_PTR dw)
    {
        _ComMapClass* p = (_ComMapClass*)pv;
        p->Lock();
        HRESULT hRes = CComObjectRootBase::_Cache(pv, iid, ppvObject, dw);
        p->Unlock();
        return hRes;
    }


    IUnknown* _GetRawUnknown()
    {  return (IUnknown*)((INT_PTR)this + _GetEntries()->dw); }


    HRESULT _InternalQueryInterface(REFIID iid, void** ppvObj)
    { return InternalQueryInterface(this, _GetEntries(), iid, ppvObj);  }


    const static ATL::_ATL_INTMAP_ENTRY* _GetEntries()
    {
        static const ATL::_ATL_INTMAP_ENTRY _entries[] =
        {
// COM_INTERFACE_ENTRY(IIntC)
            { &_ATL_IIDOF(IIntC),
              offsetofclass(IIntC, _ComMapClass),
              _ATL_SIMPLEMAPENTRY
            },
// COM_INTERFACE_ENTRY(IIntD)
            { &_ATL_IIDOF(IIntD),
              offsetofclass(IIntD, _ComMapClass),
              _ATL_SIMPLEMAPENTRY
            },
// COM_INTERFACE_ENTRY2(IDispatch, IIntD)
            { &_ATL_IIDOF(IIntD),
              reinterpret_cast<DWORD_PTR>(static_cast<IDispatch*>(
              static_cast<IIntD*>(reinterpret_cast<_ComMapClass*>(8)))) - 8,
              _ATL_SIMPLEMAPENTRY
            },
// END_COM_MAP()
            { NULL, 0, 0}
        };
        return &_entries;
    }
    virtual ULONG AddRef()  = 0;
    virtual ULONG Release() = 0;
    HRESULT QueryInterface(REFIID, void*) = 0; (#add 此三个函数顺序怎么是这样?)
};

 

根据上述代码,BEGIN_COM_MAP()等宏的作用,关键在于提供了一个静态的_GetEntries()方法,用于获取在该方法中创建的一个静态COM接口映射表。其中,当base类(或接口)是derived类(或接口)的父类(或接口)时,offsetofclass(base,derived)宏用于返回base接口(或类)指针在类中相对derived指针的在派生类虚表中的指针偏移值。_ATL_SIMPLEMAPENTRY常量表示_ATL_INTMAP_ENTRY结构的第二个成员dw表示base与derived指针的偏移值。COM_INTERFACE_ENTRY2与COM_INTERFACE_ENTRY的主要区别即为此偏移值的计算。

同时,END_COM_MAP()宏还将IUnknown的AddRef()和Release()方法声明为纯虚函数。由此看出,IUnknown接口的方法将注定有该类的子类实现。

3. 结论

每个基于ATL的COM对象必须首先创建一个静态的接口映射表,这个表的创建工作由BEGIN_COM_MAP、END_COM_MAP、COM_INTERFACE_ENTRY与COM_INTERFACE_ENTRY2这四个宏来完成。它们创建了接口映射表和接口映射表的获取函数,同时还将IUnknown的方法声明为纯虚函数,它们将由其派生类实现。


 

posted on 2011-06-01 14:00  maxweii  阅读(803)  评论(0编辑  收藏  举报