在CComObjectRootEx类中,实现了线程安全的引用计数管理。而在CComObjectRootEx的父类CComObjectRootBase中,存在对QueryInterface的一个内部实现——InternalQueryface()。
1. CComObjectRootBase类
class CComObjectRootBase
{
public:
......
static HRESULT InternalQueryInterface(
void* pThis, const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObj)
{ return hRes = AtlInternalQueryInterface(pThis, pEntries, iid, ppvObj); }
......
};
以上代码作了一些简化,去掉了调试用的条件编译选项。在代码中,InternalQueryInterface作为静态方法存在,并且仅仅将执行过程转给了AtlInternalQueryInterface()。其方法参数与标准的QueryInterface方法相比也有不同,其中也有一个新的结构类型_ATL_INTMAP_ENTRY,并且InternalQueryInterface和AtlInternalQueryInterface函数的参数一一对应。
2. _ATL_INTMAP_ENTRY结构和AtlInternalQueryInterface()函数
在atlbase.h中,_ATL_INTMAP_ENTRY结构类型有以下声明:
struct _ATL_INTMAP_ENTRY
{
const IID * piid;
DWORD_PTR dw;
_ATL_CREATORARGFUNC* pFunc;
};
其中piid表示接口的ID,其他的成员变量说明现在暂时无从知晓,只知_ATL_CREATORARGFUNC在atlbase.h中的声明是:
typedef HRESULT (WINAPI _ATL_CREATORARGFUNC) (
void* pv, REFIID riid, LPVOID* ppv, DWORD_PTR dw);
同样,这个函数类型在MSDN中也没有找到说明。
跳过这些暂时无法弄清的声明,看看AtlInternalQueryInterface()函数的实现:
ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis,
const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
if (InlineIsEqualUnknown(iid))
{
IUnknown* pUnk = (IUnknown*)((INT_PTR)pThis+pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
while (pEntries->pFunc != NULL)
{
BOOL bBlind = (pEntries->piid == NULL);
if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid))
{
if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY)
{
IUnknown* pUnk = (IUnknown*)((INT_PTR)pThis+pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
} else {
HRESULT hRes = pEntries->pFunc(pThis,
iid, ppvObject, pEntries->dw);
if (hRes == S_OK || (!bBlind && FAILED(hRes)))
return hRes;
}
}
pEntries++;
}
return E_NOINTERFACE;
}
参考MSDN,得知:
pThis - 指向包含有COM接口映射表的对象的指针;
pEntries - 一个_ATL_INTMAP_ENTRY结构类型的数组,该数组中包含有效的接口映射;
iid - 请求的接口的GUID;
ppvObj - 用以输出指向iid表示的接口的指针。
参照AtlInternalQueryInterface的实现,如果参数中的iid与IUnknown接口的iid相同(InlineIsEqualUnknown(iid))),将直接输出对象指针加上_ATL_INTMAP_ENTRY结构数组的第一个元素的dw成员的偏移。可见,在请求IUnknown时,函数返回的时pEntries数组中第一个元素表示的接口的IUnknown实现(因为每个COM接口都从IUnknown实现,在多重继承的前提下,函数只返回第一个接口的IUnknown)。
如果请求的接口不是IUnknown,则AtlInternalQueryInterface将继续搜索pEntries数组,直到存在一个元素,并且该元素的pFunc指针是NULL。可见,pEntries数组应该包含pThis所指向的COM对象所实现的所有接口,即所谓的COM对象接口映射表。并且,该数组中最后一个元素不表示任何接口且其pFunc成员应为NULL。
同时还可知,如果接口映射表元素的pFunc成员值为_ATL_SIMPLEMAPENTRY,dw成员则表示所对应的接口指针相对于pThis对象指针的偏移。否则pFunc指向一个自定义的接口指针计算函数。
3. 结论
根据上述分析,与AddRef和Release相似,ATL也没有直接实现IUnknown的QueryInterface方法,而同样是在CComObjectBase类中先作一个内部实现,该实现随着CComObjectRootEx被继承到每个COM对象中。
ATL对于QueryInterface的实现采用的是表驱动的方式(MFC也常用到表驱动方式,似乎是Microsoft钟情于表驱动这个方式,也可能这种方式的确在性能上有过人之处),因此每个ATL COM对象中必须首先存在一个包含其所有实现接口的接口映射表。
我想,下面应该去看看《接口映射表是怎样建成的》了。