Office中國(guó)論壇/Access中國(guó)論壇

 找回密碼
 注冊(cè)

QQ登錄

只需一步,快速開始

tag 標(biāo)簽: freebasic編寫com

相關(guān)帖子

版塊 作者 回復(fù)/查看 最后發(fā)表

沒(méi)有相關(guān)內(nèi)容

相關(guān)日志

分享 Freebasic編寫com組件之超高難度地方———實(shí)現(xiàn)聚合
熱度 1 ganlinlao 2015-6-4 16:44
正常情況下,無(wú)論你使用多少年的VBA,無(wú)論你使用vb寫過(guò)多少類,寫過(guò)多少ocx或dll。應(yīng)該是很少聽過(guò)com的聚合技術(shù)。事實(shí)上在Freebasic中編寫com組件也不是一件容易的事,而要實(shí)現(xiàn)com的聚合只能說(shuō)難上加難了,在這里先記下,以待以后不時(shí)的回味和細(xì)細(xì)揣摩。 一、基礎(chǔ)知識(shí)和概念圖: COM 不支持實(shí)現(xiàn)繼承的原因在于這種繼承方式將使得一個(gè)對(duì)象的實(shí)現(xiàn)同另外一個(gè)對(duì)象的實(shí)現(xiàn)緊緊地關(guān)聯(lián)起來(lái)。 在這種情況下,當(dāng)基類的實(shí)現(xiàn)被修改后,派生類將無(wú)法正常運(yùn)行而必須被修改。 (這也是為什么vb沒(méi)有實(shí)現(xiàn)繼承,這是com決定它無(wú)法成為真正的oop)因此,為了保證以組件的修改不會(huì)影響應(yīng)用程序的正常運(yùn)行, COM 并不支持實(shí)現(xiàn)繼承。但我們可以用 組件 包容 來(lái)完全 模擬實(shí)現(xiàn)繼承 聚合的概念 聚合源自組件重用。當(dāng)有兩個(gè)組件A和B,他們分別實(shí)現(xiàn)了自己的接口IA和IB。如果有一 個(gè)客戶程序創(chuàng)建了A對(duì)象使得自己可以調(diào)用IA的方法,但同時(shí)又想獲得IB的接口,調(diào)用IB的方法。這時(shí)候有兩種做法:一種是客戶程序創(chuàng)建B對(duì)象,還有一種 方法是A組件內(nèi)部創(chuàng)建B組件,然后客戶通過(guò)某種途徑調(diào)用B的接口方法。 第一種方法,使得客戶必須知道有獨(dú)立的B組件的存在,第二種方法客戶可以認(rèn)為只有一個(gè)組件A,組件A實(shí)現(xiàn)了兩個(gè)接口IA和IB。第二種方法可以制造出一種假象,讓客戶程序編寫更加簡(jiǎn)單。從組件A如何管理組件B的方法上,第二種方法還可以分為兩種: 包容和聚合 。 包容很簡(jiǎn)單,如果組件IB接口擁有一個(gè)方法F(),那么A組件就要實(shí)現(xiàn)一個(gè)自己的 IBEx接口,并實(shí)現(xiàn)IBEx::F( )方法,內(nèi)部調(diào)用IB::F()方法。這樣,客戶也就可以通過(guò)調(diào)用IBEx::F()來(lái)調(diào)用IB::F。在這種情況下,客戶只知道有IA和IBEx接口, 不知道還存在另一個(gè)B組件和IB接口。IBEx::F()增加一些代碼從而修改IB::F()方法的功能,甚至可以完全丟棄IB::F()方法。 聚合通常用于IB接口的功能完全不需要做任何的修改,就可以直接交給用戶使用的情況。這 時(shí)候,如果IB接口的方法很多,包容就顯得很笨拙。因?yàn)樗坏貌粚?duì)每一個(gè)方法作一次包裝,盡管什么都不做。COM+對(duì)象池就是通過(guò)聚合我們的組件,來(lái)把我 們組件的接口暴露給客戶的。聚合方式下,A組件直接將IB接口交給客戶,客戶就可以調(diào)用,但是客戶仍然以為是A組件實(shí)現(xiàn)了IB接口。 包容和聚合實(shí)際上是使用一個(gè)組件實(shí)現(xiàn)另外一個(gè)組件的一種技術(shù)。 在包容的情況下,外部組件將包含 內(nèi)部組件 ,而在聚合的情況下,則稱外部組件聚合 內(nèi)部組件 。 包容是在接口級(jí)別完成的。外部組件包含指向內(nèi)部組件接口的指針。此時(shí)外部組件只是內(nèi)部組件的一個(gè)客戶,它將使用內(nèi)部組件的接口來(lái)實(shí)現(xiàn)它自己的接口 外部組件也可以通過(guò)將調(diào)用轉(zhuǎn)發(fā)給內(nèi)部組件的方法重新實(shí)現(xiàn)內(nèi)部組件所支持的某個(gè)接口。并且外部組件還可以在內(nèi)部組件代碼的前后加一些代碼以對(duì)接口進(jìn)行改造 內(nèi)部組件的實(shí)現(xiàn) 客戶程序只知道A組件而不知道B組件,并且認(rèn)為A組件實(shí)現(xiàn)了IA和IB接口。因此,當(dāng)客戶創(chuàng)建了A組件(通過(guò)CoCreateInstance函數(shù)),獲取到IUnknown接口時(shí),應(yīng)該獲得的是A組件實(shí)現(xiàn)的IUnknown接口。 問(wèn) 題在于B組件有自己的IUnknown的接口實(shí)現(xiàn),如果B組件還是采用一般的方法實(shí)現(xiàn)IUnknown接口的話,當(dāng)客戶調(diào)用 IB::QueryInterface函數(shù),就不會(huì)得到IA接口,這當(dāng)然是不允許的。所以一個(gè)支持聚合的組件,它的IUnknown實(shí)現(xiàn)必然要有別于普通 組件。 B 組件應(yīng)該擁有一個(gè)成員變量IUnknown* m_pUnknownOuter;該指針可以指向A組件的IUnknown接口。當(dāng)客戶調(diào)用IB::QueryInterface請(qǐng)求時(shí),如果IB接口已 經(jīng)被聚合了,就調(diào)用m_pUnknownOuter-QueryInterface方法,這實(shí)際上就是調(diào)用了A組件的 QueryInterface方法。那么,m_pUnknownOuter指針是什么時(shí)候被初始化的呢?CoCreateInstance函數(shù)和 IClassFactory::CreateInstance方法都接受一個(gè)IUnknown參數(shù)。如果A組件內(nèi)部想聚合B組件的IB接口,他就會(huì)將自己 的IUnknown指針傳遞進(jìn)去,如果A組件并不想聚合B組件,那么簡(jiǎn)單的傳遞一個(gè)NULL就行了。 B 組件可以根據(jù)m_pUnknownOuter是否為NULL,來(lái)判斷是否被聚合。 B 組件實(shí)現(xiàn)的具體步驟如下: 1) 聲明一個(gè)INondelegationUnknown接口,該接口擁有IUnknown接口一樣的純虛函數(shù),當(dāng)然函數(shù)名前面均加上Nondelegation前綴。 2) CB類(假設(shè)CB類為組件類)繼承并實(shí)現(xiàn)INondelegationUnknown接口,實(shí)現(xiàn)代碼和普通組件的IUnknown一樣,這用于非聚合的情況下。 3) CB類的構(gòu) 造函數(shù)接受IUnknown指針,如果傳遞進(jìn)來(lái)的是NULL,說(shuō)明B組件并不被用作聚合。m_pUnknownOuter變量就指向B組件自身的 IUnknown接口。如果傳遞進(jìn)來(lái)的不是NULL,說(shuō)明被用作聚合。m_pUnknownOuter變量值等于參數(shù)值,指向外部組件的IUnknown 接口指針。 4) CB類也要 繼承并實(shí)現(xiàn)IUnknown接口。這個(gè)接口的QueryInterface函數(shù)實(shí)現(xiàn)只是調(diào)用 m_pUnknownOuter-QueryInterfac方法。m_pUnknownOuter究竟代表什么取決于創(chuàng)建組件時(shí)是否傳遞了外部 組件的IUnknown指針。 5) B組件的類 廠的CreateInstance方法內(nèi)部創(chuàng)建CB類時(shí),將IUnknown* pUnkownOuter參數(shù)傳遞給構(gòu)造函數(shù)。創(chuàng)建成功后,調(diào)用B組件自身的(而不是外部的)NondelegationQueryInterface方 法,將自身的INondelegationUnknown傳遞出去給組件A。 外部組件的實(shí)現(xiàn) 外部組件A要?jiǎng)?chuàng)建組件B的 實(shí)例,并保存B組件的INondelegationUnknown接口指 針。該接口指針是通過(guò)上一節(jié)5)由B的類廠返回的。m_pUnknownInner變量保存了B接口的INondelegationUnknown接口指 針。外部組件調(diào)用CoCreateInstance函數(shù)創(chuàng)建聚合組件時(shí),iid必須等于IID_IUnknown。 外部組件要修改自己的QueryInterface,當(dāng)客戶程序請(qǐng)求IB接口的時(shí)候,將調(diào)用m_pUnknownInner-QueryInterface方法。 外部組件可以通過(guò)QueryInterface的代碼來(lái)控制是否聚合B組件所有的接口。如果不想聚合B組件實(shí)現(xiàn)的IC接口?梢栽谳斎?yún)?shù)iid等于IID_IC的時(shí)候,返回E_NOINTERFACE。 聚合B組件的所有接口的方式稱為盲聚合。盲聚合的缺點(diǎn)是:如果B組件實(shí)現(xiàn)了IPersist接口,客 戶調(diào)用IPersist::GetClassID方法時(shí),獲得的是CLSID_CB,這就暴露了內(nèi)部的B組件;還有就是B組件實(shí)現(xiàn)的接口不了解A組件的狀 態(tài),如果B組件實(shí)現(xiàn)了ISave接口,客戶調(diào)用了它,卻不能正確完成保存組件狀態(tài)的功能。所以,一般我們要避免使用盲聚合,而要有選擇的聚合。 外部組件還有一個(gè)重要的任務(wù)就是控制內(nèi)部組件的生命周期。因?yàn)閮?nèi)部組件擁有外部組件的 IUnknown指針,這時(shí)候當(dāng)調(diào)用內(nèi)部組件的AddRef/Release,改變的是外部組件的引用計(jì)數(shù)。所以如果需要減少引用計(jì)數(shù),應(yīng)該調(diào)用 pUnknowOuter(pUnknowOuter其實(shí)就是this指針的強(qiáng)制轉(zhuǎn)換)-Release( )。CA類析構(gòu)時(shí),要采用以下特殊的做法保證不會(huì)被過(guò)早的析構(gòu)和多次釋放。 m_cRef=1; IUnknown* pUnknownOuter = this; pUnknownOuter-AddRef( ); m_pIB-Release( ); 需要注意的是CA的析構(gòu)函數(shù)是在A組件自身的引用計(jì)數(shù)為0時(shí)才會(huì)被調(diào)用,如果沒(méi)有m_cRef=1這行代碼,m_pIB-Release( )會(huì)導(dǎo)致析構(gòu)函數(shù)再次被調(diào)用。 ` 最后,析溝函數(shù)將釋放組件B。通過(guò)調(diào)用B的INondelegationUnknown::Release( )方法。 代碼如下: if(m_pUnknownInner!=NULL) { m_pUnknownInner-Release ( ); } ****************************************** Freebasic的代碼例子: 這是一個(gè)comsample.dll的例子。這個(gè)comSample里面主要有兩個(gè)組件,cMath和cSquare這兩個(gè)com組件。其中cMath是聚合者,而cSquare是被聚合者。(如果從com的使用角度看,主動(dòng)聚合者本質(zhì)上是com的client端,被聚合者本質(zhì)是com的server端,這里將通過(guò)類工廠,讓server端把內(nèi)部的方法暴露出來(lái),供client端調(diào)用) 第一步,我們先看一下cMath的頭文件BI是怎么聲明的(注:tlb是vb的頭文件,沒(méi)有tlb的com組件vb無(wú)法調(diào)用,但FB照樣可以使用沒(méi)用tlb的com組件,這個(gè)例子就是沒(méi)有額外寫tlb) cMath的BI聲明: #include Once "Win/objbase.bi" '引用 #include "CSquare.bi" '引用cSquare的頭文件 Type IMath extends IUnknown 'Imath接口聲明為iunknown接口 Declare abstract Sub SumSquare(Val1 As Integer, Val2 As Integer, pResult As Integer ptr) '聲明靜態(tài)方法 End Type extern IID_IMath As IID '導(dǎo)出IID,供外部調(diào)用 Type CMath extends IMath '定義類cMath public: ' 實(shí)現(xiàn) IUnknown Interface. Declare virtual Function QueryInterface(iid As REFIID , ppv As LPVOID Ptr ) As HRESULT '這三個(gè)涵數(shù)都是以virtual虛函數(shù)形式聲明,接口在FB中都是這樣 Declare virtual Function AddRef() As ULong Declare virtual Function Release()As ULong '實(shí)現(xiàn) IMath Interface. Declare virtual Sub SumSquare(Val1 As Integer, Val2 As Integer, pResult As Integer ptr) Declare Constructor '聲明構(gòu)造函數(shù) Declare Destructor '析構(gòu) m_pISquare As ISquare Ptr '聲明被聚合者Csquare的iSquare的接口 private: '內(nèi)部成員數(shù)據(jù) m_cRef As Long '這是用來(lái)記錄引用的狀態(tài) End Type
個(gè)人分類: FreeBasic|3176 次閱讀|1 個(gè)評(píng)論

QQ|站長(zhǎng)郵箱|小黑屋|手機(jī)版|Office中國(guó)/Access中國(guó) ( 粵ICP備10043721號(hào)-1 )  

GMT+8, 2025-7-13 07:34 , Processed in 0.116122 second(s), 13 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

返回頂部