ATL介面映射宏詳解[2]

來源:互聯網
上載者:User
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,這是我們給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::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常式:COMMAP

ATL中是以多重繼承的方式來實現組件的,但在繼承樹中如果有多個分支實現了同一
個介面,當查詢這個介面時就需要知道把哪個分支返回給它。這個宏就是幹這個工作的
通常這個宏是用於IDispatch介面。我們先來看看它的典型用法:
class COuter :
public IDispatchImpl &LIBID_COMMAPLib)>,
public IDispatchImpl &LIBID_COMMAPLib)>,
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模板類從類T中派生,所以COuter要從兩個它的模板類中
繼承,IOuter1、IOuter2都是雙介面,即都是從IDispatch派生的類,所以可得COuter
有兩條分支,也是個菱形結構,所以按照我們的樣本,這個位移值也應該是4。為了證明
我們的設想,我們再來通過函數堆棧來驗證我們的結果。

函數堆棧:
5.ATL::AtlInternalQueryInterface(...)
4.ATL::CComObjectRootBase::InternalQueryInterface(...)
3.CMyObject::_InternalQueryInterface(...)
2.ATL::CComObject::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 *)強制轉換,可以獲得這
個虛函數表的頂端地址,這正是我們所需要的。或許會問為什麼得到的是虛函數表的地
址,而不是一個類執行個體的地址呢?別忘了,介面是沒有資料的,它只有純虛函數。對於
客戶來說,它只能通過介面定義的虛函數來訪問它,而不可能訪問實現介面的類的成員
變數,組件的資料對客戶來說是不可見的,所以只用得到虛函數表的地址就行了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.