ATL學習筆記(3): QueryInterface功能的實現
在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物件中必須首先存在一個包含其所有實現介面的介面對映表。
我想,下面應該去看看《介面對映表是怎樣建成的》了。