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的方法声明为纯虚函数,它们将由其派生类实现。