ATL:IDispatchImpl, IDispEventImpl, IDispEventSimpleImpl的區別和聯繫

這幾個類都和IDispatch的實現有關係,但是他們提供的IDispatch的實現是不同的。

IDispatchImpl只能用於雙接口(Dual Interface)的實現。IDispatchImpl本身的IDispatch接口實現是使用ITypeInfo::Invoke的。ITypeInfo簡單來說是一個代表TypeLibrary中一個類型的COM對象,比如某個interface IA。而ITypeInfo::Invoke是把DispID使用TypeLibrary中的接口定義,換算成虛函數的Index,然後通過虛函數表(Vtable)來直接調用接口的函數。舉例來說:

[dual]
Interface IA : IDispatch
{
    [id(1)] HRESULT FuncA();
    [id(2)] HRESULT FuncB();
}

IA所對應的ITypeInfo對象(嚴格來說是實現ITypeInfo接口的COM對象)調用Invoke方法,傳入id=2。ITypeInfo::Invoke便可以通過TypeLibrary中的類型信息換算出2對應着IA中虛函數表的索引=(IUnknown) +4 (IDispatch)+2 (FuncB) -1 (從0開始算起) = 8 的函數指針。如果IA是dispinterface(純IDispatch的接口),ITypeInfo::Invoke則會直接失敗。

順便說一句,ATL本身對多個IDispatch實現支持不太好,無法很容易的將多個IDispatch實現合併成一個。比如考慮一下:我們可能繼承自多個IDispatchImpl,他們分別具有自己的IDispatch實現,支持不同的接口。我們可以通過COM_INTERFACE_ENTRY2(IDispatch, XXX) 選擇其中一個,但是無法很容易的合併。

 

IDispEventImpl和ISimpleEventImpl這兩個類顧名思義主要是用於事件處理的。在COM中,爲了讓C/C++這種靜態語言和VB/Jscript等動態語言都可以調用事件,而且事件本身速度一般不是特別重要,通常COM中的事件都是隻支持IDispatch接口,而不支持普通的虛函數接口,也就是定義爲dispinterface,如下:

dispinterface IA
{
methods:
    [id(1)] int FuncA();
    [id(2)] int FuncB();
}

在這種情況下,爲了讓ATL知道DISPID和函數實現的對應關係,用戶必須手動提供一張表格,來定義這種對應關係,也就是通過一些BEGIN_SINK_MAP/SINK_ENTRY等等宏來定義,這些細節MSDN有詳細描述,也比較好理解,這裏不再贅述。最重要的是,這兩個類不可以直接用來提供dispinterface的實現,而是必須用在事件處理中。原因很簡單:IDispEventImpl和IDispEventSimplImpl所定義的IDispatch實現位於單獨的Vtable中。大家都知道在ATL中,爲了支持QI,所有支持的QI的接口都會使用BEGIN_COM_MAP,COM_INTERFACE_ENTRY等宏來定義,形成一張表,以供ATL所提供的QueryInterface實現來查找。這個實現,本質上是基於C++的Casting機制,這也是爲什麼ATL需要引入COM_INTERFACE_ENTRY2這樣的宏來告訴ATL從哪個繼承分支來獲得某個接口(比如有多個接口IA、IB都實現IC,那麼你需要告訴ATL是從IA Cast到IC還是從IB Cast到IC)。由於IDispEventImpl和IDispEventSimpleImpl這兩個類並沒有直接繼承IDispatch,這也意味着COM_INTERFACE_ENTRY這樣的宏完全失去了作用,也就是說無法通過QueryInterface來直接獲得IDispEventImpl和IdispEventSimpleImpl這兩個類所實現事件回調接口。ATL的處理辦法是,強制大家都必須要用IdispEventSimplImpl::DispEventAdvise/ IdispEventSimplImpl::DispEventUnadvise來註冊事件,而普通的AtlAdvise和AtlUnadvise是無法工作的。原因也正是QueryInterface無法QI到對應的事件接口,而DispEventAdvise可以直接強制cast,如下:

HRESULT DispEventAdvise(IUnknown* pUnk, const IID* piid)
{
    ATLENSURE(m_dwEventCookie == 0xFEFEFEFE);        
    return AtlAdvise(pUnk, (IUnknown*)this, *piid, &m_dwEventCookie);
}

顯然IDispEventSimpleImpl是不繼承自Iunknown的,因此這個Cast是強制的Cast,等價於reinterpret_cast。如果大家看看IdispEventSimpleImpl的實現,這個類不繼承自Iunknown,但是卻定義了Iunknown和IDispatch的所有方法,這樣,強制Cast到Iunknown是可以工作的,因爲Vtable的layout是一致的。而IDispEventSimpleImpl的QueryInterface的實現則是支持QI到事件回調接口的,這也就解決了問題。

按我來看這個設計實在是有些奇怪,爲什麼不可以直接利用COM_INTERFACE_ENTRY來支持QI呢?當然了,ATL目前的這個設計可以解決問題,只是比較容易用錯,我自己也是一段時間不用就忘記了,這裏把我的發現寫下來,可以做今後的參考,同時也希望能夠對用ATL的朋友有些幫助。

作者:張羿 (ATField) Blog: http://blog.csdn.net/atfield EMail: [email protected] 轉載請註明出處
發佈了119 篇原創文章 · 獲贊 3 · 訪問量 78萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章