1. 程式人生 > >ATL學習筆記(3): QueryInterface功能的實現

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物件中必須首先存在一個包含其所有實現介面的介面對映表。

我想,下面應該去看看《介面對映表是怎樣建成的》了。