




下載本文檔
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、1,六 . COM接口的其他實(shí)現(xiàn)方法,基于表格驅(qū)動(dòng)的接口查詢 接口查詢的本質(zhì) 宏 應(yīng)用 多重繼承下的名字沖突 潛在的缺陷 臨時(shí)的方案 使用復(fù)合技術(shù)(嵌套類)實(shí)現(xiàn)接口 COM主對(duì)象和COM子對(duì)象 COM 主對(duì)象的實(shí)現(xiàn) COM 子對(duì)象的實(shí)現(xiàn) 基于復(fù)合技術(shù)的COM對(duì)象的內(nèi)存結(jié)構(gòu),使用前面的方法,我們已經(jīng)可以順利地實(shí)現(xiàn)接口與對(duì)象了.這些基本的技術(shù)是實(shí)現(xiàn)COM的基石.但是,人類對(duì)于完美與高效的追求是無止境的.本章介紹一些更加精細(xì)的技術(shù),看這些技巧是如何提高COM的效率,如何使得COM的功能細(xì)致入微的.,針對(duì)接口的引用計(jì)數(shù) 需求 實(shí)現(xiàn)方法 多重繼承與復(fù)合的結(jié)合 復(fù)合技術(shù)的表格驅(qū)動(dòng) 動(dòng)態(tài)復(fù)合接口 MFC對(duì)C
2、OM的支持,2,1.基于表格驅(qū)動(dòng)的接口查詢,1.1 接口查詢的本質(zhì) 前面我們使用繼承的方式實(shí)現(xiàn)接口,使用多重繼承的方式實(shí)現(xiàn)多個(gè)接口. 在這種方式下,接口的查詢QueryInterface函數(shù)的實(shí)現(xiàn)非常的直接且直觀. 在多重繼承方式下,接口類是基類, IUnkown接口是最上層的基類. 對(duì)象類是接口類的派生的子類. 在內(nèi)存中,子類比基類“大”. 因?yàn)樽宇惓税惖某蓡T以外,還包含自己的成員.子類的一個(gè)實(shí)例中包含有基類的一個(gè)“subobject”,子對(duì)象. 如果這個(gè)基類還有基類,這個(gè)子對(duì)象中還含有一個(gè)更上層的子對(duì)象. QueryInterface函數(shù)的本質(zhì)是: 使用statice_cast操作
3、符在子類的對(duì)象中加上基類的偏移從而從而得到基類的子對(duì)象.轉(zhuǎn)換到不同的基類時(shí),要加上不同的偏移. 所以QueryInterface實(shí)際上是在不同的基類和不同的偏移中工作. 我們可以把基類和對(duì)應(yīng)的偏移量抽象出來.作成表格.使得QueryInterface的工作更加形式化. 最終使得COM的編碼更加形式化. (這里的工作為向MFC過渡而熱身.我們看到,MFC中古怪的代碼也是有其理性的由來的. ),3,1.2 宏,為了實(shí)現(xiàn)表格驅(qū)動(dòng)的QueryInterface, 我們定義這樣的一個(gè)結(jié)構(gòu): typedef HRESULT (STDAPICALLTYPE *INTERFACE_FINDER)(void *
4、pThis, DWORD dwData, REFIID riid, void *ppv); /這是一個(gè)查詢接口的函數(shù),暫時(shí)這里并沒有用上. #define ENTRY_IS_OFFSET INTERFACE_FINDER(-1) /所以這里定義了一個(gè)偽函數(shù)ENTRY_IS_OFFSET typedef struct _INTERFACE_ENTRY const IID * pIID; / 要尋找的接口的IID INTERFACE_FINDER pfnFinder; / finder function long dwData; / finder function所需的參數(shù).這里指偏移量. INT
5、ERFACE_ENTRY; /暫時(shí),這里只使用了第一和第三個(gè)分量,4,然后定義了幾個(gè)宏: #define BASE_OFFSET(ClassName, BaseName) (DWORD(static_cast(reinterpret_cast(0 x10000000) - 0 x10000000) 這個(gè)宏用來計(jì)算基類BaseName到子類ClassName的偏移.(一個(gè)完整對(duì)象中的基類子對(duì)象的起始地址到完整對(duì)象的起始地址的偏移) reinterpret_cast(0 x10000000) 把絕對(duì)內(nèi)存地址0 x10000000轉(zhuǎn)換成為子類對(duì)象的地址. static_cast(子類指針) 把子類指
6、針轉(zhuǎn)換為基類指針.這個(gè)工作由編譯器計(jì)算出偏移.并且加上偏移值. DWORD(基類指針地址) - 0 x10000000 基類指針地址轉(zhuǎn)換成絕對(duì)的數(shù)字后減去子類指針的絕對(duì)地址的數(shù)字. 宏BASE_OFFSET(ClassName, BaseName) 返回基類BaseName到子類ClassName的偏移量. 如下圖所示: 在這個(gè)宏的基礎(chǔ)上又定義了幾個(gè)宏:,5,#define BEGIN_INTERFACE_TABLE(ClassName) typedef ClassName _InterfaceTableClassName; /申明了一個(gè)靜態(tài)函數(shù),返回接口表 static const INTE
7、RFACE_ENTRY *GetInterfaceTable(void) static const INTERFACE_ENTRY table = /函數(shù)體中, 定義一個(gè)靜態(tài)的接口表, 定義了類,此表就分配了.對(duì)所有的對(duì)象而言是同一個(gè). #define IMPLEMENTS_INTERFACE(ItfName) /結(jié)尾項(xiàng) 我們還要定義一個(gè)函數(shù):,6,HRESULT STDAPICALLTYPE InterfaceTableQueryInterface(void *pThis, const INTERFACE_ENTRY *pTable, REFIID riid, void *ppv) if (
8、 riid= IID_IUnknown) /對(duì)于IUnknown接口 *ppv = pThis + pTable-dwData;/直接加上第一項(xiàng)的偏移量. (IUnknown*)(*ppv)-AddRef(); /計(jì)數(shù)加1 return S_OK; else /別的接口 HRESULT hr = E_NOINTERFACE; while (pTable-pIID) /如果沒有到達(dá)尾部則, (結(jié)尾項(xiàng)為空 ) if (riid=pTable-pIID) /比較ID *ppv = pThis + pTable-dwData;/加上偏移量. (IUnknown*)(*ppv)-AddRef(); /計(jì)
9、數(shù)加1 hr = S_OK; break; /找到了處理完則退出 pTable+;/否則循環(huán)遍歷接口表. if (hr != S_OK) *ppv = 0; /不支持此接口 return hr; 在此基礎(chǔ)上, 以下宏實(shí)現(xiàn)IUnknown接口:,7,先定義一個(gè)結(jié)構(gòu)用于引用計(jì)數(shù): struct AUTO_LONG LONG value; AUTO_LONG(void) : value(0) ; /構(gòu)造函數(shù)使得其值為 #define IMPLEMENT_UNKNOWN(ClassName) AUTO_LONG m_cRef; /引用計(jì)數(shù)變量 STDMETHODIMP QueryInterface(R
10、EFIID riid, void *ppv) return InterfaceTableQueryInterface(this, GetInterfaceTable(), riid, ppv); /直接調(diào)用剛定義的InterfaceTableQueryInterface STDMETHODIMP_(ULONG) AddRef(void) return InterlockedIncrement( /實(shí)現(xiàn)IUnknown定義的三個(gè)函數(shù),8,現(xiàn)在來看應(yīng)用. 對(duì)于第五章字典對(duì)象的例子: class CDictionary : public IDictionary , public ISpellChec
11、k public : CDictionary(); CDictionary(); /構(gòu)造函數(shù),析構(gòu)函數(shù) virtual HRESULT _stdcall QueryInterface(const IID,1.3 應(yīng)用,9,我們可以寫成: class CDictionary : public IDictionary , public ISpellCheck public : CDictionary(); CDictionary(); /構(gòu)造函數(shù),析構(gòu)函數(shù) / 使用宏來定義接口表,實(shí)現(xiàn)IUnknown 成員函數(shù) IMPLEMENT_UNKNOWN(CDictionary) BEGIN_INTERF
12、ACE_TABLE(CDictionary) IMPLEMENT_INTERFACES(IDictionary) /請(qǐng)求IUnkown接口,則返回IDictionary.因?yàn)樗潜淼牡谝豁?xiàng). 見InterfaceTableQueryInterface的定義. IMPLEMENT_INTERFACES(ISpellCheck) END_INTERFACE_TABLE() virtual BOOL _stdcall Initialize(); / IDictionary成員函數(shù) . virtual BOOL _stdcall CheckWord (String word, String *); /
13、 ISpellCheck成員函數(shù) private : structDictWord *m_pData; char*m_DictFilename128;/與功能相關(guān)的私有數(shù)據(jù). / int m_Ref ;不再需要單獨(dú)定義引用計(jì)數(shù) ; /并沒有引進(jìn)新技術(shù),但是,更加形式化.便于編程,提高效率.,10,2. 多重繼承下的名字沖突,2.1 潛在的缺陷: 使用多重繼承使用C+類來實(shí)現(xiàn)COM接口,是一種非常有效的技術(shù).編碼量小,直觀.建立COM規(guī)范所要求的vptr和vtbl的工作大多由編譯器完成了. 使用多重繼承,一個(gè)虛函數(shù)出現(xiàn)在多個(gè)基類中,在子類中實(shí)現(xiàn)后,該子類的屬于多個(gè)基類的虛表中的該虛函數(shù)的表項(xiàng)都指
14、向這個(gè)實(shí)現(xiàn). QueryInterface, AddRef, Release就是利用這樣的方法實(shí)現(xiàn)的, 而且工作得很好. 這個(gè)特性對(duì)于基類中其他的函數(shù)也是成立的.但是有的時(shí)候,這就會(huì)成為一種限制.一種潛在的缺陷. 設(shè)想一種交通工具水上飛機(jī)boatplane.它既可以象飛機(jī)一樣飛,也可以象船一樣在水中航行.我們做一個(gè)COM對(duì)象BoatPlane,它使用多重繼承的方式實(shí)現(xiàn)了接口IBoat,和接口IPlane. 這兩個(gè)接口都派生自接口IVehicle. IVehicle有一個(gè)函數(shù)GetMaxSpeed以返回該交通工具的最大速度.IBoat繼承了此函數(shù).同樣IPlane也繼承了此函數(shù). 注意接口是定義
15、函數(shù)的地方,它不能夠?qū)崿F(xiàn)它,(IBoat和IPlane是抽象基類,GetMaxSpeed是純虛函數(shù)). 在COM對(duì)象類BoatPlane中對(duì)此函數(shù)的實(shí)現(xiàn),會(huì)導(dǎo)致BoatPlane中的屬于IBoat和IPlane的GetMaxSpeed函數(shù)表項(xiàng)都指向該實(shí)現(xiàn).這意味著客戶得到COM對(duì)象的接口指針I(yè)Boat和IPlane時(shí),調(diào)用GetMaxSpeed會(huì)得到同樣的結(jié)果! 多重繼承下的名字沖突. 潛在的缺陷爆發(fā)了,11,本章中用到的類的繼承層次,使用多重繼承的方式實(shí)現(xiàn)接口時(shí)的潛在缺陷,一種簡(jiǎn)單的解決方案是: IBoat不從IVehicle派生, 把GetMaxSpeed換成GetMasBoatSpeed
16、, IPlane也不從IVehicle派生, 把GetMaxSpeed換成GetMaxPlaneSpeed. 換個(gè)名字而已.再在BoatPlane類中對(duì)這兩個(gè)函數(shù)分別實(shí)現(xiàn), 50KM/H, 800KM/H 問題似乎解決,但是, 我們要改變接口的繼承結(jié)構(gòu)嗎? 而且, IBoat類的設(shè)計(jì)者十分不情愿把GetMaxSpeed換成GetMasBoatSpeed, 因?yàn)椤癇oat”對(duì)其而言是多余的,不雅觀的.同樣,IPlane的設(shè)計(jì)者也有同感. 畢竟, 人類對(duì)完美的追求是無止境的,12,一個(gè)別致的方案: 針對(duì)IBoat,設(shè)計(jì)一個(gè)中間類: struct IXBoat:public IBoat virtua
17、l HRESULT GetMaxBoatSpeed(long *pV)=0; /新增加一個(gè)純虛函數(shù). 此函數(shù)在COM對(duì)象中實(shí)現(xiàn) HRESULT GetMaxSpeed(long *pV) return GetMaxBoatSpeed(pV); /實(shí)現(xiàn)了接口IBoat的GetMaxSpeed,但是,只轉(zhuǎn)交給另一個(gè)函數(shù)GetMaxBoatSpeed. COM對(duì)象不再改寫此函數(shù). /此類既不是COM接口類,也不是COM實(shí)現(xiàn)類. 針對(duì)IPlane, 也作類似的處理,設(shè)計(jì)一個(gè)中間類IXPlane,增加一個(gè)純虛函數(shù)GetMaxPlaneSpeed,并把其GetMaxSpeed轉(zhuǎn)交給新函數(shù)處理.,2.2臨時(shí)
18、的方案,13,而COM對(duì)象類: class BoatPlane:public IXBoat, public IXPlane public: / IUnknown的方法: QueryInterface,AddRef,Release /IBoat的方法: HRESULT swim(void); / IPlane的方法: HRESULT fly(void); /IXBoat的方法: HRESULT GetMaxBoatSpeed(long *pV) *pV=50; /IXPlane的方法: HRESULT GetMaxPlaneSpeed(long *pV) *pV=800; /注意沒有改寫接口類的
19、IBoat和IPlane的GetMaxSpeed. 只改寫了中間類的兩個(gè)函數(shù). COM對(duì)象的內(nèi)存結(jié)構(gòu)如下:,14,多重繼承中發(fā)生名字沖突的一種解決方案.,15,IBoat-GetMaxSpeed(*pV)將在內(nèi)部調(diào)用GetMaxBoatSpeed,返回50 IPlane-GetMaxSpeed(*pV)將在內(nèi)部調(diào)用GetMaxPlaneSpeed,返回800 以上方案相當(dāng)不錯(cuò).注意,BoatPlane類沒有改寫接口類的GetMaxSpeed方法.實(shí)際上,一旦改寫了,那么,費(fèi)盡心機(jī)所加的中間類,以及中間函數(shù)都將失效. 客戶使用IBoat接口或IPlane接口調(diào)用GetMaxSpeed時(shí)將調(diào)用Bo
20、atPlane的實(shí)現(xiàn).所有的努力白費(fèi). 這是一個(gè)悖論! 接口定義了,實(shí)現(xiàn)卻不能寫它,客戶還要調(diào)用. 稱之為完美無缺,尚難服眾. 在下面的章節(jié)中,我們有新的方法.,16,3.使用復(fù)合(COMPOSITE)技術(shù)實(shí)現(xiàn)接口,3.1 COM主對(duì)象和COM子對(duì)象 復(fù)合(COMPOSITE)指在一個(gè)類中包含了另一個(gè)類的實(shí)例作為其數(shù)據(jù)成員.有時(shí)也稱為嵌套類. (當(dāng)一個(gè)類A內(nèi)嵌另一個(gè)類B時(shí), 為方便起見,稱A為B的父類,或包裝類,稱B為A的子類. 注意與繼承關(guān)系帶來的基類與子類的關(guān)系區(qū)別開來. ) COM規(guī)定了接口的結(jié)構(gòu),但是并沒有規(guī)定其實(shí)現(xiàn)方法. 復(fù)合方式是使用C+語言實(shí)現(xiàn)COM接口的另一種方法. 使用復(fù)合技
21、術(shù)可以很好地解決名字沖突問題. 方法是: 把發(fā)生名字沖突的接口以不同的C+的類實(shí)現(xiàn),讓COM對(duì)象類復(fù)合(即內(nèi)嵌)這些類的實(shí)例. 為了讓這些被內(nèi)嵌(復(fù)合)進(jìn)來的數(shù)據(jù)成員在客戶的眼里(通過接口指針)看起來是一個(gè)對(duì)象, 通常我們?cè)贑OM對(duì)象中做一個(gè)QueryInterface的主實(shí)現(xiàn)(master implementation), 其他數(shù)據(jù)成員的QueryInterface都委托給這個(gè)主實(shí)現(xiàn). COM對(duì)象類的定義如下:,17,class BoatPlane public: BoatPlane(void): m_Ref(0) HRESULT _stdcall QueryInterface(REFIID
22、 iid,void *ppv); ULONG _stdcall AddRef(); /這三個(gè)函數(shù)不必是虛函數(shù) ULONG _stdcall Release(); /嵌套類從接口類派生 struct XBoat : public IBoat inline BoatPlane *This(); /函數(shù)返回指向父類的指針 HRESULT _stdcall QueryInterface(REFIID iid,void *ppv); ULONG _stdcall AddRef(); /這幾個(gè)是繼承自IUnknown,虛的. ULONG _stdcall Release(); HRESULT _stdca
23、ll Swim(); /具體的功能 HRESULT _stdcall GetMaxSpeed(long *pV); m_xBoat; /* 嵌套類數(shù)據(jù)成員1, 實(shí)現(xiàn)了IBoat接口. 在使用繼承的方式時(shí),它就是COM對(duì)象. 但這里,它作為主對(duì)象的一個(gè)成員. 不妨稱之為COM子對(duì)象,相應(yīng)地BoatPlane的實(shí)例稱為COM主對(duì)象. 子對(duì)象只使用了單繼承. 同一主對(duì)象的不同子對(duì)象都使用單繼承的方式. */,18,struct XPlane:public IPlane /嵌套類從接口類派生 inline BoatPlane *This(); /函數(shù)返回指向父類的指針 HRESULT _stdcall
24、 QueryInterface(REFIID iid,void *ppv); ULONG _stdcall AddRef(); ULONG _stdcall Release(); HRESULT _stdcall Fly(); HRESULT _stdcall GetMaxSpeed(long *pV); m_xPlane; /嵌套類數(shù)據(jù)成員2 實(shí)現(xiàn)了IPlane接口,另一個(gè)COM子對(duì)象. intm_Ref ;/ 用作引用計(jì)數(shù) char * m_pTonsOfMemForBoat; /為實(shí)現(xiàn)Boat功能而需要的數(shù)據(jù).見后文 ./其他數(shù)據(jù)成員. BoatPlane的QueryInterface等
25、函數(shù)如下:,19,HRESULT BoatPlane:QueryInterface(const IID BoatPlane 對(duì)QueryInterface的處理,并不是把指向自己的指針this進(jìn)行static_cast轉(zhuǎn)換, 而是對(duì)自己的成員變量即COM子對(duì)象進(jìn)行static_cast轉(zhuǎn)換. 再傳出去.,3.2 COM 主對(duì)象的實(shí)現(xiàn),20,ULONG CDictionary:AddRef() m_Ref +; /跟使用繼承的方式實(shí)現(xiàn)接口時(shí)的引用計(jì)數(shù)方法完全一致 return (ULONG) m_Ref; ULONG CDictionary:Release() m_Ref -; if (m_Re
26、f = 0 ) delete this; return 0; return (ULONG) m_Ref; ,21,為了維護(hù)對(duì)象的實(shí)體身份,(在這里為了實(shí)現(xiàn)IBoat和IPlane接口,我們使用了三個(gè)類, 其中兩個(gè)派生自接口類,然后作為第三個(gè)的內(nèi)嵌的數(shù)據(jù)成員) 我們不能讓客戶有所察覺,即“透明”地實(shí)現(xiàn)接口. 因此這里我們把COM子對(duì)象對(duì)IUnknown定義的函數(shù)的實(shí)現(xiàn)委托給COM主對(duì)象來完成. 因此有必要在COM子對(duì)象中訪問COM主對(duì)象的成員. 它們的This內(nèi)聯(lián)函數(shù)就是這個(gè)目的: inline BoatPlane* BoatPlane:XBoat:This(void) return (Boat
27、Plane*)(char*)this-offsetof(BoatPlane,m_xBoat); This函數(shù)把this指針減去類分量在類中的偏移得到父類指針. HRESULT BoatPlane:XBoat:QueryInterface(REFIID iid,void *ppv) return This()-QueryInterface(iid,ppv); /通過父類指針調(diào)用父類的實(shí)現(xiàn) ULONG BoatPlane:XBoat: AddRef() return This()-AddRef(); /通過父類指針調(diào)用父類的實(shí)現(xiàn) ULONG BoatPlane:XBoat: Release() r
28、eturn This()-Release(); /通過父類指針調(diào)用父類的實(shí)現(xiàn) /對(duì)于XPlane也采用類似的方法. 3.4 基于復(fù)合技術(shù)的COM對(duì)象的內(nèi)存結(jié)構(gòu): COM對(duì)象的結(jié)構(gòu)如下圖所示:,3.3 COM 子對(duì)象的實(shí)現(xiàn),22,基于復(fù)合技術(shù)實(shí)現(xiàn)COM接口 名稱沖突消除了.,23,4. 針對(duì)接口的引用計(jì)數(shù),4.1 需求 使用復(fù)合技術(shù)需要更多的編碼,而且,復(fù)合技術(shù)產(chǎn)生的代碼的質(zhì)量也可能不如使用多重繼承的方式好. 復(fù)合技術(shù)實(shí)現(xiàn)接口成功地消除了名字沖突. 復(fù)合技術(shù)之所以能夠做到這一點(diǎn)是因?yàn)樗鼪]有多重繼承所固有的“潛在的缺陷”, 如果這個(gè)缺陷會(huì)影響功能的話. 實(shí)際上,利用復(fù)合技術(shù)的這一個(gè)優(yōu)勢(shì), 我們還能
29、夠?qū)崿F(xiàn)針對(duì)接口的引用計(jì)數(shù). 在此之前,我們所謂的引用計(jì)數(shù)都是針對(duì)對(duì)象的. 一個(gè)對(duì)象的所有接口都對(duì)同一個(gè)計(jì)數(shù)變量進(jìn)行操作.對(duì)象無法區(qū)分是哪個(gè)接口對(duì)其進(jìn)行操作的.而且,實(shí)際上,通常我們也不必區(qū)分它. 但是,存在這樣的情況, 我們的COM對(duì)象越來越復(fù)雜,功能越來越多.(想一想我們的手機(jī)) COM對(duì)象為實(shí)現(xiàn)不同的接口準(zhǔn)備了完全不同的資源,如果暫時(shí)不使用其中的某個(gè)接口的話,我們完全可以對(duì)其所需要的資源暫時(shí)不予分配.(我們打電話的時(shí)候一定要把攝像頭打開嗎?).而把分配工作放到必要時(shí)進(jìn)行,而且,也要及時(shí)地釋放.,24,仍然考慮水上飛機(jī),在BoatPlane類中我們定義了一個(gè)成員變量: char * m_pT
30、onsOfMemForBoat; 這個(gè)成員變量只在在swim函數(shù)中要使用.也就是說,只有IBoat指針會(huì)使用它.而與IPlane指針無關(guān). 假設(shè)m_pTonsOfMemForBoat需要分配一個(gè)很大的內(nèi)存空間.我們當(dāng)然希望只在必要的時(shí)候分配.然而,如果使用多重繼承的方式實(shí)現(xiàn)COM接口,那么意味者所有的虛表中的AddRef和Release項(xiàng)都只指向同一個(gè)實(shí)現(xiàn).也即我們無法從引用計(jì)數(shù)中區(qū)分出IBoat接口來.當(dāng)然,對(duì)于分配過程我們還是有點(diǎn)辦法: HRESULT BoatPlane :QueryInterface(REFIID iid,void *ppv) if(iid=IID_Boat) if(m
31、_pTonsOfMemForBoat=NULL) m_pTonsOfMemForBoat=new char1024*1024*10; /任務(wù)只完成了一半, 10M的內(nèi)存只在最需要的時(shí)候分配. *ppv=static_cast( else if 但是,我們無法知道什么時(shí)候釋放掉.因?yàn)槲覀儾荒荑b別IBoat和IPlane所發(fā)出的Release調(diào)用. 可見, 針對(duì)接口的引用計(jì)數(shù)有明確的應(yīng)用需求. 實(shí)現(xiàn)方法如下:,25,我們使用復(fù)合技術(shù)來實(shí)現(xiàn)針對(duì)接口的引用計(jì)數(shù). COM 子對(duì)象XBoat的引用計(jì)數(shù)不再簡(jiǎn)單地委托給COM主對(duì)象實(shí)現(xiàn) 在上一節(jié)的BoatPlane定義中增加一個(gè)成員變量int m_BoatR
32、ef; 在BoatPlane的構(gòu)造函數(shù)中被賦值0. 除了XBoat的AddRef和Release函數(shù)變?yōu)? ULONG BoatPlane:XBoat: AddRef() ULONG res=InterLockedIncrement( ,4.2 實(shí)現(xiàn)方法,26,ULONG BoatPlane:XBoat: Release() ULONG res=InterLockedDecrement( XPlane的引用計(jì)數(shù)函數(shù)保持不變. 客戶的使用完全與以前一致.,27,為了使得此技術(shù)能正常工作,必須保證所有的接口指針用戶遵從COM規(guī)范的要求, Release調(diào)用必須作用在它對(duì)應(yīng)的AddRef指針上. 即
33、AddRef和Release必須完全保持配對(duì).因此在標(biāo)準(zhǔn)的QueryInterface中,在ppv指針賦值后,要使用 (IUnkown*)(*ppv)-AddRef(); /使用新指針 而不能使用 AddRef(); /使用舊指針. 新舊指針指向同一個(gè)COM對(duì)象的接口, 它們有可能指的同一個(gè)對(duì)象.(在多重繼承的情況下,同一個(gè)主對(duì)象),也有可能指的不同的對(duì)象(在復(fù)合技術(shù)下,不同的子對(duì)象). 在前者無論使用誰調(diào)用AddRef都是一樣的,在后者則有可能不同.為了一致起見,應(yīng)遵循COM規(guī)范,使用新指針. 以上方案可以實(shí)現(xiàn)針對(duì)接口的引用計(jì)數(shù),從而實(shí)現(xiàn)資源的動(dòng)態(tài)最優(yōu)分配和釋放.,28,5.多重繼承與復(fù)合的
34、結(jié)合,多重繼承雖然存在“潛在的缺陷”,但是, 如果它正好滿足我們的需求, 實(shí)際上,大多數(shù)時(shí)候是滿足的,而且具有編碼簡(jiǎn)單,代碼質(zhì)量高等優(yōu)點(diǎn). 而復(fù)合技術(shù)則能避免可能的名字沖突, 而且可以實(shí)現(xiàn)對(duì)接口的引用計(jì)數(shù),提高程序的運(yùn)行效率. 我們可以綜合運(yùn)用以上兩種方法, 在一個(gè)COM對(duì)象中,以不同的方法實(shí)現(xiàn)不同要求的接口. 仍然考慮水上飛機(jī), 我們可以這樣來實(shí)現(xiàn)它的接口: 使用繼承的方式實(shí)現(xiàn)IPlane接口, 使用復(fù)合的方式實(shí)現(xiàn)IBoat接口(IBoat不是有特殊的引用計(jì)數(shù)要求嗎?). COM對(duì)象的定義如下:,29,class BoatPlane: public IPlane /IPlane接口通過繼承的
35、方式實(shí)現(xiàn) public:BoatPlane(void): m_Ref(0) HRESULT _stdcall QueryInterface(REFIID iid,void *ppv); ULONG _stdcall AddRef(); /這三個(gè)函數(shù)通過IPlane從IUnknown繼承而來 ULONG _stdcall Release(); /這里改寫虛函數(shù),而不像前一節(jié),是單純的函數(shù) HRESULT _stdcall Fly(); /IPlane的函數(shù) HRESULT _stdcall GetMaxSpeed(long *pV);/IVehicle的函數(shù) struct XBoat:publi
36、c IBoat inline BoatPlane *This(); /函數(shù)返回指向父類的指針 HRESULT _stdcall QueryInterface(REFIID iid,void *ppv); ULONG _stdcall AddRef(); ULONG _stdcall Release(); HRESULT _stdcall Swim(); HRESULT _stdcall GetMaxSpeed(long *pV); /COM子對(duì)象的實(shí)現(xiàn)可以 與主對(duì)象的實(shí)現(xiàn)不一致. 通過IBoat接口和IPlane接口能得到不同的結(jié)果. m_xBoat; /嵌套類數(shù)據(jù)成員,COM子對(duì)象 實(shí)現(xiàn)了I
37、Boat接口. intm_Ref ; / 用作主對(duì)象的引用計(jì)數(shù) int m_BoatRef; /用作COM子對(duì)象 IBoat接口的引用計(jì)數(shù) char * m_pTonsOfMemForBoat; ./其他數(shù)據(jù)成員. 主對(duì)象的QueryInterface函數(shù)如下:,30,HRESULT BoatPlane:QueryInterface(const IID IPlane接口調(diào)用QueryInterface當(dāng)然是上述函數(shù),而IBoat的QueryInterface也應(yīng)該委托給它. 其他的函數(shù)的實(shí)現(xiàn)方式都與以前一樣. 客戶得到的IPlane接口是指向COM主對(duì)象的,客戶得到的IBoat接口是指向Boa
38、tPlane的成員變量m_xBoat即COM子對(duì)象的.接口的轉(zhuǎn)換過程請(qǐng)自行分析.,31,6.復(fù)合技術(shù)的表格驅(qū)動(dòng),其實(shí),我們已經(jīng)看到,使用繼承或使用復(fù)合來實(shí)現(xiàn)接口的差別并不大, 這兩種技術(shù)可以和平共處. 我們?cè)帽砀耱?qū)動(dòng)的方式實(shí)現(xiàn)了多重繼承方式下的COM接口的QueryInterface函數(shù).實(shí)際上,對(duì)于復(fù)合技術(shù),也可以使用表格驅(qū)動(dòng)的方式來實(shí)現(xiàn)它的QueryInterface函數(shù). 表格驅(qū)動(dòng)的本質(zhì)是把接口的IID號(hào)和COM對(duì)象到這個(gè)IID號(hào)所代表的接口類子對(duì)象“subobject”的偏移聯(lián)系起來.注意,我們的QueryInterface函數(shù)是在COM對(duì)象中實(shí)現(xiàn)的.而接口調(diào)用此函數(shù),要么是通過虛
39、函數(shù)改寫的方式,要么是通過一個(gè)指向父類指針的方式進(jìn)行委托調(diào)用. 無論哪種方式都是調(diào)用的COM對(duì)象定義的QueryInterface, 從這里(COM對(duì)象的this)出發(fā), 如果有從COM對(duì)象到接口類子對(duì)象的偏移.(注意,這個(gè)偏移,有可能是從子類到基類的偏移,也有可能是從父類到嵌套子類的基類的偏移),我們當(dāng)然可以得到接口類子對(duì)象.也就是接口指針. 上一段話的意思是說, 為了實(shí)現(xiàn)支持復(fù)合的表格驅(qū)動(dòng),只需要添加使用復(fù)合技術(shù)的接口的接口表項(xiàng), 而QueryInterface函數(shù),以及InterfaceTableQueryInterface函數(shù),(見前面章節(jié)) 都不用更改.,32,定義如下的宏: #de
40、fine COMPOSITE_OFFSET(ClassName, BaseName, MemberType, MemberName) (DWORD(static_cast(reinterpret_cast(0 x10000000 + offsetof(ClassName, MemberName) - 0 x10000000) reinterpret_cast(0 x10000000 + offsetof(ClassName, MemberName) 0 x10000000加上父類到嵌套成員的偏移轉(zhuǎn)換為嵌套類 (static_cast(嵌套類指針)把嵌套類指針轉(zhuǎn)換為它的基類指針即接口指針.這里由
41、編譯器進(jìn)行計(jì)算偏移. (DWORD)接口指針- 0 x10000000 接口指針的絕對(duì)數(shù)字減去父類的起始地址0 x10000000. 得到父類到嵌套類的基類的偏移.,0 x1000,0 x1012,ClassName 父類,MemberType 嵌套類 第一步,偏移 第三步,BaseName 嵌套類的基類 即接口類 第二步,33,#define BEGIN_INTERFACE_TABLE(ClassName) typedef ClassName _InterfaceTableClassName; #define IMPLEMENTS_INTERFACE_WITH_COMPOSITE(Reque
42、stedItfName, DataMemberType, DataMemberName) STDMETHODIMP QueryInterface(REFIID riid, void *ppv) return This()-QueryInterface(riid, ppv); STDMETHODIMP_(ULONG) AddRef(void) return This()-AddRef(); STDMETHODIMP_(ULONG) Release(void) return This()-Release(); 以上宏為復(fù)合的接口實(shí)現(xiàn)IUnknown定義的函數(shù)QueryInterface AddRe
43、f 和Release,35,使用這些宏來完成上一節(jié)的水上飛機(jī)(兩種不同方式實(shí)現(xiàn)的接口): class BoatPlane: public IPlane /IPlane接口通過繼承的方式實(shí)現(xiàn) public: struct XBoat:public IBoat /IBoat接口使用復(fù)合方式實(shí)現(xiàn) IMPLEMENT_COMPOSITE_UNKNOWN (BoatPlane,XBoat,m_xBoat); /復(fù)合接口實(shí)現(xiàn)IUnknown接口 HRESULT _stdcall Swim(); HRESULT _stdcall GetMaxSpeed(long *pV); m_xBoat; /嵌套類數(shù)據(jù)成
44、員,實(shí)現(xiàn)了IBoat接口. IMPLEMENT_UNKNOWN(BoatPlane) / IPlane接口實(shí)現(xiàn)IUnknown BEGIN_INTERFACE_TABLE(BoatPlane) IMPLEMENT_INTERFACES(IPlane) IMPLEMENT_INTERFACES_AS(IVehicle,IPlane)/如果請(qǐng)求IVehicle則返回IPlane,當(dāng)然也可以返回IBoat. IMPLEMENTS_INTERFACE_WITH_COMPOSITE (IBoat,XBoat,m_xBoat) /把復(fù)合接口加入到接口表中 END_INTERFACE_TABLE() HRE
45、SULT _stdcall Fly(); /IPlane的函數(shù) HRESULT _stdcall GetMaxSpeed(long *pV);/IVehicle的函數(shù) char * m_pTonsOfMemForBoat; /注意,這里沒有實(shí)現(xiàn)針對(duì)接口的引用技術(shù),如果要實(shí)現(xiàn)不能使用IMPLEMENT_COMPOSITE_UNKNOWN宏,36,7.動(dòng)態(tài)復(fù)合接口,事實(shí)上,除了使用單獨(dú)的引用計(jì)數(shù)外,復(fù)合接口還可以進(jìn)一步優(yōu)化.一個(gè)COM子對(duì)象直到客戶請(qǐng)求它的復(fù)合接口的時(shí)候才真正創(chuàng)建,可以進(jìn)一步優(yōu)化資源. 在特定的場(chǎng)合下有其重要的用途.這樣的接口稱為動(dòng)態(tài)復(fù)合接口,也稱為tearoff接口. class
46、 BoatPlane: public IPlane /IPlane接口通過繼承的方式實(shí)現(xiàn) public: BoatPlane(void): m_Ref(0) HRESULT _stdcall QueryInterface(REFIID iid,void *ppv); ULONG _stdcall AddRef(); ULONG _stdcall Release(); HRESULT _stdcall Fly(); /IVehicle的函數(shù) HRESULT _stdcall GetMaxSpeed(long *pV); intm_Ref ; / 用作主對(duì)象的引用計(jì)數(shù) char * m_pTons
47、OfMemForBoat; ./其他數(shù)據(jù)成員. IBoat * m_pBoat; /指針用來保存動(dòng)態(tài)創(chuàng)建的接口,37,struct XBoat:public:IBoat /嵌套類實(shí)現(xiàn)了IBoat接口. XBoat(BoatPlane *pThis); /構(gòu)造函數(shù) int m_BoatRef; / 自己負(fù)責(zé)自己的計(jì)數(shù) BoatPlane *m_pThis; /指向父類的指針 inline BoatPlane *This()return m_pThis; /返回指向父類的指針 HRESULT _stdcall QueryInterface(REFIID iid,void *ppv); ULONG
48、_stdcall AddRef(); ULONG _stdcall Release(); HRESULT _stdcall Swim(); HRESULT _stdcall GetMaxSpeed(long *pV); /注意只定義了嵌套類,沒有數(shù)據(jù)成員. 即只有COM子對(duì)象的類定義, 而沒有COM子對(duì)象本身. 主對(duì)象第一次接收到IBoat接口請(qǐng)求時(shí),動(dòng)態(tài)地創(chuàng)建一個(gè)新的復(fù)合接口. 這個(gè)工作當(dāng)然是在QueryInterface中完成的:,38,HRESULT BoatPlane:QueryInterface(const IID /子對(duì)象銷毀時(shí)也通知主對(duì)象.,39,動(dòng)態(tài)復(fù)合接口維護(hù)自己的引用計(jì)數(shù).
49、 ULONG BoatPlane:XBoat: AddRef() return =InterLockedIncrement( /內(nèi)部的引用計(jì)數(shù) ,40,8.MFC對(duì)COM的支持,MFC中使用CcmdTargert類采用了與以上示例類似的方法用復(fù)合嵌套類的方式提供對(duì)COM的支持。它使用接口映射表的機(jī)制來實(shí)現(xiàn)COM接口之間的查詢轉(zhuǎn)換功能。 接口映射表記錄了每一個(gè)嵌套類的接口ID與其所代表的接口虛表與父類指針的偏移量。因?yàn)榍短最惖淖饔糜蛟诟割愖饔糜虻膬?nèi)部,嵌套類不能直接訪問父類的成員,CCmdTarget利用嵌套類成員與父類this指針的偏移量使得嵌套類可以計(jì)算出父類的this指針,從而訪問父類的成
50、員。這種處理方法我們已經(jīng)在前面的例子中看到多次了. 大部分乏味枯燥的操作都是使用宏替換的方式實(shí)現(xiàn)的. 這些宏的風(fēng)格與我們已經(jīng)見到的也相仿. MFC中大量使用宏來進(jìn)行各種基礎(chǔ)設(shè)施的構(gòu)造工作,比如消息映射,比如類型識(shí)別,永久性支持等等.這些樣式古怪的宏雖然功能強(qiáng)大,但是往往是初學(xué)者的攔路虎.,41,MFC使用了一組宏來簡(jiǎn)化操作 #define DECLARE_INTERFACE_MAP() private: static const AFX_INTERFACEMAP_ENTRY _interfaceEntries; protected: static AFX_DATA const AFX_INTE
51、RFACEMAP interfaceMap; static const AFX_INTERFACEMAP* PASCAL _GetBaseInterfaceMap(); virtual const AFX_INTERFACEMAP* GetInterfaceMap() const; 定義了靜態(tài)的數(shù)據(jù)成員_interfaceEntries 和interfaceMap,靜態(tài)成員函數(shù)_GetBaseInterfaceMap虛成員函數(shù)GetInterfaceMap。 其中:struct AFX_INTERFACEMAP_ENTRY const void* piid; / the interface i
52、d (IID) (NULL for aggregate) size_t nOffset; / offset of the interface vtable from m_unknown ; struct AFX_INTERFACEMAP #ifdef _AFXDLL const AFX_INTERFACEMAP* (PASCAL* pfnGetBaseMap)(); #else const AFX_INTERFACEMAP* pBaseMap; /指向基類的接口映射 #endif const AFX_INTERFACEMAP_ENTRY* pEntry; / map for this clas
53、s ;,42,DECLARE_INTERFACE_MAP()定義了一張接口表interfaceMap,表中第一項(xiàng)指向其基類的接口表pBaseMap,第二項(xiàng)指向接口表的入口pEntry,而接口表中的每一項(xiàng)包括接口的IID和接口vtable和父類指針之間的偏移。 使用這種結(jié)構(gòu),用戶可以從CcmdTargert類派生自己的COM類,被派生的類可以繼承其父類實(shí)現(xiàn)的接口,通過接口表結(jié)構(gòu)的第一項(xiàng)獲取其基類接口,從而形成接口鏈。 接口表的實(shí)現(xiàn)過程也是利用一組宏,以字典對(duì)象為例: BEGIN_INTERFACE_MAP(CDictionaryObj, CCmdTarget) INTERFACE_PART(CD
54、ictionaryObj, IID_Dictionary, Dictionary) INTERFACE_PART(CDictionaryObj, IID_SpellCheck, SpellCheck) END_INTERFACE_MAP(),43,#define BEGIN_INTERFACE_MAP(theClass, theBase) const AFX_INTERFACEMAP* PASCAL theClass:_GetBaseInterfaceMap() return ,44,BEGIN_INTERFACE_MAP給出了靜態(tài)函數(shù)_GetBaseInterfaceMap和虛成員函數(shù)Get
55、InterfaceMap的實(shí)現(xiàn)過程,它們分別返回基類和自己的接口表,并且定義了靜態(tài)數(shù)據(jù)成員interfaceMap接口表和_interfaceEntries接口表項(xiàng)。 interfaceMap的第一個(gè)成員使用靜態(tài)函數(shù)指針_GetBaseInterfaceMap填入,第二個(gè)成員使用接口表項(xiàng)的第一項(xiàng)作為其入口_interfaceEntries0。 INTERFACE_PART定義每一個(gè)接口項(xiàng),END_INTERFACE_MAP() 在映射表中放入一個(gè)結(jié)束項(xiàng)。 注意INTERFACE_PART在定義接口項(xiàng)時(shí)使用了宏offset來計(jì)算成員與父類指針的偏移量: #define offsetof(s,m)
56、 (size_t) public: BEGIN_INTERFACE_PART(Dictionary, IDictionary) /定義接口 INIT_INTERFACE_PART(CDictionary, Dictionary) STDMETHOD_(BOOL, Initialize)(); STDMETHOD_(BOOL, LoadLibrary)(LPOLESTR); . END_INTERFACE_PART_STATIC(Dictionary) BEGIN_INTERFACE_PART(SpellCheck, ISpellCheck) INIT_INTERFACE_PART(CDicti
57、onary, SpellCheck) STDMETHOD_(BOOL, CheckWord)(LPOLESTR, LPOLESTR *); END_INTERFACE_PART_STATIC(SpellCheck) protected: DECLARE_INTERFACE_MAP() /定義了接口映射表。 private : structDictWord *m_pData; . ;,46,#define BEGIN_INTERFACE_PART(localClass, baseClass) class X#localClass : public baseClass public: STDMET
58、HOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); STDMETHOD(QueryInterface)(REFIID iid, LPVOID* ppvObj); 宏BEGIN_INTERFACE_PART封裝了嵌套類的定義。同時(shí)聲明了接口的前三個(gè)成員函數(shù)AddRef,Release和QueryInterface。 #define INIT_INTERFACE_PART(theClass, localClass) size_t m_nOffset; INIT_INTERFACE_PART_DERIVE(theClass, localClass) #define INIT_INTERFACE_PART_DERIVE(theClass, localClass) X#localClass() m_nOffset = offsetof(theClass, m_x#localClass); 宏INIT_INTERFACE_PART定義了記錄偏移量的數(shù)據(jù)成員m_nOffset,這個(gè)成員也是通過宏offset來完成的。 #
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 江蘇房屋維修管理辦法
- 發(fā)票違法檢舉管理辦法
- 職業(yè)規(guī)劃與就業(yè)指導(dǎo)教程
- 農(nóng)家栽培紅薯管理辦法
- 村級(jí)項(xiàng)目立項(xiàng)管理辦法
- 道路改造與混凝土管鋪設(shè)施工方案設(shè)計(jì)及舊路面拆除策略探討
- 醫(yī)用織物清洗管理辦法
- 杭州交警頭盔管理辦法
- 加強(qiáng)資金安全管理措施
- 安全工作周例會(huì)
- 來料檢驗(yàn)規(guī)范
- 電鍍產(chǎn)品檢驗(yàn)記錄
- 2023-2024學(xué)年遼寧省大連市小學(xué)語文五年級(jí)期末評(píng)估試卷附參考答案和詳細(xì)解析
- 2023年小學(xué)數(shù)學(xué)必背定義和公式
- 2023年四川省宜賓市全科醫(yī)學(xué)專業(yè)實(shí)踐技能測(cè)試卷(含答案)
- 電梯井道腳手架施工方案
- 興平市生活垃圾焚燒發(fā)電項(xiàng)目環(huán)評(píng)報(bào)告
- 主令電器(課用)課件
- 湘少版英語六年級(jí)下冊(cè)全冊(cè)教案
- 湖南省長(zhǎng)郡中學(xué)“澄池”杯數(shù)學(xué)競(jìng)賽初賽試題(掃描版含答案)
- 消防系統(tǒng)施工總進(jìn)度計(jì)劃
評(píng)論
0/150
提交評(píng)論