基於構件複用的軟體方法與COM支援
張顥
華中理工大學電腦學院
摘要 作為研究的熱點,近年來提出構件模型層出不窮.出於相互之間互通性的考慮,必須具備一個公用的構件
底層架構.分析各種實際的因素,COM是一個可行的選擇.本文討論了COM規範對組件複用的支援及其發展前景,提
出了基於COM的複用思想:以介面的標準化推動服務的標準化,為複用軟體的開發和使用建立規範.
關鍵詞 軟體複用,COM,分布對象,組件,物件導向
構件複用作為一種現實有效軟體開發方法正受到越來越多的重視和研究。近年來,隨著DNA,中介軟體等三層體
繫結構和分布式對象的興起,為基於可複用構件的軟體體繫結構賦予了更大的內涵。從而使我們在評價構件複
用的意義時,不應只局限於開發週期的縮短,軟體品質的可靠提高,更要認識到構件組裝式的開發在系統維護(如
版本更新,功能增刪等),分散式運算等方面所帶來的極大便利。由於維護階段占軟體開發週期總成本的67%左右
,因此構件重用對整個軟體產業的重要意義就不言而喻了.從維護和分布計算的角度考慮,構件複用就是要實現
"隨插即用".
傳統的物件導向程式設計語言中,基於繼承機制的類的複用,只是原始碼級的重用,在原始碼不可得的情況下(構件產
業化的發展趨勢下,這種情況很普遍),就變得毫無意義了.更為重要的是,聯編以後,類構件就只是一個邏輯上的
虛幻的概念了,不會給將來可能的處理帶來任何方便.
MicroSoft自90年代初就進行了COM的開發,曆經OLE1,VBX組件,OLE2,ActiveX,COM+的不斷完善,現已成為一個相
當成熟的組件模型,對構件複用提供了有力的底層支援.
一 COM對象的封裝
COM是一個二進位的標準,它詳細規定了一個COM組件所應具有的記憶體結構.COM對象間的互動完全基於對此記憶體
結構的操作.因此可以在很大程度上忽略不同程式設計語言,應用環境之間的差別,解決了重新編譯重新發行的問題
.二進位代碼級的相容性要求作業系統的支援,但是COM描述對象串連的方法與傳統的API式共用系統服務不同.
串連建立後,COM底層庫不再被需要,停止耗用系統資源,與API相比,作業系統必須一直管理組件之間的串連.
COM用介面的概念對組件的功能屬性進行完全的封裝.與組件的通訊必須通過介面進行.介面不僅是一個邏輯上
的概念,而且也存在著與之相對應的實體記憶體結構(VTABLE).一個對象可以對應多個介面,一個介面也可以由多
個對象所實現,表現出靈活的多態性.同時也為版本管理提供了方便.當使用新版本的組件替換老版本時,只要該
組件實現了舊版本的介面(通過包容,彙總等手段),就保證了其與原用軟體系統的相容.同時新增功能(新的介面
)又可被自然的使用.
介面完全封裝了內部功能,屬性的具體實現,使得COM對象對外表現為"黑盒"結構,完全吻合物件導向系統所要求
的"強內聚性".但由於對介面的過多強調,COM組件一般不具備廣泛提倡的"弱耦合性"的特點.不過,微軟一向重
視介面的不變性,試圖用介面的標準化推動服務的標準化,以介面為基礎為軟體複用建立實用的架構.其實
ActiveX技術規範中的相當大一部分都是通過定義標準的介面及其相互之間的邏輯關係來確定的.
二 自動化
在考慮調用介面內成員函數的具體實現時,就會發現由於組件的特殊性,這種實現需要與通常完全不同的規範.
需要解決的問題有:來源程式中如何標識一個組件(物理上,就是一段已經存在了的,具有一定功能的二進位代碼)
,對於組件內特定函數的調用,編譯器將如何做出處理,如何進行參數的檢驗及傳回值的收集.
在舊的編程模式中,以上問題的解決均需要一個對組件進行充分定義的說明性檔案.而且,該說明性檔案的格式
必須完全符合所用的程式設計語言的文法.這就產生了以下一些矛盾.首先,為每一個發行組件均配置各種不同版本
的說明檔案在實踐中並不可行;第二,即使這樣的標頭檔通過類型庫自動轉換得到,為各種程式設計語言提供這種轉
換工具同樣是不可行的;第三,組件中所用到的資料類型並不一定總能與目標編稱語言一一對應;第四,這種笨拙
的實現方法,與構件的"隨插即用"概念相去甚遠,程式員難以接受.
針對以上問題,COM規範提出了自動化技術,較好的實現了以符號為導航的動態綁定.Idispatch是實現這一點的
關鍵介面.
Class Idispatch : public Iunknown
{
public:
virtual HRESULT GetTypeInfoCount(UINT * pctinfo) = 0;
virtual HRESULT GetTypeInfo(UINT iTinfo,LCID
lcid,ItypeInfo ** ppTInfo) = 0;
virtual HRESULT GetIDsOfNames(REFIID riid,LPOLESTR *
rgszNames,UINT cNames,LCID lcid,DISPID *rgDispId) = 0;
virtual HRESULT Invoke(DISPID dispIdMember,REFIID
riid,LCID lcid,WORD wFlags,DISPPARAMS *pDispParams,
VARIANT *pVarResult,EXCEINFO *pExcepInfo,
UINT *puArgErr) = 0;
};
在Automation 物件中,每一個成員函數均對應一個分發ID(DISPID),服務導航器InVoke就是通過此分發ID建立與目標
函數的串連.可以用GetIDsofName實現符號(函數名)到ID的映射.因此,應用程式就有了一個統一的介面來使用
組件提供的各項服務.
對於資料類型的相容性問題,微軟提供了一個生硬的解決方案.定義一個儘可能"包羅永珍"的大的資料結構
VARIANT
typedef struct tagVARIANT
{
VARTYPE vt; //類型標示
union
{ // 傳實值型別
short iVal;
long lVal;
byte bVal;
float fltVal;
……
// 參考型別
short *piVal;
……
IUnknown **ppunkVal;
IDispatch **ppdispVal;
VARIANT *pvarVal;
void *byref;
};
};
Automation 物件與其調用者之間參數與傳回值的傳遞,必須在VARIANT的範圍之內進行.各種不同種類的VARIANT型參
數之間的轉換,可以在InVoke中實現.為程式員提供更加智能化的編程介面(如VB中實現的那樣).
COM為所有的組件對象規定了一個必須實現的基底介面IUnknown,這不僅定義了最基本的公用服務,而且還使一個
指向IUnknown介面的指標客觀上成為組件執行個體的對象標示.除物件存留期的管理外,這種對象標示還使COM組件
具有了自解釋的特性:通過對Iunknown介面的成員函數QueryInterface的調用可以查詢到對象實現的所有介面
.
不過上述自動化的各種優點是以其資料類型的限制和效能的部分損失為代價的.雖然編譯器可以通過類型庫進
行前綁定(參數類型的檢查,符號到分發ID的轉換),但是最耗時的InVoke卻難以最佳化.為此COM規定了雙介面機制
,為程式員提供了在動態綁定與靜態聯編之間選擇的自由.同時,自動化技術的存在,也為指令碼語言(VBScript,
JavaScript)提供了活力.
三 包容和彙總
作為物件導向系統的重要特性,COM支援兩種重用模型:包容(containment)和彙總(aggregation)它們的思路基
本一致,只是在實現方法上有所不同.
考慮包容的情況,假定已經有一個實現了介面IReuseInterface的COM對象A,由於新的需求,我們又要實現一個新
的COM對象B,它既要實現介面IReuseInterface,又要實現介面INewInterface,而且IreuseInterface所提供的服
務與對象A的介面基本一致,只要再實現新添加的功能就可完成對象B的開發.最直觀的想法就是在對象B的內部
"包容"一個對象A的執行個體.所有對B的介面IreuseInterface成員函數的調用均在其內部簡單地轉寄給對象A,對於
B的客戶來說,更本無需知道A的存在.這種包容關係可用表示:
包容模型在實現時較為靈活,可以再將調用轉寄給內部對象的前後,進行一些本地化的處理,從而彌補A和B各自
IreuseInterface介面在功能上的微小差異.
依然考慮上面的例子,如果B的IReuseInterface介面在功能上與A實現的完全一致,不需要做任何改動.現在採用
彙總模型來實現B.對象B本身並不是實現介面IreuseInterface,它只實現介面InewInterface.當B客戶請求
IreuseInterface 介面時,對象B把對象A的IreuseInterface介面指標傳遞給客戶程式,因此客戶直接與A進行了
互動(雖然它並不知道對象A的存在).彙總模型的實現如所示:
當然,彙總模型在對Iunknown指標的處理上要比包容模型複雜一些.需要進行對象雙方的合作,並不是每對象都
能支援彙總的特性.但彙總體現了組件軟體真正意義上的複用,而包容的重用性只是體現在客戶服務器模型相對
性的基礎上.實際也就是客戶程式和組件程式的嵌套關係.這是包容和彙總的本質不同.
四 統一資料轉送和訊息傳遞
在基於軟體複用開發的應用系統中,各分立的組件對象要相互協作來共同完成某一特定任務邏輯.為支援各種可
能複雜的協作關係,僅有參數資訊的傳遞是遠遠不夠的,必須提供應用間資料交換的標準方案和事件,訊息的通
知機制.
在這兩方面,COM均提供了較為成熟的規範.即統一資料轉送(UDT,Uniform Data Transfer)和MSMQ.
眾所周知的OLE程式利用剪貼簿傳輸連線物件資訊,就是UDT的一種典型應用.統一資料轉送建立在結構化儲存的
基礎上,包括兩方面的內容:首先是資料格式的統一,其次是傳輸協議的建立.統一資料轉送機制使用"資料對象
"作為資訊實體,通過IDataObject介面暴露其內部資訊.由於資料對象本身是一個COM對象,因此它不僅可以表達
一般的結構化資訊,也可以表達一些非結構化資訊.
傳輸協議任務就是將一個組件提供的資料對象即IdataObject介面指標傳遞給另一個組件對象.接收方得到
IdataObject介面指標後就可以利用其成員函數擷取所需的資料.傳輸協議的具體實現與資料交換是完全分開的
,因此可以在最大限度與原系統保持相容的情況下,方便的擴充協議.較常見的傳輸協議包括剪貼簿,"拖-放"技
術等.另外,為適應一些即時性比較強的應用,COM還提供了"資料變化通知"機制,以擴充資料對象與客戶程式之
間的雙向通訊.MicroSoft Message Queue(MSMQ)版本1是Windows NT作業系統的一個新特徵.MSMQ提供可在不可
靠的網路上進行可靠的關鍵任務資訊通訊.與大多數現代應用程式用緊密耦合且同步通訊相反,MSMQ基於一個消
息排隊模型來提供一個非同步,鬆散耦合,可靠的網路通訊.出於功能複用及標準化的考慮,MSMQ 使用了面向訊息
的中介軟體(MOM Message_Oriented Middleware).MOM提供了一個隊列和訊息的通訊基礎結構.應用程式向隊列寫
入訊息,訊息中介軟體將訊息傳遞給被請求的隊列,另一個應用程式(或同一應用程式的另一個組件)從目標隊列讀
取訊息並進行處理.訊息的內容完全由讀寫隊列的應用程式控制.MSMQ支援大範圍的隊列類型.公用應用程式隊
列對MSMQ客戶是可見的,因為它們發布在一個命名伺服器上,即MQIS(Message Queue Information Store,訊息
隊列資訊儲存).MSMQ客戶能夠查詢MQIS來確定是否存在某一公用應用程式隊列以及它已哪台電腦為主機.另
一方面,私人的應用程式隊列不在MQIS中發布.因此,想向私人隊列發送訊息,就必須詳細知道此隊列的主機.
五 總結
企業級應用系統領域中的構件複用,由於有了三層體繫結構的支援而變得清晰,明確.在Windows DNA架構中應用
系統被分為了表現層,業務層和資料層.其中的業務層又可根據需要分為多個子層.表現層和資料層的重用已經
相當標準化了,如各種支援GUI開發的類庫,ActiveX控制項,OLEDB,ODBC等.構件複用的重點在業務處理層.筆者認
為通過對商業世界中各項服務的明確劃分,對東服務介面的定義標準化,可以有效地實現體繫結構和組件的複用
.
隨著Windows 2000的發布,即將浮出水面的COM+作為COM,DCOM,MTS的某種整合,與作業系統的結合更加緊密.
COM+提供了對Server Load Balancer,安全性,訊息事件模型,MTS服務的更好支援.同時,COM+的部分特性將在下一代編譯器
(Visual Studio 7.0)中有所體現,使得組件化程式設計更加自然,友好.隨著COM+的發表,從現在到將來,COM將
成為一條基於Windows的面向組件對象的軟體開發之路.
參考文獻
① Stephen R.Schach,Software Engineering with JAVA,McGraw-HIll
② Component Object Model specification,MicroSoft Corp.,1993
③ 潘愛民,COM原理與應用 ,清華大卻出版社
④ 袁小玲,組件技術--企業管理資訊系統開發的新曙光,電腦工程與應用
1999,9
⑤ 徐正權,駱婷,異質可複用軟構件屬性模型,電腦應用研究,2000,3
⑥ 來欣,鄧鐵清,分布對象與WWW技術的整合研究與實踐,電腦工程與應用,
1999,7
⑦ Rogerson Dale,Inside COM,Microsoft Press,1997.ISBN 1572313498.
⑧ Don Benage Azam Mirza,Building Enterprise Solutions with Visual Studio
6.0,Macmillan Computer Publishing U.S.A