ATL接口映射宏詳解

這幾天看了看ATL的接口映射宏,不知不覺看得比較深入了,突然就萌發了把它寫出來的想法。ATL中定義了很多接口映射宏,有幾個還是比較重要的,雖然好象沒有必要把它所有的細節都弄得很清楚,但深入學習的過程中也可以順帶學一學其他的ATL類,對它的機制也可以更清楚一些,應該還是會有些好處的吧。我按照我學習的過程把它寫出來,也 不知道大家能不能看懂。想模仿一下侯老師的手筆力爭把其內部細節解釋清楚,但也不敢大言不慚的美其名曰“深入淺出”,呵呵,只希望能對大家有所幫助了。 

  以後將分別介紹ATL中各個形式爲COM_INTERFACE_ENTRY_XX的接口映射宏並將按照從易到難的順序講解,每一部分都將建立在前一部分的基礎上。每一部分都將通過分析實際的調用函數堆棧來進行分析,堆棧的寫法是從下向上。文中所涉及的代碼都爲略寫,只列出相關部分。 

  一、COM_INTERFACE_ENTRY(x) 

  首先我們從一個最典型的應用開始: 

  定義一個最簡單的ATL DLL: 

class ATL_NO_VTABLE CMyObject : 
public CComObjectRootEx, 
public CComCoClass, 
public IDispatchImpl 

    ..... 
    BEGIN_COM_MAP(CMyObject) 
        COM_INTERFACE_ENTRY(IMyObject) //一個雙接口 
        COM_INTERFACE_ENTRY(IDispatch) 
    END_COM_MAP() 
    ..... 
};

  編寫一段最簡單的查詢接口代碼: 

IUnknown *pUnk; 
IMyObject *pMyObject; 
CoCreateInstance(CLSID_MyObject, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&pUnk); 
pUnk->QueryInterface(IID_IMyObject, (void **)&pMyObject);

  執行客戶代碼,首先我們看看組件對象是如何被創建的。 

  函數調用堆棧一: 

4........... 
  3.ATL::CComCreator< ATL::CComObject< CMyObject > >::CreateInstance(...) 
  2.ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< CMyObject > >, 
          ATL::CComCreator< ATL::CComAggObject< CMyObject > > >::CreateInstance(...) 
  1.ATL::CComClassFactory::CreateInstance(...) 
  4.ATL::AtlModuleGetClassObject(...) 
  9.ATL::AtlInternalQueryInterface(...) 
  8.ATL::CComObjectRootBase::InternalQueryInterface(...) 
  7.ATL::CComClassFactory::_InternalQueryInterface(...) 
  6.ATL::CComObjectCached::QueryInterface(...) 
  5.ATL::CComCreator >:: 
  CreateInstance(...) 
  4.ATL::AtlModuleGetClassObject(...) 
  3.ATL::CComModule::GetClassObject(...) 
  2.DllGetClassObject(...) 
  1.CoCreateInstance(...)(客戶端)

  解釋如下: 

1: 

CoCreateInstance(CLSID_MyObject, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&pUnk); 

其內部將調用OLE API函數CoGetClassObject(), 而CoGetClassObject則會通過 LoadLibrary(...)裝入DLL,並調用DLL中的DllGetClassObject()函數。 



2: 

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{

    return _Module.GetClassObject(rclsid, riid, ppv);
}

  其中值得注意的是_Module變量,在DLL中定義了全局變量: 

  CComModule _Module; 

  ATL通過一組宏: 

BEGIN_OBJECT_MAP(ObjectMap) 
    OBJECT_ENTRY(CLSID_MyObject, CMyObject) 
END_OBJECT_MAP() 

#define BEGIN_OBJECT_MAP(x) static _ATL_OBJMAP_ENTRY x[] = { 
#define OBJECT_ENTRY(clsid, class) {&clsid, class::UpdateRegistry, \
        class::_ClassFactoryCreatorClass::CreateInstance, \//關鍵 
        class::_CreatorClass::CreateInstance, \
        NULL, 0, class::GetObjectDescription, \
        class::GetCategoryMap, class::ObjectMain }, 
  #define END_OBJECT_MAP() {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

  生成一個靜態全局_ATL_OBJMAP_ENTRY型數組:ObjectMap[]; 
  然後ATL又在 

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/ 


    ..... 
    _Module.Init(ObjectMap, hInstance, &LIBID_TEST2Lib); 
    ..... 
}

  中初始化_Module //注意在有的情況下是在InitInstance()中初始化_Module 

  那麼_Module初始化都做了些什麼呢,其實他什麼也沒做,在CComModule::Init中,它調用AtlModuleInit(_ATL_MODULE* pM, _ATL_OBJMAP_ENTRY* p, HINSTANCE h),在其中關鍵的只有一句:pM->m_pObjMap = p;可見_Module僅僅是把這個全局對象映射數組 ObjectMap[]給存了起來。那麼爲什麼可以通過_Module.GetClassObject得到類廠呢?其實關鍵在於我們的組件CMyObject繼承的又一個基類CComCoClass! 在CComCoClass中缺省定義了一個宏DECLARE_CLASSFACTORY()而 

#define DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(CComClassFactory)
#define DECLARE_CLASSFACTORY_EX(cf) 
    typedef CComCreator< ccomobjectcached< cf > > _ClassFactoryCreatorClass;

  CComCreator,CComObjectCached我們暫且不管,但一看到CComClassFactory,顧名思義,我們就知道我們要的類廠終於出現了!每個組件內部原來都有一個類廠對象。繞了一大圈,我們現在已經知道了_Module中包含了我們所要的每個組件的類廠對象,這對目前來說已經足夠了,現在繼續路由下去!

3: 

HRESULT CComModule::GetClassObject(REFCLSID rclsid,REFIID riid,LPVOID* ppv)
{
    return AtlModuleGetClassObject(this, rclsid, riid, ppv); 
}

CComModule::GetClassObject的實現非常簡單,僅僅是調用ATL的API函數。 

4: 

ATLINLINE ATLAPI AtlModuleGetClassObject(_ATL_MODULE* pM, REFCLSID rclsid, REFIID riid, LPVOID* ppv) 

    _ATL_OBJMAP_ENTRY* pEntry = pM->m_pObjMap;//從_Module中取出對象映射數組 

    while (pEntry->pclsid != NULL) 
        { 
        if ((pEntry->pfnGetClassObject != NULL) && InlineIsEqualGUID(rclsid, *pEntry->pclsid)) 
        { 
            if (pEntry->pCF == NULL) 
            { 
                hRes = pEntry->pfnGetClassObject(pEntry->pfnCreateInstance, 
                            IID_IUnknown, (LPVOID*)&pEntry->pCF); 
            } 
            if (pEntry->pCF != NULL) 
                hRes = pEntry->pCF->QueryInterface(riid, ppv); 
            break; 
        } 
        pEntry = _NextObjectMapEntry(pM, pEntry); 
    } 
}

現在好象已經有點看不懂了,看來我們得看看_ATL_OBJMAP_ENTRY的結構了 

struct _ATL_OBJMAP_ENTRY 

    const CLSID* pclsid; 
    HRESULT (WINAPI *pfnUpdateRegistry)(BOOL bRegister); 
    _ATL_CREATORFUNC* pfnGetClassObject; 
    _ATL_CREATORFUNC* pfnCreateInstance; 
    IUnknown* pCF; 
    DWORD dwRegister; 
    _ATL_DESCRIPTIONFUNC* pfnGetObjectDescription; 
    _ATL_CATMAPFUNC* pfnGetCategoryMap; 
}

  pclsid很清楚就代表着我們組件的CLSID;pfnGetClassObject我們也已經知道了它就是CMyObject::_ClassFactoryCreatorClass::CreateInstance(我們組件所包含的類廠對象的CreateInstance函數);pCF我們也可以猜出它是指向這個類廠的IUnknown指針,代表這個類廠對象是否被創建過,若類廠對象已經存在,就不用再創建新的類廠對象了。現在就剩下pfnCreateInstance我們還不明白怎麼回事。其實答案還是在 CComCoClass中! 
  在CComCoClass中缺省定義了宏DECLARE_AGGREGATABLE(x),這個宏表示這個組件既可以是聚集的也可以是非聚集的,關於聚集的概念我們暫且不理,先看它的定義: 

#define DECLARE_AGGREGATABLE(x) public:\ 
    typedef CComCreator2< ccomcreator< CComObject< x > >, \ 
        CComCreator< ccomaggobject< x > > > _CreatorClass; 

我們看到了一個熟悉的字符串_CreatorClass, 原來這還有一個組件包含的對象。但還有一個問題我們沒有搞清楚,就是爲什麼_ClassFactoryCreator和_CreatorClass後面都要跟着一個CreateInstance? 看來我們必須先來看看CComCreator是個什麼東西了。 

template < class T1 >
class CComCreator 

public: 
    static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv) 
    {..... 
    } 
};

  原來它裏面只有一個CreateInstance函數,我們現在終於大體明白_ClassFactoryCreatorClass::CreateInstance 表示什麼意思了,它就代表CComClassFactory::CreateInstance(..)吧,差不多就是這樣了。那我們再來看看CComCreator2有什麼不同: 

template < class T1, class T2 >
class CComCreator2 

public: 
    static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv) 
    { 
        return (pv == NULL) ? 
            T1::CreateInstance(NULL, riid, ppv) : 
            T2::CreateInstance(pv, riid, ppv); 
    } 
};

  這個類與CComCreator很類似,都只有一個CreateInstance成員函數,從_CreatorClass 中我們可以知道它實際上包含兩個類CComObject,CComAggObject的CreateInstance函數(通過CComCreator),其中CComObject用於非聚集對象,CComAggObject用於聚集對象根據情況它建立相應的對象。(ATL中實際生成的組件對象不是CMyObject,而是 CComObject,CComAggObject或CComPolyObject對象,這個概念很重要,但現在暫且不談) 現在我們對AtlModuleGetClassObject(...)基本已經知道是怎麼回事了,它就是根據存在對象映射數組中的創建類廠的函數的地址來創建類廠。pfnGetClassObject以及 pfnCreateInstance我們基本上都已經知道是怎麼回事了,但還有一個問題爲什麼要把pEntry->pfnCreateInstance作爲pEntry->pfnGetClassObject(...)中的一個參數傳遞?答案在下面呢,讓我們繼續路由下去! 

5: 

CComCreator::CreateInstance(void* pv, REFIID riid, LPVOID* ppv) 

    T1* p = NULL; 
    ATLTRY(p = new T1(pv))//創建類廠對象 
    if (p != NULL) 
    { 
        p->SetVoid(pv); 
        p->InternalFinalConstructAddRef(); 
        hRes = p->FinalConstruct(); 
        p->InternalFinalConstructRelease(); 
        if (hRes == S_OK) 
        hRes = p->QueryInterface(riid, ppv); 
        if (hRes != S_OK) 
            delete p; 
    } 
}

  注意這裏的T1是CComObjectCached< ATL::CComClassFactory >,這是我們給CComCreator 的模板參數。我們又一次看到了我們熟悉的操作符'new'!直到現在我們終於創建了組件的類廠。但還沒完,繼續往下走,看看SetVoid(pv)裏幹了些什麼? 

void CComClassFactory::SetVoid(void* pv) 

    m_pfnCreateInstance = (_ATL_CREATORFUNC*)pv; 
}

  大家還記得我們曾經把CMyObject::_CreatorClass::CreateInstance作爲參數傳給 pEntry->pfnGetClassObject(...)吧,當時我們不明白是怎麼回事,現在已經豁然開朗!原來是類廠需要它來創建組件對象!雖然我們只是從字面意思猜出這一點,但實際上也正如我們所預料的那樣,在  CComClassFactory::CreateInstance(...)中,我們看到了m_pfnCreateInstance(pUnkOuter, riid, ppvObj);現在一切都已經明白了, ATL爲我們創建類廠而作的層層包裝我們都已經打開,剩下的創建組件的過程已經是我們很熟悉的過程了! 
  但是現在還沒有完,我們還需要爲類廠對象查詢一個IUnknown指針,這個指針就存在我們在前面所看到的pEntry->pCF中。 

  6: 

STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
{return _InternalQueryInterface(iid, ppvObject);}

  現在調用的是CComObjectCached::QueryInterface,至於這個類有何特別之處,我們現在好象還不需要知道,我也很累的說,呵呵。 

  7: 

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

  所有的類的_InternalQueryInterface(...)都是在BEGIN_COM_MAP中定義的。 CComObjectCached沒有BEGIN_COM_MAP宏,所以現在調用的是CComClassFactory的。注意把this指針和接口映射數組_GetEntries()傳給了InternalQueryInterface(), 這是InternalQueryInterface(...)實現查詢的依據。在BEGIN_COM_MAP(x)中定義了一個靜態的接口映射數組: 
_ATL_INTMAP_ENTRY _entries[]; 

  每一個接口映射宏實際上都是向這個數組中增加了一項。一個接口映射宏包括三個部分:接口的IID號、偏移值(大部分時候下)、需要執行的函數,對一般接口來說不用執行其他函數。_GetEntries()就是返回這個數組。還有一些細節問題以後再說。 

8: 

static HRESULT WINAPI InternalQueryInterface(void* pThis,
    const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)

    ... 
    HRESULT hRes = AtlInternalQueryInterface(pThis, pEntries, iid, ppvObject);
... 
}

  現在調用的是CComObjectRootBase::InternalQueryInterface(...) 

  9:現在我們終於到了QueryInterface的鼻祖了。

  AtlInternalQueryInterface(...)是整個查詢過程的終點,它遍歷接口映射表,並根據每一項做出相應的動作。ATL中的消息映射宏有很多種,相應的動作也很多,但現在我們不管那些,現在我們要做的就是查到一個IUnknown接口,這很容易,我們甚至不需要遍歷接口映射表。 

ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis, 
    const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject) 

    ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY); 
    if (ppvObject == NULL) 
        return E_POINTER; 

    *ppvObject = NULL; 
    if (InlineIsEqualUnknown(iid)) // use first interface 
    { 
        IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw); 
        pUnk->AddRef(); 
        *ppvObject = pUnk; 
        return S_OK; 
    } 
    ...//還有一大堆呢,但現在用不上,就節省點空間吧 
}

  這裏有一個規定,接口映射表的第一個接口必須是_ATL_SIMPLEENTRY型的。至於爲什麼有這個要求,以及pThis+pEntries->dw是什麼意思,我們以後再說吧,那也是一堆問題。總之,我們現在如願以償輕鬆的獲得了我們所需要的類廠對象以及IUnknown指針。 
  4:我差一點以爲我們可以勝得返回到第一步了,但在ATL::AtlModuleGetClassObject 處卻又停了下來,看看它的源碼,原來還要再通過我們剛獲得的IUnknown指針查詢 IClassFactory指針。又是一通相同的調用,從第6步到第9步一模一樣,我們將進行相同的調用。但注意在第9步中,我們這回查的不再是IUnknown指針了,所以我們需要看看我剛纔還沒列出的代碼,但這留到下一次函數堆棧再看吧 

  1:終於終於我們已經完成了創建類廠對象的全部操作,現在我們要做的就是我們熟悉的調用類廠對象的CreateInstance(...)函數創建組件的過程了。正如我們所見到的,現在OLE開始調用CComClassFactory::CreateInstance()了,我們還沒忘記,在類廠對象中保留了創建組件用的CreateInstance()函數, 這個過程已經很明朗了。 

  2.不用再重複了吧,看第4步。 

  3.不用再重複了吧,看第4步。 

  4.如果繼續路由下去的話,我們的堆棧還可以很長,但這只是重複的枯躁的勞動。我就不繼續走下去了,我也很累的說,唉。 

  函數調用堆棧二: 

0:............ 
  5.ATL::AtlInternalQueryInterface(...) 
  4.ATL::CComObjectRootBase::InternalQueryInterface(...) 
  3.CMyObject::_InternalQueryInterface(...) 
  2.ATL::CComObject< CMyObject >::QueryInterface(...) 
  1.pUnk->QueryInterface(IID_IMyObject, (void **)&pMyObject);(客戶端)

  解釋如下: 

  1.我們通過剛剛獲得的組件對象的IUnknown接口指針來查詢IMyObject指針,這纔是我們真正需要的指針。 

  2.還記得我們說過ATL真正創建的組件並不是CMyObject,而是CComObject,CComAggObject 或CComPolyObject,這裏我們創建的是CComObject.所以理所當然我們要調用 CComObject::QueryInterface(...),而確實CComObject也實現了這個函數。 

 
  STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) 
{return _InternalQueryInterface(iid, ppvObject);} 
它只是簡單地調用_InternalQueryInterface(...),我們也說過,只有類裏面申明瞭BEGIN_COM_MAP宏纔會有_InternalQueryInterface(...),所以現在執行轉到了它的父類CMyObject中去,所以將調用CMyObject::_InterfaceQueryInterface(...) 

  3.以後的調用我們已經很熟悉了,還用我再說一遍嗎,呵呵 

  4.這個調用我們也很熟悉了,不用多說了吧 

  5.現在我們將要查詢的是一個非IUnknown接口,所以我們來看看我們以前沒列出的代碼

ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis, 
    const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject) 

    //確保接口映射的第一項是個簡單接口 
    //若是查詢IUnknown接口,執行相應的操作 
    //以下將遍歷接口映射表,試圖找到相應的接口 
    while (pEntries->pFunc != NULL) 
    { 
        BOOL bBlind = (pEntries->piid == NULL); 
        if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid)) 
        {
            //_ATL_SIMPLEMAPENTRY就表明是個簡單接口 
            if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset 
            { 
                ATLASSERT(!bBlind); 
                IUnknown* pUnk = (IUnknown*)((int)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; 
    } 
}

  函數的邏輯很清楚,只有兩點可能不太理解,一個是 (IUnknown*)((int)pThis+pEntries->dw)是什麼意思,另一個是pEntries->pFunc到底 要幹些什麼事。前一個問題將在講述COM_INTERFACE_ENTRY2中講述,後一個問題將在以後講述不同類型的接口時分別解釋。飯總是要一口一口吃的嘛,呵呵。   現在我們只需關心一下我們的IMyObject是怎麼被查找的。看一下它的宏我們把COM_INTERFACE_ENTRY(IMyObject)解開以後形式爲: 

{&_ATL_IIDOF(IMyObject), //得到IMyObject的IID值 
offsetofclass(IMyObject, CMyObject), //定義偏移量 
_ATL_SIMPLEMAPENTRY},//表明是個簡單接口

  同樣對於offsetofclass(IMyObject, CMyObject)我們也將留到下一次再講。根據這個結構,我們很容易就能獲得IMyObject接口指針。 
  0:OK,it is over.依次退棧返回。 
  其實這次查詢發生的過程在剛纔的調用序列中也發生了,當查詢IClassFactory接口時就有類似的過程,但還是把它單獨提了出來,只爲了看看典型的情形,呵呵。 



二、COM_INTERFACE_ENTRY2(x, x2) 

  ATL中是以多重繼承的方式來實現組件的,但在繼承樹中如果有多個分支實現了同一個接口,當查詢這個接口時就需要知道把哪個分支返回給它。這個宏就是幹這個工作的通常這個宏是用於IDispatch接口。我們先來看看它的典型用法: 

class COuter : 
    public IDispatchImpl< IOuter1, &IID_IOuter1, &LIBID_COMMAPLib>,//IOuter1是一個雙接口
    public IDispatchImpl< IOuter2, &IID_IOuter2, &LIBID_COMMAPLib>,//IOuter2也是一個雙接口
    public ... 

public: 
    COuter(){} 
    ... 
    BEGIN_COM_MAP(COuter) 
        COM_INTERFACE_ENTRY2(IDispatch, IOuter2) ,//將暴露IOuter2所繼承的路線 , 
        COM_INTERFACE_ENTRY(IOuter1) 
        COM_INTERFACE_ENTRY(IOuter2) 
        ... 
    END_COM_MAP
};

  IDispatchImpl<...>這個類中實現了IDispatch接口,所以現在組件中有兩個IDispatch 的實現。那查詢IDispatch接口時,返回哪個實現呢? 
  我們再來看看COM_INTERFACE_ENTRY2(x, x2)的定義 

#define BEGIN_COM_MAP(x) public: \ 
    typedef x _ComMapClass; \ 
    .................... 
#define COM_INTERFACE_ENTRY2(x, x2)\ 
    {&_ATL_IIDOF(x),\ //得到接口的IID值 
    (DWORD)((x*)(x2*)((_ComMapClass*)8))-8,\ 
    _ATL_SIMPLEMAPENTRY}, //表明是一個簡單接口

  現在問題就在於(DWORD)((x*)(x2*)((_ComMapClass*)8))-8是個什麼意思? 
我們先來考察一下下面一段代碼: 

class A1 

public: 
    virtual void Test(){} 
}; 


class A2 : public A1 

public: 
    virtual void Test(){} 
}; 

class A3 : public A1 

public: 
virtual void Test(){} 
}; 

class A : public A2, public A3 

}; 


    DWORD dw; dw = (DWORD)((A *)8); //dw = 0x08 
    dw = (DWORD)((A3 *)(A *)8); //dw = 0x0c 
    dw = (DWORD)((A1 *)(A3 *)(A *)8); //dw = 0x0c 
    dw = (DWORD)((A1 *)(A3 *)(A *)8) - 8;//dw = 4 
}

  這個繼承圖是個典型的菱形結構,在類A中保存有兩個虛函數表指針,分別代表着它的兩個分支。當爲類A申明一個對象並實例化時,系統會爲其分配內存。在這塊內存的最頂端保留着它的兩個虛函數表指針。分析程序運行的結果,可以看出,最後的結果4代表了指向接口A3的虛函數表指針與類A對象的內存塊頂端之間的偏移量。 
  下面我們再看一個更爲複雜點的繼承關係: 

class B1 

public: 
virtual void Test(){} 
}; 

class B2 

public: 
virtual void Test(){} 
}; 

class B3 

public: 
public: 
virtual void Test(){} 
}; 

class B4 : public B1, public B2 

public: 
virtual void Test(){} 
}; 

class B5 : public B2, public B3 

public: 
virtual void Test(){} 
}; 

class B : public B4, public B5 

}; 


    DWORD dw; dw = (DWORD)((B *)8);      //dw = 0x08 
    dw = (DWORD)((B5 *)(B *)8);       //dw = 0x10 
    dw = (DWORD)((B2 *)(B5 *)(B *)8);    //dw = 0x10 
    dw = (DWORD)((B2 *)(B5 *)(B *)8) - 8;  //dw = 8 
}

  類B將保留四個虛函數表指針,因爲它共有四個分支。我們的目的是想獲得B::B5::B2這個分支中的B2接口,最後的結果8正是我們所需要的,它表示在類B內存塊的偏移量。
  從上面兩個例子中,我們已經明白了(DWORD)((x*)(x2*)((_ComMapClass*)8))-8的作用通過這個值我們能獲得我們所需要的接口。 
  下面我們針對我們的實際情況COM_INTERFACE_ENTRY2(IDispatch, IOuter2)來分析一下
  IDispatchImpl< class T,... >模板類從類T中派生,所以COuter要從兩個它的模板類中繼承, IOuter1、IOuter2都是雙接口,即都是從IDispatch派生的類,所以可得COuter有兩條分支,也是個菱形結構,所以按照我們的示例,這個偏移值也應該是4。爲了證明我們的設想,我們再來通過函數堆棧來驗證我們的結果。 

  函數堆棧: 

5.ATL::AtlInternalQueryInterface(...) 
  4.ATL::CComObjectRootBase::InternalQueryInterface(...) 
  3.CMyObject::_InternalQueryInterface(...) 
  2.ATL::CComObject< CMyObject >::QueryInterface(...) 
  1.pUnk->QueryInterface(IID_IDispatch, (void **)&pDispatch)

  解釋: 

  1:這是我們的驗證代碼,pUnk是組件的IUnknown指針 

  2--5:這些代碼我們現在都已經很熟悉了,我們只需再看看AtlInternalQueryInterface 的具體實現。 

ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis, 
    const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject) 

    ........... 
    while (pEntries->pFunc != NULL) 
    {
        BOOL bBlind = (pEntries->piid == NULL); 
        if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid)) 
        { 
            if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset 
            {
                ATLASSERT(!bBlind); 
                IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw); 
                pUnk->AddRef(); 
                *ppvObject = pUnk; 
                return S_OK; 
            } 
            .....//如果是非簡單接口的話... 
        } 
        pEntries++; 
    } 
    return E_NOINTERFACE; 
}

  關鍵的一句話就是IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw); 通過觀察變量,正如我們所料pEntries->dw=4。(int)pThis+pEntries->dw)保證了我們可以得到IOuter2分支的虛函數表,又因爲IDispatch也是從IUnknown繼承,在虛函數表的最頂端放的是IUnknown的虛函數指針,所以進行(IUnknown *)強制轉換,可以獲得這個虛函數表的頂端地址,這正是我們所需要的。或許會問爲什麼得到的是虛函數表的地址,而不是一個類實例的地址呢?別忘了,接口是沒有數據的,它只有純虛函數。對於客戶來說,它只能通過接口定義的虛函數來訪問它,而不可能訪問實現接口的類的成員變量,組件的數據對客戶來說是不可見的,所以只用得到虛函數表的地址就行了。



 三、COM_INTERFACE_ENTRY_TEAR_OFF(iid, x) 

  使用這個宏的目的就是爲了把一些很少用到的接口放在一個單獨的組件中實現,僅當查詢到這個接口時,才創建這個組件,並且當它的引用計數減爲0時就會被釋放掉。我們知道ATL中組件是通過多重繼承實現的,每繼承一個接口,在爲它分配的內存塊中就會多一個虛函數表指針,用這個宏就可以爲每個組件的實例節省下這一個虛函數表指針來(一個指針4個字節,好象也不多啊,呵呵)
  下面我們來看它的典型用法: 

class CTearOff1: //該類是專門用來實現分割接口ITearOff1的 
    public IDispatchImpl< ITearOff1, &IID_ITearOff1, &LIBID_COMMAPLib >,
    public CComTearOffObjectBase //外部對象 

public: 
    CTearOff1(){} 
    ~CTearOff1(){} 

    BEGIN_COM_MAP(CTearOff1) 
        COM_INTERFACE_ENTRY(ITearOff1) 
    END_COM_MAP() 

    HRESULT STDMETHODCALLTYPE get_Name(BSTR* pbstrName) 
    { 
        *pbstrName = ::SysAllocString(L"ITearOff1"); 
        return S_OK; 
    } 
}; 

class COuter : public ..... //我們真正要實現的組件 

public: 
    ........... 
    BEGIN_COM_MAP(COuter) 
        ........... 
        COM_INTERFACE_ENTRY_TEAR_OFF(IID_ITearOff1, CTearOff1) 
    END_COM_MAP() 
    ........... 
};

CTearOff1實現了Tear-off接口ITearOff1,實現方法與其他組件並無不同。唯一不同的是它從CComTearOffObjectBase繼承,CComTearOffObjectBase定義如下: 

template < class Owner, class ThreadModel = CComObjectThreadModel >
class CComTearOffObjectBase : public CComObjectRootEx 

public: 
    typedef Owner _OwnerClass; 
    CComObject* m_pOwner; 
    CComTearOffObjectBase() {m_pOwner = NULL;} 
};

  我們又看到了我們熟悉的一個類CComObject,它是組件的真正生成類。從上面的定義中可知道CComTearOffObjectBase主要功能就是包含了一個指向外部對象(在這裏就是我們的組件類CComObject)的指針。它的功能將在後面看到。
我們繼續用我們的老辦法來跟蹤一下看看它的執行過程。假設pOuter是我們已經獲得的組件的IOuter接口指針。
  執行pOuter->QueryInterface(IID_ITearOff1, (void **)&pTear1); 
函數堆棧一: 

7.CTearOff1::_InternalQueryInterface(...) 
  6.ATL::CComInternalCreator< ATL::CComTearOffObject< CTearOff1 > >::CreateInstance(...) 
  5.ATL::CComObjectRootBase::_Creator(...) 
  4.ATL::AtlInternalQueryInterface(...) 
  3.ATL::CComObjectRootBase::InternalQueryInterface(...) 
  2.COuter::_InternalQueryInterface(...) 
  1.ATL::CComObject< COuter >::QueryInterface(...)

  解釋: 

  1--4:這些代碼已經遇到過很多次了,我們還是集中精力看看核心代碼: 

ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis, 
    const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject) 

    //.......... 
    while (pEntries->pFunc != NULL) 
    { 
        BOOL bBlind = (pEntries->piid == NULL); 
        if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid)) 
        { 
            if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset 
            {
                //若是簡單接口,.... 
            } 
            else //actual function call 
            {
                HRESULT hRes = pEntries->pFunc(pThis, iid, ppvObject, pEntries->dw); 
                if (hRes == S_OK || (!bBlind && FAILED(hRes))) 
                    return hRes; 
            } 
        } 
        pEntries++; 
    } 
    return E_NOINTERFACE; 
}

  當在COuter的接口映射數組中找到ITearOff1後,因爲它不是一個簡單接口,所以要執行pEntries->pFunc(....)。
我們先來看看COM_INTERFACE_ENTRY_TEAR_OFF的定義: 

#define COM_INTERFACE_ENTRY_TEAR_OFF(iid, x)\ 
    {&iid,\ 
    (DWORD)&_CComCreatorData<\ 
        CComInternalCreator< CComTearOffObject< x > >\ 
        >::data,\ 
    _Creator},

看不太明白,還是繼續我們路由得了 
5:原來_Creator是CComObjectRootBase的靜態成員函數,它可是COuter的一個基類啊,所以纔可以這樣寫而不會編譯出錯。看看它的實現吧: 

static HRESULT WINAPI _Creator(void* pv, REFIID iid, void** ppvObject,DWORD) 

    _ATL_CREATORDATA* pcd = (_ATL_CREATORDATA*)dw; 
    return pcd->pFunc(pv, iid, ppvObject); 


struct _ATL_CREATORDATA 

    _ATL_CREATORFUNC* pFunc; 
}; 

typedef HRESULT (WINAPI _ATL_CREATORFUNC)(void* pv, REFIID riid, LPVOID* ppv); 

template < class Creator > 
_ATL_CREATORDATA _CComCreatorData::data = {Creator::CreateInstance};

  源代碼都列出來了,不用我多說,大家也都能看懂了。繼續路由吧 

  6:繞了一大圈,現在我們調用的應該是 

CComInternalCreator<...>::CreateInstance 

template < class T1 >
class CComInternalCreator 

public: 
    static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv) 
{
ATLASSERT(*ppv == NULL); 
HRESULT hRes = E_OUTOFMEMORY; 
T1* p = NULL; 
ATLTRY(p = new T1(pv)) 
if (p != NULL) 
        { 
            p->SetVoid(pv); 
            p->InternalFinalConstructAddRef(); 
            hRes = p->FinalConstruct(); 
            p->InternalFinalConstructRelease(); 
            if (hRes == S_OK) hRes = p->_InternalQueryInterface(riid, ppv); 
            if (hRes != S_OK) delete p; 
        } 
        return hRes; 
    } 
};

  同我們所見到的大多數Creator類一樣,它也只有一個靜態CreateInstance函數。現在我們終於可以創建我們分割組件了,它不是CTearOff1,它也是經了一層包裝的,是 CComTearOffObject! 現在我們再來看看它的構造函數幹了些什麼事: 

CComTearOffObject(void* pv) 

    ATLASSERT(m_pOwner == NULL); 
    m_pOwner = reinterpret_cast< CComObject< Base::_OwnerClass >* >(pv); 
    m_pOwner->AddRef(); 
}

  還記得CTearOff1是從CComTearOffObjectBase繼承的嗎,這個基類包含了一個成員變量m_pOwner,現在它被賦值爲指向它的外部對象的指針了。 
  7.現在終於把這個實現分割接口的組件創建了,剩下的在CTearOff1中查詢ITearOff1的工作已經是重複勞動了,不再贅述。 

  執行pTear1->QueryInterface(ITearOff1, (void **)&pTear2)
一個實現分割接口的組件有可能包含多個分割接口,我們來檢測一下它的查詢過程。 

  函數堆棧二: 

4.............. 
  3.COuter::_InternalQueryInterface(...) 
  2.ATL::CComObject< COuter >::QueryInterface(...) 
  1.ATL::CComTearOffObject< CTearOff1 >::QueryInterface(...)

  解釋: 

  1: 

STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) 

    return m_pOwner->QueryInterface(iid, ppvObject); 
}

  還記得我們創建的分割組件是CComTearOffObject< CTearOff1 >嗎?現在執行查詢操作的是它的成員函數。它的實現很簡單,事實上它什麼也沒做,僅僅是把它交給它的外部對象(即CComObject< COuter >)去做了。還記得m_pOwner是在構造函數裏賦值的吧。現在是否感到有些不妙呢?呵呵 
  2、3:果然,現在已經不用再看下去了,剩下的將是重複我們在調用第一條查詢操作所做的一切。這個過程很簡單,但它也隱含說明了一點:若對一個實現分割接口的組件每查詢一次它的接口,就會調用一個新的實例!!!在上例中,最後的結果pTear2和pTear1 是不一樣的!!這顯然是浪費!
在下一節中,我們將介紹一個可以解決這個問題的宏! 


四.COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(iid, x, punk) 

  這個宏與上一節所講的COM_INTERFACE_ENTRY_TEAR_OFF宏最主要的不同就在於,當查詢分割對象中其他接口時,不會再新建新的對象。下面還是先看看它的典型用法:

class CTearOff2: 
    public IDispatchImpl< ITearOff2, &IID_ITearOff2, &LIBID_COMMAPLib >, 
    public CComTearOffObjectBase< COuter > 

public: 
    CTearOff2(){} 
    ~CTearOff2(){} 

    BEGIN_COM_MAP(CTearOff2) 
        COM_INTERFACE_ENTRY(ITearOff2) 
    END_COM_MAP() 

    HRESULT STDMETHODCALLTYPE get_Name(BSTR* pbstrName) 
    { 
        *pbstrName = ::SysAllocString(L"ITearOff2"); 
        return S_OK; 
    } 
}; 

class COuter : public .... 

public: 
    BEGIN_COM_MAP(COuter) 
        COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(IID_ITearOff2, CTearOff2, m_pUnkTearOff2.p) 
...... 
    END_COM_MAP() 

    CComPtr< IUnknown > m_pUnkTearOff2; 
    ..... 
};

  CTearOff2實現了分割接口ITearOff2,它的類定義與上一節所看見的CTearOff1一模一樣可見不管是哪種分割接口,實現都是一樣的,不同的地方在於COuter。在COuter中增加了一個成員變量m_pUnkTearOff2作爲宏的一個參數。
  我們繼續用老辦法跟蹤它的內部執行過程,假設pOuter是已經獲得的組件COuter有接口IOuter指針。 

  執行pOuter->QueryInterface(IID_ITearOff2, (void **)&pTear1); 

  函數堆棧一: 

9.CTearOff2::_InternalQueryInterface(...) 
  8.ATL::CComCachedTearOffObject< CTearOff2 >::QueryInterface(...)(第二次調用) 
  9.ATL::CComCachedTearOffObject< CTearOff2 >::QueryInterface(...) 
  8.ATL::CComCreator< ATL::CComCachedTearOffObject< CTearOff2 > >::CreateInstance() 
  7.ATL::CComObjectRootBase::_Cache(...) 
  6.COuter::_Cache(...) 
  5.ATL::AtlInternalQueryInterface(...) 
  4.ATL::CComObjectRootBase::InternalQueryInterface(...) 
  3,COuter::_InternalQueryInterface(...) 
  2.ATL::CComObject< COuter >::QueryInterface(...) 
  1.CTestDlg::OnButton1() line 187 + 22 bytes

  解釋: 

  1:pOuter->QueryInterface(IID_ITearOff2, (void **)&pTear1); 

  2-5:這段代碼見到很多次了,不用再講了,現在程序執行到 

  HRESULT hRes = pEntries->pFunc(pThis, iid, ppvObject, pEntries->dw); 看來我們得看看這個宏的定義才能知道pFunc是執行的什麼功能了。 

 
#define COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(iid, x, punk)\ 
    {&iid,\ 
    (DWORD)&_CComCacheData<\ 
        CComCreator< CComCachedTearOffObject< x > >,\ 
        (DWORD)offsetof(_ComMapClass, punk)\ 
       ?gt;::data,\ 
    _Cache}, 

  與我們上一節見的宏的定義不太一樣,還是先跟蹤下去再說。 

  6:原來在BEGIN_COM_MAP中也定義了_Cache函數: 

 
static HRESULT WINAPI _Cache(void* pv,REFIID iid,void** ppvObject,DWORD dw )\ 
{\ 
    ...... 
    HRESULT hRes = CComObjectRootBase::_Cache(pv, iid, ppvObject, dw);\ 
    ...... 
}\ 

  7:看看CComObjectRootBase::_Cache的源碼: 

static HRESULT WINAPI _Cache(void* pv,REFIID iid,void** ppvObject,DWORD dw) 
{
    HRESULT hRes = E_NOINTERFACE; 
    _ATL_CACHEDATA* pcd = (_ATL_CACHEDATA*)dw; 
    IUnknown** pp = (IUnknown**)((DWORD)pv + pcd->dwOffsetVar); 
    if (*pp == NULL) hRes = pcd->pFunc(pv, IID_IUnknown, (void**)pp); 
    if (*pp != NULL) hRes = (*pp)->QueryInterface(iid, ppvObject); 
    return hRes; 
}

  現在問題的關鍵是dw了,dw是從pEntries->dw傳過來的。我們得看一下宏定義中的 

(DWORD)&_CComCacheData<\ 
        CComCreator< CComCachedTearOffObject< x> >,\ 
        (DWORD)offsetof(_ComMapClass, punk)\ 
        >::data,\

是什麼意思。 

 
template < class Creator, DWORD dwVar >
_ATL_CACHEDATA _CComCacheData< Creator, dwVar >::data = {dwVar, Creator::CreateInstance}; 

  CComCreator我們在前面已經見過它的定義了,它只有一個成員函數 

CreateInstance. 
template < class contained >
class CComCachedTearOffObject : 
    public IUnknown, 
    public CComObjectRootEx< contained::_ThreadModel::ThreadModelNoCS >

public: 
    typedef contained _BaseClass; 
    CComCachedTearOffObject(void* pv) 
: m_contained(((contained::_OwnerClass*)pv)->GetControllingUnknown()) 
    {
        m_contained.m_pOwner = reinterpret_cast< CComObject< contained::_OwnerClass >* >(pv); 
    } 

    CComContainedObject< contained > m_contained; 
};

  CComCachedTearOffObject是這個宏與上一節所講宏不同的關鍵所在,因爲它包含了一個CComContainedObject的對象。這個對象的作用在查詢的時候再講。
  我們再來看看offsetof的定義: 
#define offsetof(s,m) (size_t)&(((s *)0)->m) 
對(DWORD)offsetof(_ComMapClass, punk)來說,就是punk在_ComMapClass類中的偏移值。
  現在來看看_ATL_CACHEDDATA是什麼東西。 

struct _ATL_CACHEDATA 

    DWORD dwOffsetVar;
    _ATL_CREATORFUNC* pFunc; 
}; 

typedef HRESULT (WINAPI _ATL_CREATORFUNC)(void* pv, REFIID riid, LPVOID* ppv);

  要注意的是從_Cached()函數傳進來的參數dw就是_ATL_CACHEDATA結構的變量,所以可知道(DWORD)pv + pcd->dwOffsetVar)得到的就是在類COuter中定義的m_pUnkTearOff2的偏移值,所以IUnknown** pp就是指向m_pUnkTearOff2的一個指向指針的指針。 
  而pdc->pFunc()則會調用CComCreator< CComCachedTearOffObject < x > >::CreateInstance 

  8:下面將調用CComCreator::CreateInstance,將創建一個CComCachedTearOffObject<>的對象實例。其構造函數定義如下: 

CComCachedTearOffObject(void* pv) 
: m_contained(((contained::_OwnerClass*)pv)->GetControllingUnknown()) 
{
    ATLASSERT(m_contained.m_pOwner == NULL); 
    m_contained.m_pOwner = reinterpret_cast< CComObject< contained::_OwnerClass >* >(pv); 
}

  這裏contained就是CTearOff2,contained::_OwnerClass就是COuter,可見m_contained保存了外部對象的指針。 

  9:創建完對象後,將查詢接口ITearOff2 

STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) 
{
    //如果是IUnknown,...,返回IUnknwon接口指針 
    else 
        hRes = m_contained._InternalQueryInterface(iid, ppvObject); 
    ..... 
}

  注意,這裏把查詢工作交給了m_contained,也就是一個CComContainedObject對象。不過現在查詢的是IUnknown指針,別忘了,我們在COuter中還定義了一個IUnknown指針呢,現在查詢的就是它!! 

  8:經過一系列退棧,退到_Cache()中,現在還要繼續查詢ITearOff2接口。是根據我們剛剛查詢到的IUnknown指針查詢ITearOff2。所以再一次進入 ATL::CComCachedTearOffObject< CTearOff2 >::QueryInterface(...),不過這回將調用的是m_contained._InternalQueryInterface(...)了。 

  9:因爲CComContainedObject m_contained的基類是CTearOff2,所以將調用CTearOff2::_InternalQueryInterface(...) 

  剩下的操作就沒什麼特別之處了,僅僅一般的查詢操作。 

  執行pTear1->QueryInterface(ITearOff2, (void **)&pTear2); 

  函數堆棧二: 

12.ATL::AtlInternalQueryInterface(...) 
  11.ATL::CComObjectRootBase::InternalQueryInterface(...) 
  10.CTearOff2::_InternalQueryInterface(...) 
  9.ATL::CComCachedTearOffObject< CTearOff2 >::QueryInterface(...) 
  8.ATL::CComObjectRootBase::_Cache(...) 
  7.COuter::_Cache(...) 
  6.ATL::AtlInternalQueryInterface(...) 
  5.ATL::CComObjectRootBase::InternalQueryInterface(...) 
  4.COuter::_InternalQueryInterface(...) 
  3.ATL::CComObject< COuter >::QueryInterface(...) 
  2.ATL::CComObjectRootBase::OuterQueryInterface(...) 
  1.ATL::CComContainedObject< CTearOff2 >::QueryInterface(...)

  解釋: 

  1:第一步就可能使我們迷惑了,爲什麼執行的是CComContainedObject::QueryInterface

  在上一節中,執行的是ATL::CComTearOffObject< CTearOff1 >::QueryInterface(...),所以我們也自然而然的猜想,這裏應該執行的是CComCachedTearOffObject的函數。但是來看看CComCachedTearOffObject的定義: 

template < class contained >
class CComCachedTearOffObject : 
    public IUnknown, 
    public CComObjectRootEx< contained::_ThreadModel::ThreadModelNoCS >
{ ... };

  原來CComCachedTearOffObject沒有從contained類(在這裏就是CTearOff2)中繼承,而 CComTearOffObject卻是從CTearOff1繼承的!所以我們剛纔得到的pTear1就不可能是CComCachedTearOffObject的對象。而實際上,CComContainedObject是從CTearOff2繼承的,在上面的函數堆棧中第9步查詢ITearOff2接口時,把工作交給了m_contained, 這是個CComContainedObject< CTearOff2 >對象,所以實際上最後查詢得到的ITearOff2 指向的是CComContainedObject< CTearOff2 >對象。所以現在執行的會是 CComContainedObject::QueryInterface(...)!!! 

STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) 
{
    HRESULT hr = OuterQueryInterface(iid, ppvObject); 
    if (FAILED(hr) && _GetRawUnknown() != m_pOuterUnknown) 
        hr = _InternalQueryInterface(iid, ppvObject); 
    return hr; 

//?m_pOuterUnknown

2: 

HRESULT OuterQueryInterface(REFIID iid, void ** ppvObject) 

    return m_pOuterUnknown->QueryInterface(iid, ppvObject); 
}

  把查詢工作交給外部對象完成,也就是COuter。 

  第一、二步的功能與上一節中所講的一樣,都是交給外部對象去處理,不同之處在下面3-8:COuter中的查詢過程與上例中無異,我們可以直接跳到第8步中 

static HRESULT WINAPI _Cache(void* pv, REFIID iid, void** ppvObject,DWORD dw) 

    .... 
    if (*pp == NULL) hRes = pcd->pFunc(pv, IID_IUnknown, (void**)pp); 
    if (*pp != NULL) hRes = (*pp)->QueryInterface(iid, ppvObject); 
    return hRes; 
}

  還記得我們在COuter中定義了一個IUnknown指針m_pUnkTearOff2吧,我們在第一次查詢 ITearOff2接口時,創建了CTearOff2對象,並查詢一個IUnknown指針給了它.現在它就發揮作用了,在_Cache中將判斷如果m_pUnkTearOff2不等於空,則表明CTearOff2已經創建就不會再創建它了,而是直接用它去查詢接口. 
  9:所以現在將調用CComCachedTearOffObject< CTearOff2>::QueryInterface(...),在上一個函數堆棧中的第9步中我們已經看到了這個QueryInterface(...)的代碼,它把查詢工作交給m_contained._InternalQueryInterface(.),其實因爲CComContainedObject中沒有定義BEGIN_COM_MAP宏,所以也沒有定義_InternalQueryInterface(),所以實際上調用的是它包含的類的函數,即CTearOff2::_InternalQueryInterface(...) 

  10-12:以下的工作就很簡單了,不再贅述。 

  總結: 

  COM_INTERFACE_ENTRY_CACHED_TEAR_OFF是個相對比較麻煩的宏,它與上一節介紹的宏相比不同之處就在於創建分割接口對象的過程只用進行一次,如果對象已經創建,則下一次查詢該對象的接口時不會再創建一個新的分割對象。爲了達到這個目的,它在外部對象中包含了一個IUnknown指針,並在第一次創建分割對象時查詢這個IUnknown指針,這樣就可以通過判斷這個指針是否爲空來知道這個分割對象是否已經創建,從而決定是否創建新的分割對象,並通過它去查詢分割對象內其它接口。這裏特別需要注意的是,實際上有兩個對象被創建,一個是CComCachedTearOffObject< CTearOff2 >,另一個是 CComContainedObject< CTearOff2 >。並且第一個對象內部實現了第二個對象,真正的查詢工作也是交給第二個對象去做。COuter::m_pUnkTearOff2是前面一個對象的IUnknown指針,當用它去查詢ITearOff2時,實際上是交給了其內部對象m_contained去做了,這在第8、9步可以看得很清楚。
  終於把這個宏講完了,我感覺這個宏可能是ATL接口映射宏中實現最複雜的了,其實它並沒有利用到CComContainedObject的真正功能。感覺實現這個宏也許不應這麼麻煩的。



五.COM_INTERFACE_ENTRY_AGGREGATE(iid, punk) 

  這一節中將介紹ATL中用於聚集對象的宏。聚集對象的概念請參閱其它參考書。
  現在先看一看這個宏的典型用法: 

class CAgg : 
    public IDispatchImpl< IAgg, &IID_IAgg, &LIBID_AGGREGLib >,
    public ISupportErrorInfo, 
    public CComObjectRoot, 
    public CComCoClass< CAgg,&CLSID_CAgg >

    ..... 
};

  CAgg是一個聚集類,它的實現與一般的ATL組件沒有區別,只是注意在它的類定義中不要加入DECLARE_NO_AGGREGATABLE. 

class COuter : 
    public CChainBase, 
    public IDispatchImpl< IOuter, &IID_IOuter, &LIBID_COMMAPLib >, 
    public CComCoClass< COuter,&CLSID_COuter >
{
    HRESULT FinalConstruct(); 
    void FinalRelease(); 

    BEGIN_COM_MAP(COuter) 
        COM_INTERFACE_ENTRY_AGGREGATE(IID_IAgg, m_pUnkAgg.p) 
    END_COM_MAP() 

    DECLARE_GET_CONTROLLING_UNKNOWN() 

    CComPtr< IUnknown > m_pUnkAgg; 
};

  COuter包含了聚合組件CAgg,它包含了幾個不同之處: 

  (1)加入了COM_INTERFACE_ENTRY_AGGREGATE(IID_IAgg, m_pUnkAgg.p)宏。 

#define COM_INTERFACE_ENTRY_AGGREGATE(iid, punk)\ 
    {&iid,\ 
    (DWORD)offsetof(_ComMapClass, punk),\ 
    _Delegate},

  offsetof我們在上一節中已經見過,可以猜到它求的就是punk在類中的位置。也就是m_pUnkAgg在COuter中的位置。 

  (2)加入了宏DECLARE_GET_CONTROLLING_UNKNOWN(),其定義爲: 

#define DECLARE_GET_CONTROLLING_UNKNOWN() public:\ 
    virtual IUnknown* GetControllingUnknown() {return GetUnknown();}

  我們也沒必要繼續深究下去,僅從字面意思就可以看出這個函數將返回組件的IUnknown 指針。 

  (3)在COuter中加入一個成員變量:CComPtr< IUnknown > m_pUnkAgg; m_pUnkAgg將用於獲得被聚集組件的IUnknown指針。 

  (4)重載了FinalConstruct,FinalRelease 

HRESULT COuter::FinalConstruct() 
{
    IUnknown* pUnkOuter = GetControllingUnknown(); 
    HRESULT hRes = CoCreateInstance(CLSID_CAgg, pUnkOuter, CLSCTX_ALL, IID_IUnknown, (void**)&m_pUnkAgg); 
    return hRes; 


void COuter::FinalRelease() 
{
    m_pUnkAgg.Release(); 
    ..... 
}

  當創建組件COuter後將會調用FinalConstruct,所以會在這裏創建聚集組件。原則上聚集組件可以僅在需要的時候才創建,但也可以隨着包含它的組件一起創建。聚集組件的創建沒什麼特別之處,只是要注意它將查詢IUnknown指針,並返回給m_pUnkAgg.外部組件將通過m_pUnkAgg操作聚集組件。另外注意到使用pUnkOuter作爲CoCreateInstance的參數,這將導致創建CComAggObject< COuter >對象,內部包含一個CComContainedObject的包含對象。與上一節中的CComCachedTearOff<>類似,CComAggObject< COuter >也不是從COuter派生的,所以真正的組件對象不是CComAggObject< COuter >對象,而是它內部包含的CComContainedObject< COuter >對象。同樣pUnkOuter得到的將是CComAggObject<>的IUnknown指針,也同樣調用它的QueryInterface會轉而調用CComContainedObject的_InternalQueryInterface函數(呵呵,現在可都還是我猜的,看我猜的對不對吧) 

  運行pOuter->QueryInterface(IID_IAgg, (void **)&pAgg1) 

  函數堆棧一: 

9.ATL::AtlInternalQueryInterface(...) 
  8.ATL::CComObjectRootBase::InternalQueryInterface(...) 
  7.CAgg::_InternalQueryInterface(...) 
  6.ATL::CComAggObject< CAgg >::QueryInterface(...) 
  5.ATL::CComObjectRootBase::_Delegate(...) 
  4.ATL::AtlInternalQueryInterface(...) 
  3.ATL::CComObjectRootBase::InternalQueryInterface(...) 
  2.COuter::_InternalQueryInterface(...) 
  1.ATL::CComObject< COuter >::QueryInterface(...)

  解釋: 

  1-5:這幾步函數調用我們已經見了很多次了,因爲在這個宏定義使用了_Delegate,所以將調用CComObjectRootBase::_Delegate(...). 

static HRESULT _Delegate(void* pv,REFIID iid,void** ppvObject,DWORD dw) 

    HRESULT hRes = E_NOINTERFACE; 
    IUnknown* p = *(IUnknown**)((DWORD)pv + dw); 
    if (p != NULL) hRes = p->QueryInterface(iid, ppvObject); 
    return hRes; 
}

  第二句話的含義我們在上一節中已經見過了,最後的結果p=COuter::m_pUnkAgg.

  6:正如我們剛纔所料,現在調用的是CComAggObject< CAgg >::QueryInterface() 

STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) 
{
    //如果查詢的是IUnknown,則.... 
    else 
        hRes = m_contained._InternalQueryInterface(iid, ppvObject); 
    return hRes; 
}

  也正如我們所料,將交給它的包含對象去做.(這段代碼在上一節好象也見過是吧,呵呵) 
  7-9:同上一節一樣,將交給CAgg::_InternalQueryInterface(...),剩下的工作將由CAgg完成了。最後返回的指針實際上將是CComContainedObject< CAgg >組件的接口指針。 

  運行pAgg1->QueryInterface(IID_IAgg, (void **)&pAgg2) 

  函數堆棧二: 

9.CAgg::_InternalQueryInterface(...) 
  8.ATL::CComAggObject< CAgg >::QueryInterface(...) 
  7.ATL::CComObjectRootBase::_Delegate(...) 
  6.ATL::AtlInternalQueryInterface(...) 
  5.ATL::CComObjectRootBase::InternalQueryInterface(...) 
  4.COuter::_InternalQueryInterface(...) 
  3.ATL::CComObject< COuter >::QueryInterface(...) 
  2.ATL::CComObjectRootBase::OuterQueryInterface(...) 
  1.ATL::CComContainedObject< CAgg >::QueryInterface(...)

  解釋: 

  1-9:瀏覽整個堆棧,與我們上一節所見的堆棧二太相近了,這是因爲都是使用了包含對象。包含對象起了個代理的作用,他先把查詢交給外部對象(COuter)去做(第1,2步), 當外部對象發現要查詢的是聚集組件的接口時(IAgg),就會再把查詢交還給它保留的聚集組件的指針(m_pUnkAgg,第7步中,注意這不是真正的聚集組件),m_pUnkAgg再把查詢交給包含對象(第8步中),包含對象再把查詢交給真正實現接口的類CAgg(第9步). 若外部對象發現要查詢的是外部組件的接口時,那就很簡單了,直接查詢就行了。這樣就防止了外部組件與聚集組件查詢操作的不一致性。唉,真個過程真麻煩,不過還好,與上一節的宏很類似。相關的源碼可參看上一節。 


 六、COM_INTERFACE_ENTRY_AGGREGATE_BLIND 參ATL例程COMMAP 

  上一節我們講了COM_INTERFACE_ENTRY_AGGREGATE,這節要介紹的宏與它很類似。 

#define COM_INTERFACE_ENTRY_AGGREGATE_BLIND(punk)\ 
    {NULL,\ 
    (DWORD)offsetof(_ComMapClass, punk),\ 
    _Delegate},

  從定義上就可以看出,它與上一節介紹宏的唯一區別就在於,它沒有指明接口ID!!
  所以在它的定義中第一項也是NULL。 

  這個宏的用法與我們COM_INTERFACE_ENTRY_AGGREGATE一模一樣。大家可以參考上一節內容以及ATL的例程COMMAP。 

  我們來看看AtlInternalQueryInterface()中的相關代碼。 

ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis, 
    const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject) 
{
    //如果是IUnknown,.... 
    while (pEntries->pFunc != NULL) 
    {
        BOOL bBlind = (pEntries->piid == NULL); 
        if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid)) 
        { 
            if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset
            {
                ATLASSERT(!bBlind); 
                IUnknown* pUnk = (IUnknown*)((int)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; 


  注意變量bBlind: 

  BOOL bBlind = (pEntries->piid == NULL);

若沒指定接口ID,也繼續執行後面的操作,可見即使並非我們所需要的IID,也會執行_Delegate. 
  從上可見,這個宏適用於一個聚集組件有多個接口的情況,這樣只要是查詢這個聚集組件的接口,就會進入_Delegate函數。但要特別注意的是這個宏的位置!! 比如若是這樣的順序: 

BEGIN_COM_MAP 
    COM_INTERFACE_ENTRY_AGGREGATE_BLIND(m_pUnkAggBlind.p) 
    COM_INTERFACE_ENTRY(IOuter) 
END_COM_MAP

  當查詢IOuter接口時就會出錯!!!


七、COM_INTERFACE_ENTRY_AUTOAGGREGATE(iid, punk, clsid) 

  先看看這個宏的定義: 

#define COM_INTERFACE_ENTRY_AUTOAGGREGATE(iid, punk, clsid)\ 
    {&iid,\ 
    (DWORD)&_CComCacheData<\ 
        CComAggregateCreator< _ComMapClass, &clsid >,\ 
        (DWORD)offsetof(_ComMapClass, punk)\ 
        >::data,\ 
    _Cache},

先看看它的典型用法: 

class CAutoAgg : 
    public IDispatchImpl< IAutoAgg, &IID_IAutoAgg, &LIBID_AGGREGLib >, 
    public ISupportErrorInfo, 
    public CComObjectRoot, 
    public CComCoClass< CAutoAgg,&CLSID_CAutoAgg > 

    ...... 
};

與一般的組件並無二樣。 

class COuter : 
    public CChainBase, 
    public IDispatchImpl, 
    public CComCoClass 

    BEGIN_COM_MAP(COuter) 
        COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IAutoAgg, m_pUnkAutoAgg.p, CLSID_CAutoAgg) 
    END_COM_MAP() 

    CComPtr m_pUnkAutoAgg; 
};

  與宏COM_INTERFACE_ENTRY_AGGREGRATE(_)不同,COuter不用在FinalConstruct中創建聚集組件。外部組件會自動創建聚集組件!!! 

1。 

template < class Creator, DWORD dwVar >
_ATL_CACHEDATA _CComCacheData< Creator, dwVar >::data = {dwVar, Creator::Creat eInstance};

2。 

static HRESULT WINAPI _Cache(void* pv, REFIID iid, void** ppvObject, DWORD dw) 

    HRESULT hRes = E_NOINTERFACE; 
    _ATL_CACHEDATA* pcd = (_ATL_CACHEDATA*)dw; 
    IUnknown** pp = (IUnknown**)((DWORD)pv + pcd->dwOffsetVar); 
    if (*pp == NULL) hRes = pcd->pFunc(pv, IID_IUnknown, (void**)pp); 
    if (*pp != NULL) hRes = (*pp)->QueryInterface(iid, ppvObject); 
    return hRes; 
}

3。 

template < class T, const CLSID* pclsid >
class CComAggregateCreator 

public: 
    static HRESULT WINAPI CreateInstance(void* pv, REFIID/*riid*/, LPVOID* ppv ) 
    {
        ATLASSERT(*ppv == NULL); 
        ATLASSERT(pv != NULL); 
        T* p = (T*) pv; 
        return CoCreateInstance(*pclsid, p->GetControllingUnknown(), CLSCTX_ALL, IID_IUnknown, ppv); 
    } 
};

  因爲_Cache,_CComCacheData,CComAggregateCreator這幾個類和函數我們已經在前面見過或者見過類似的,所以就不再多講了。總之我們可以看到,若m_pUnkAutoAgg.p不爲空則直接查詢,否則創建聚集組件。 

  與宏COM_INTERFACE_ENTRY_AGGREGATE相比,這個宏似乎更好一些,僅當需要時纔會創建,使用更簡單。 

  八、COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND( punk, clsid )

  看看它的定義: 

#define COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND(punk, clsid)\ 
    {NULL,\ 
    (DWORD)&_CComCacheData<\ 
        CComAggregateCreator< _ComMapClass, &clsid >,\ 
        (DWORD)offsetof(_ComMapClass, punk)\ 
    >::data,\ 
    _Cache},

  呵呵,這個宏綜合了 COM_INTERFACE_ENTRY_AUTOAGGREGATE() 和 COM_INTERFACE_ENTRY_AGGREGATE_BLIND() 的特點,既可以自動創建也可以很方便地查詢聚集組件中的多個接口。不再贅述!! 


九、COM_INTERFACE_ENTRY_CHAIN(classname) 參ATL例程COMMAP 

  先看看它的定義: 

#define COM_INTERFACE_ENTRY_CHAIN(classname)\ 
    {NULL,\ 
    (DWORD)&_CComChainData< classname, _ComMapClass >::data,\ 
    _Chain},

  典型用法: 

class CChain : 
    public IDispatchImpl< IChain, &IID_IChain, &LIBID_COMMAPLib >, 
    public ISupportErrorInfo, 
    public CComObjectRoot,
    public CComCoClass< CChain,&CLSID_CChain >

    ........ 
};

  它與一般的組件無異。 

class COuter : 
    public CChain, 
    .... 
{
    BEGIN_COM_MAP(COuter) 
        ...... 
        COM_INTERFACE_ENTRY_CHAIN(CChain) 
    END_COM_MAP() 
};

  我們對查詢的過程已經很熟悉了,可以直接來看看_Chain的功能。_Chain()是CComObjectRootBase的成員函數: 

static HRESULT WINAPI _Chain(void* pv, REFIID iid, void** ppvObject,DWORD dw) 

    _ATL_CHAINDATA* pcd = (_ATL_CHAINDATA*)dw; 
    void* p = (void*)((DWORD)pv + pcd->dwOffset); 
    return InternalQueryInterface(p, pcd->pFunc(), iid, ppvObject); 


struct _ATL_CHAINDATA 
{
    DWORD dwOffset; 
    const _ATL_INTMAP_ENTRY* (WINAPI *pFunc)(); 
};

  我們再看看宏定義中的dw部分: 

template < class base, class derived >
_ATL_CHAINDATA _CComChainData< base, derived >::data = 
    {offsetofclass(base, derived), base::_GetEntries};

  基本上我們已經看懂是怎麼回事了,void *p將得到基類的指針,InteralQueryInterface 我們已經很熟悉了,_Chain把基類的指針以及基類的接口映射宏傳給它,實際上是查詢基類的接口!!! 
  一般情況下把這個宏放在BEGIN_COM_MAP和END_COM_MAP之間的最後面,這表示只有在當前類中查不到接口時纔去查父類的接口。不過也經常把它放在第一位,這時就是先去查父類接口,只有父類沒有實現這種接口時才查自己。在ATL中組件是以多重繼承的方式實現的,ATL定義了很多類實現了一些常用的接口,這些類經常被做爲組件的基類,所以這個宏被大量使用。 

  所有重要的宏我們都已經講過了,剩下的都是些很簡單的宏了.呵呵,還是把它們都羅列一下,善始善終嘛. 

  十、COM_INTERFACE_ENTRY_IID(iid, x) 

#define COM_INTERFACE_ENTRY_IID(iid, x)\ 
    {&iid,\ 
    offsetofclass(x, _ComMapClass),\ 
    _ATL_SIMPLEMAPENTRY},

  十一、COM_INTERFACE_ENTRY2_IID(iid, x, x2) 

#define COM_INTERFACE_ENTRY2_IID(iid, x, x2)\ 
    {&iid,\ 
    (DWORD)((x*)(x2*)((_ComMapClass*)8))-8,\ 
    _ATL_SIMPLEMAPENTRY},

  從定義上看這兩個宏與COM_INTERFACE_ENTRY()和COM_INTERFACE_ENTRY2()相比,都只是多了一項"iid"。沒有別的好處,只不過由用戶明確指出接口IID,而不用系統根據接口名字去轉換了。 

  十二、COM_INTERFACE_ENTRY_FUNC( iid, dw, func ) 

#define COM_INTERFACE_ENTRY_FUNC(iid, dw, func)\ 
    {&iid, \ 
    dw, \ 
    func},

  還記得AtlInternalQueryInterface()中的代碼嗎?如果在接口映射表中找到了我們要找的接口,並且這個接口不是_ATL_SIMPLEENTRY型的,則執行宏定義中的指定的函數。
  這個宏就給我們提供了自己編寫處理函數的功能。這個函數必須是如下定義:HRESULT WINAPI func(void* pv, REFIID riid, LPVOID* ppv, DWORD dw); 
當AtlInternalQueryInterface調用func時,會傳進相關的信息。pv是類對象的指針,riid是要查詢的接口,ppv是要返回查詢得到的接口指針,dw是在宏定義中指定的參數。另外如果函數中不打算返回接口指針,則應把ppv賦爲NULL,並返回S_FALSE或 E_NOINTERFACE。返回S_FALSE剛會繼續查找下去,若返回E_NOINTERFACE則會終止查詢。若返回接口指針,則應返回S_OK. 

  十三、COM_INTERFACE_ENTRY_FUNC_BLIND(dw, func) 

#define COM_INTERFACE_ENTRY_FUNC_BLIND(dw, func)\
    {NULL, \ 
    dw, \ 
    func},

  至於_BLIND類型的特點可以看前面幾節。 

  十四、COM_INTERFACE_ENTRY_NOINTERFACE(x) 

#define COM_INTERFACE_ENTRY_NOINTERFACE(x)\
    {&_ATL_IIDOF(x), \
    NULL, \
    _NoInterface}, 

_NoInterface是CComObjectRootBase的成員函數,看看它的定義: 
static HRESULT WINAPI _NoInterface(...)
{
    return E_NOINTERFACE;
}

  原來它只是返回E_NOINTERFACE,並且將終止查詢。
  哈哈,看來是不想讓別人查到這個接口啊!!! 

  十五、COM_INTERFACE_ENTRY_BREAK(x) 

#define COM_INTERFACE_ENTRY_BREAK(x)\
    {&_ATL_IIDOF(x), \
    NULL, \
    _Break}, 

_Break也是CComObjectRootBase的成員函數,看看它的定義: 
static HRESULT WINAPI _Break(...)

    iid;
    _ATLDUMPIID(iid, _T("Break due to QI for interface "), S_OK); 
    DebugBreak();
    return S_FALSE;
}

  如果查到這個接口將調用DebugBreak(),並返回S_FALSE,繼續查詢下去。DebugBreak()是什麼效果大家自己試試吧,一定很熟悉的,呵呵。

  至此全部十五個接口映射宏我們都已經講完了,唉,真是不容易,特別是前面幾個宏跟蹤起來很麻煩。因爲文本方式的限制,所以很多東西不容易表達清楚。有些叫法也是我自己這麼叫的,可能與別人的習慣不同。沒辦法,大家將就將就了,呵呵。


文章出處:http://www.yesky.com/20020520/1612040_9.shtml



發佈了35 篇原創文章 · 獲贊 21 · 訪問量 76萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章