注冊 登錄
Office中國論壇/Access中國論壇 返回首頁

ganlinlao的個人空間 http://m.mzhfr.cn/?230471 [收藏] [復制] [分享] [RSS]

日志

Freebasic編寫com組件之超高難度地方———實現(xiàn)聚合

熱度 1已有 3175 次閱讀2015-6-4 16:44 |個人分類:FreeBasic| Freebasic, freebasic編寫com, Freebasic

    正常情況下,無論你使用多少年的VBA,無論你使用vb寫過多少類,寫過多少ocx或dll。應該是很少聽過com的聚合技術。事實上在Freebasic中編寫com組件也不是一件容易的事,而要實現(xiàn)com的聚合只能說難上加難了,在這里先記下,以待以后不時的回味和細細揣摩。

一、基礎知識和概念圖:


COM不支持實現(xiàn)繼承的原因在于這種繼承方式將使得一個對象的實現(xiàn)同另外一個對象的實現(xiàn)緊緊地關聯(lián)起來。在這種情況下,當基類的實現(xiàn)被修改后,派生類將無法正常運行而必須被修改。(這也是為什么vb沒有實現(xiàn)繼承,這是com決定它無法成為真正的oop)因此,為了保證以組件的修改不會影響應用程序的正常運行,COM并不支持實現(xiàn)繼承。但我們可以用組件包容來完全模擬實現(xiàn)繼承


聚合的概念
       聚合源自組件重用。當有兩個組件A和B,他們分別實現(xiàn)了自己的接口IA和IB。如果有一 個客戶程序創(chuàng)建了A對象使得自己可以調用IA的方法,但同時又想獲得IB的接口,調用IB的方法。這時候有兩種做法:一種是客戶程序創(chuàng)建B對象,還有一種 方法是A組件內部創(chuàng)建B組件,然后客戶通過某種途徑調用B的接口方法。
第一種方法,使得客戶必須知道有獨立的B組件的存在,第二種方法客戶可以認為只有一個組件A,組件A實現(xiàn)了兩個接口IA和IB。第二種方法可以制造出一種假象,讓客戶程序編寫更加簡單。從組件A如何管理組件B的方法上,第二種方法還可以分為兩種:包容和聚合
包容很簡單,如果組件IB接口擁有一個方法F(),那么A組件就要實現(xiàn)一個自己的 IBEx接口,并實現(xiàn)IBEx::F( )方法,內部調用IB::F()方法。這樣,客戶也就可以通過調用IBEx::F()來調用IB::F。在這種情況下,客戶只知道有IA和IBEx接口, 不知道還存在另一個B組件和IB接口。IBEx::F()增加一些代碼從而修改IB::F()方法的功能,甚至可以完全丟棄IB::F()方法。
      聚合通常用于IB接口的功能完全不需要做任何的修改,就可以直接交給用戶使用的情況。這 時候,如果IB接口的方法很多,包容就顯得很笨拙。因為它不得不對每一個方法作一次包裝,盡管什么都不做。COM+對象池就是通過聚合我們的組件,來把我 們組件的接口暴露給客戶的。聚合方式下,A組件直接將IB接口交給客戶,客戶就可以調用,但是客戶仍然以為是A組件實現(xiàn)了IB接口。
包容和聚合實際上是使用一個組件實現(xiàn)另外一個組件的一種技術。在包容的情況下,外部組件將包含內部組件,而在聚合的情況下,則稱外部組件聚合內部組件。

       包容是在接口級別完成的。外部組件包含指向內部組件接口的指針。此時外部組件只是內部組件的一個客戶,它將使用內部組件的接口來實現(xiàn)它自己的接口


外部組件也可以通過將調用轉發(fā)給內部組件的方法重新實現(xiàn)內部組件所支持的某個接口。并且外部組件還可以在內部組件代碼的前后加一些代碼以對接口進行改造




       客戶程序只知道A組件而不知道B組件,并且認為A組件實現(xiàn)了IA和IB接口。因此,當客戶創(chuàng)建了A組件(通過CoCreateInstance函數(shù)),獲取到IUnknown接口時,應該獲得的是A組件實現(xiàn)的IUnknown接口。      問 題在于B組件有自己的IUnknown的接口實現(xiàn),如果B組件還是采用一般的方法實現(xiàn)IUnknown接口的話,當客戶調用 IB::QueryInterface函數(shù),就不會得到IA接口,這當然是不允許的。所以一個支持聚合的組件,它的IUnknown實現(xiàn)必然要有別于普通 組件。
       B組件應該擁有一個成員變量IUnknown* m_pUnknownOuter;該指針可以指向A組件的IUnknown接口。當客戶調用IB::QueryInterface請求時,如果IB接口已 經(jīng)被聚合了,就調用m_pUnknownOuter->QueryInterface方法,這實際上就是調用了A組件的 QueryInterface方法。那么,m_pUnknownOuter指針是什么時候被初始化的呢?CoCreateInstance函數(shù)和 IClassFactory::CreateInstance方法都接受一個IUnknown參數(shù)。如果A組件內部想聚合B組件的IB接口,他就會將自己 的IUnknown指針傳遞進去,如果A組件并不想聚合B組件,那么簡單的傳遞一個NULL就行了。
       B組件可以根據(jù)m_pUnknownOuter是否為NULL,來判斷是否被聚合。
       B組件實現(xiàn)的具體步驟如下:
1) 聲明一個INondelegationUnknown接口,該接口擁有IUnknown接口一樣的純虛函數(shù),當然函數(shù)名前面均加上Nondelegation前綴。
2) CB類(假設CB類為組件類)繼承并實現(xiàn)INondelegationUnknown接口,實現(xiàn)代碼和普通組件的IUnknown一樣,這用于非聚合的情況下。
3) CB類的構 造函數(shù)接受IUnknown指針,如果傳遞進來的是NULL,說明B組件并不被用作聚合。m_pUnknownOuter變量就指向B組件自身的 IUnknown接口。如果傳遞進來的不是NULL,說明被用作聚合。m_pUnknownOuter變量值等于參數(shù)值,指向外部組件的IUnknown 接口指針。
4) CB類也要 繼承并實現(xiàn)IUnknown接口。這個接口的QueryInterface函數(shù)實現(xiàn)只是調用 m_pUnknownOuter->QueryInterfac方法。m_pUnknownOuter究竟代表什么取決于創(chuàng)建組件時是否傳遞了外部 組件的IUnknown指針。
5) B組件的類 廠的CreateInstance方法內部創(chuàng)建CB類時,將IUnknown* pUnkownOuter參數(shù)傳遞給構造函數(shù)。創(chuàng)建成功后,調用B組件自身的(而不是外部的)NondelegationQueryInterface方 法,將自身的INondelegationUnknown傳遞出去給組件A。

       外部組件A要創(chuàng)建組件B的實例,并保存B組件的INondelegationUnknown接口指 針。該接口指針是通過上一節(jié)5)由B的類廠返回的。m_pUnknownInner變量保存了B接口的INondelegationUnknown接口指 針。外部組件調用CoCreateInstance函數(shù)創(chuàng)建聚合組件時,iid必須等于IID_IUnknown。
       外部組件要修改自己的QueryInterface,當客戶程序請求IB接口的時候,將調用m_pUnknownInner->QueryInterface方法。
       外部組件可以通過QueryInterface的代碼來控制是否聚合B組件所有的接口。如果不想聚合B組件實現(xiàn)的IC接口?梢栽谳斎?yún)?shù)iid等于IID_IC的時候,返回E_NOINTERFACE。
       聚合B組件的所有接口的方式稱為盲聚合。盲聚合的缺點是:如果B組件實現(xiàn)了IPersist接口,客 戶調用IPersist::GetClassID方法時,獲得的是CLSID_CB,這就暴露了內部的B組件;還有就是B組件實現(xiàn)的接口不了解A組件的狀 態(tài),如果B組件實現(xiàn)了ISave接口,客戶調用了它,卻不能正確完成保存組件狀態(tài)的功能。所以,一般我們要避免使用盲聚合,而要有選擇的聚合。
       外部組件還有一個重要的任務就是控制內部組件的生命周期。因為內部組件擁有外部組件的 IUnknown指針,這時候當調用內部組件的AddRef/Release,改變的是外部組件的引用計數(shù)。所以如果需要減少引用計數(shù),應該調用 pUnknowOuter(pUnknowOuter其實就是this指針的強制轉換)->Release( )。CA類析構時,要采用以下特殊的做法保證不會被過早的析構和多次釋放。
       m_cRef=1;
       IUnknown* pUnknownOuter = this;
       pUnknownOuter->AddRef( );
       m_pIB->Release( );
需要注意的是CA的析構函數(shù)是在A組件自身的引用計數(shù)為0時才會被調用,如果沒有m_cRef=1這行代碼,m_pIB->Release( )會導致析構函數(shù)再次被調用。
`      最后,析溝函數(shù)將釋放組件B。通過調用B的INondelegationUnknown::Release( )方法。
代碼如下:
       if(m_pUnknownInner!=NULL)
{
       m_pUnknownInner->Release ( );
}

******************************************
Freebasic的代碼例子:
這是一個comsample.dll的例子。這個comSample里面主要有兩個組件,cMath和cSquare這兩個com組件。其中cMath是聚合者,而cSquare是被聚合者。(如果從com的使用角度看,主動聚合者本質上是com的client端,被聚合者本質是com的server端,這里將通過類工廠,讓server端把內部的方法暴露出來,供client端調用)
第一步,我們先看一下cMath的頭文件BI是怎么聲明的(注:tlb是vb的頭文件,沒有tlb的com組件vb無法調用,但FB照樣可以使用沒用tlb的com組件,這個例子就是沒有額外寫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                  '導出IID,供外部調用

Type CMath extends IMath               '定義類cMath

public:
    ' 實現(xiàn) IUnknown Interface.
    Declare virtual Function QueryInterface(iid As  REFIID , ppv As LPVOID Ptr ) As HRESULT                     '這三個涵數(shù)都是以virtual虛函數(shù)形式聲明,接口在FB中都是這樣
    Declare virtual Function  AddRef() As ULong
    Declare virtual Function  Release()As ULong

    '實現(xiàn) IMath Interface.
    Declare virtual Sub  SumSquare(Val1  As Integer,  Val2  As Integer, pResult As Integer ptr)
   
    Declare Constructor     '聲明構造函數(shù)
     Declare Destructor      '析構
     
     m_pISquare  As  ISquare Ptr       '聲明被聚合者Csquare的iSquare的接口

private:                      '內部成員數(shù)據(jù)
    m_cRef  As Long      '這是用來記錄引用的狀態(tài)
End Type


發(fā)表評論 評論 (1 個評論)

回復 t小寶 2015-6-14 14:47
路過看看

facelist doodle 涂鴉板

您需要登錄后才可以評論 登錄 | 注冊

QQ|站長郵箱|小黑屋|手機版|Office中國/Access中國 ( 粵ICP備10043721號-1 )  

GMT+8, 2025-7-13 03:17 , Processed in 0.075701 second(s), 18 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

返回頂部