簡介
自從1993年Microsoft首次公布了COM技術以後,Windows平台上的開發模式發生了巨大的變化,以COM為基礎的一系列軟體組件化技術將Windows編程帶入了組件化時代。廣大的開發人員在為COM帶來的軟體組件化趨勢歡欣鼓舞的同時,對於COM開發技術的難度和煩瑣的細節也感到極其的不便。COM編程一度被視為一種高不可攀的技術,令人望而卻步。開發人員希望能夠有一種方便快捷的COM開發工具,提高開發效率,更好地利用這項技術。
針對這種情況,Microsoft公司在推出COMSDK以後,為簡化COM編程,提高開發效率,採取了許多方案,特別是在MFC(MicrosoftFoundationClass)中加入了對COM和OLE的支持。但是隨著Internet的發展,分散式的組件技術要求COM組件能夠在網路上傳輸,而又儘量節約寶貴的網路頻寬資源。採用MFC開發的COM組件由於種種限制不能很好地滿足這種需求,因此Microsoft在1995年又推出了一種全新的COM開發工具ATL。
ATL是ActiveXTemplateLibrary的縮寫,它是一套C++模板庫。使用ATL能夠快速地開發出高效、簡潔的代碼(EffectiveandSlimcode),同時對COM組件的開發提供最大限度的代碼自動生成以及可視化支持。為了方便使用,從MicrosoftVisualC++5.0版本開始,Microsoft把ATL集成到VisualC++開發環境中。1998年9月推出的VisualStudio6.0集成了ATL3.0版本。ATL已經成為Microsoft標準開發工具中的一個重要成員,日益受到C++開發人員的重視。
益處
這還要先從ATL產生以前的COM開發方式說起。
在ATL產生以前,開發COM組件的方法主要有兩種:一是使用COMSDK直接開發COM組件,另一種方式是通過MFC提供的COM支持來實現。
直接使用COMSDK開發COM組件是最基本也是最靈活的方式。通過使用Microsoft提供的開發包,我們可以直接編寫COM程式。但是,這種開發方式的難度和工作量都很大,一方面,要求開發者對於COM的技術原理具有比較深入的了解(雖然對技術本身的深刻理解對使用任何一種工具都是非常有益的,但對於COM這樣一整套複雜的技術而言,在短時間內完全掌握是很難的),另一方面,直接使用COMSDK要求開發人員自己去實現COM套用的每一個細節,完成大量的重複性工作。這樣做的結果是,不僅降低了工作效率,同時也使開發人員不得不把許多精力投入到與套用需求本身無關的技術細節中。雖然這種開發方式對於某些特殊的套用很有必要,但這種編程方式並不符合組件化程式設計方法所倡導的可重用性,因此,直接採用COMSDK不是一種理想的開發方式。
使用MFC提供的COM支持開發COM套用可以說在使用COMSDK基礎上提高了自動化程度,縮短了開發時間。MFC採用面向對象的方式將COM的基本功能封裝在若干MFC的C++類中,開發者通過繼承這些類得到COM支持功能。為了使派生類方便地獲得COM對象的各種特性,MFC中有許多預定義宏,這些宏的功能主要是實現COM接口的定義和對象的註冊等通常在COM對象中要用到的功能。開發者可以使用這些宏來定製COM對象的特性。
另外,在MFC中還提供對Automation和ActiveXControl的支持,對於這兩個方面,VisualC++也提供了相應的AppWizard和ClassWizard支持,這種可視化的工具更加方便了COM套用的開發。
MFC對COM和OLE的支持確實比手工編寫COM程式有了很大的進步。但是MFC對COM的支持是不夠完善和徹底的,例如對COM接口定義的IDL語言,MFC並沒有任何支持,此外對於近些年來COM和ActiveX技術的新發展MFC也沒有提供靈活的支持。這是由MFC設計的基本出發點決定的。MFC被設計成對Windows平台編程開發的面向對象的封裝,自然要涉及Windows編程的方方面面,COM作為Windows平台編程開發的一個部分也得到MFC的支持,但是MFC對COM的支持是以其全局目標為出發點的,因此對COM的支持必然要服從其全局目標。從這個方面而言,MFC對COM的支持不能很好的滿足開發者的要求。
隨著Internet技術的發展,Microsoft將ActiveX技術作為其網路戰略的一個重要組成部分大力推廣,然而使用MFC開發的ActiveXControl,代碼冗餘量大(所謂的“肥代碼FatCode”),而且必須要依賴於MFC的運行時刻庫才能正確地運行。雖然MFC的運行時刻庫只有部分功能與COM有關,但是由於MFC的繼承實現的本質,ActiveXControl必須背負運行時刻庫這個沉重的包袱。如果採用靜態連線MFC運行時刻庫的方式,這將使ActiveXControl代碼過於龐大,在網路上傳輸時將占據寶貴的網路頻寬資源;如果採用動態連線MFC運行時刻庫的方式,這將要求瀏覽器一方必須具備MFC的運行時刻庫支持。總之MFC對COM技術的支持在網路套用的環境下也顯得很不靈活。
解決上述COM開發方法中的問題正是ATL的基本目標。
首先ATL的基本目標就是使COM套用開發儘可能地自動化,這個基本目標就決定了ATL只面向COM開發提供支持。目標的明確使ATL對COM技術的支持達到淋漓盡致的地步。對COM開發的任何一個環節和過程,ATL都提供支持,並將與COM開發相關的眾多工具集成到一個統一的編程環境中。對於COM/ActiveX的各種套用,ATL也都提供了完善的Wizard支持。所有這些都極大地方便了開發者的使用,使開發者能夠把注意力集中在與套用本身相關的邏輯上。
其次,ATL因其採用了特定的基本實現技術,擺脫了大量冗餘代碼,使用ATL開發出來的COM套用的代碼簡練高效,即所謂的“SlimCode”。ATL在實現上儘可能採用最佳化技術,甚至在其內部提供了所有C/C++開發的程式所必須具有的C啟動代碼的替代部分。同時ATL產生的代碼在運行時不需要依賴於類似MFC程式所需要的龐大的代碼模組,包含在最終模組中的功能是用戶認為最基本和最必須的。這些措施使採用ATL開發的COM組件(包括ActiveXControl)可以在網路環境下實現套用的分散式組件結構。
第三,ATL的各個版本對Microsoft的基於COM的各種新的組件技術如MTS、ASP等都有很好的支持,ATL對新技術的反應速度大大快於MFC。ATL已經成為Microsoft支持COM套用開發的主要開發工具,因此COM技術方面的新進展在很短的時間內都會在ATL中得到反映。這使開發者使用ATL進行COM編程可以得到直接使用COMSDK編程同樣的靈活性和強大的功能。
基本技術
雖然使用ATL開發COM套用是一件非常簡單的事情,但是在ATL簡單易用的界面後面卻包含著複雜的技術。面對ATL生成的大量代碼,我們即使不去深入地了解這些代碼的含義也可以開發出COM套用來,但是如果我們要充分地挖掘ATL的潛力,開發出更靈活、強大的COM套用,則必須對ATL使用的基本技術有所了解。研究ATL的實質最好的教材就是由VisualC++提供的ATL原始碼。本文這一部分只是對ATL中用到的最基本的技術進行簡單的介紹。
簡單地說來,ATL中所使用的基本技術包括以下幾個方面:
COM技術
C++模板類技術(Template)
C++多繼承技術(Multi-Inheritance)
COM技術是理解ATL的基礎,使用ATL進行開發要對COM技術的基本概念有最低限度的了解。由於COM是一項非常複雜龐大的技術體系,限於本文的篇幅,這裡不再贅述。對於本文中提到的COM基本概念也不做過多的解釋,請讀者參閱有關的參考書籍。
作為ATL最核心的實現技術的模板是對標準C++語言的擴展,但是在大多數的C++編程環境中,人們很少使用它,這是因為模板的功能雖然很強,但是它內部機制比較複雜,需要比較多的C++知識和經驗才能靈活地使用它。在MFC中的CObjectArray等功能類就是由模板來定義的。完全通過模板來定義程式的整體類結構,ATL是迄今為止做得最為成功的。
所謂模板類簡單地說是對類的抽象。我們知道C++語言用類定義了構造對象(這裡指C++對象而不是COM對象)的方式,對象是類的實例,而模板類定義的是類的構造方式,使用模板類定義實例化的結果產生的是不同的類。因此可以說模板類是“類的類”。
基本使用
這一部分將重點介紹ATL的基本使用過程。由於ATL已經被集成在MicrosoftVisulalStudio的VisualC++開發環境中,因此要使用ATL必須先安裝VisualC++。在下面的討論中有關COM的基本知識請參閱有關的文檔,這裡不再詳細說明。
使用ATL開發一個COM套用基本可以分為以下幾個步驟:
創建一個新的ATL工程,並對工程的選項進行適當的配置。
向新創建的工程添加新的ATL類,並對該類進行一些初始配置工作。
根據COM套用的基本要求向新的ATL類加入新的接口定義,並實現相應的接口成員函式。
編譯連線工程,註冊COM套用。
下面將根據這些步驟依次介紹ATL的基本使用過程(給出的是VisualStudio6.0的使用):
1.創建工程
首先啟動VisualC++集成開發環境,選擇“File”選單下的“New...”命令,在“New”對話框中選擇“Project”頁。
選擇“ATLCOMAppWizard”項,這是創建ATL工程的AppWizard嚮導入口。然後在“Projectname”編輯框中輸入工程的名字,單擊“OK”按鈕,進入AppWizard對話框。
在AppWizard對話框中主要的設定選項有:
COM服務程式的類型:
-動態連線庫(DynamicLinkingLibrary)最終產生一個動態連線庫(DLL)形式的COM服務程式;
-應用程式(Executableapplication)最終產生一個可執行程式類型(EXE)的COM服務程式;
-NT服務(NTService):產生一個以NT服務方式運行的COM服務程式。
允許嵌入Proxy/Stub代碼。由Microsoft提供的MIDL編譯IDL檔案以後,將產生用於對象調度(Marshaling)的Proxy/Stub的代碼。傳統地,這部分代碼與COM服務程式的代碼是分離的,但是由於新的COM標準支持多執行緒環境下的COM對象服務,因此在動態連線庫的COM服務程式中也要有Proxy/Stub的支持。為了支持在網路上的傳輸,ATL允許用戶選擇將Proxy/Stub的代碼包括在生成的DLL代碼中。這個選項在EXE和NT服務類型的COM套用條件下不可選。
允許支持MFC。由於ATL對除COM以外的基本的Windows編程方面的支持極為有限,同時許多程式設計師對MFC又非常熟悉,因此在ATL的工程設定中允許在ATL工程內部支持使用MFC,即可以使用MFC定義的類。這在一方面來看是非常方便的,特別是對於習慣於使用MFC的開發人員來說,能夠使用MFC提供的各種功能強大的類的支持,而不必直接使用WindowsSDK。從另一個方面來看,在ATL工程中使用MFC同時就喪失了ATL代碼輕量級的特點。
支持MTS。MTS是MicrosoftTransactionServer的縮寫,它是Microsoft在COM技術方面的一個新的分支,這裡不作詳細說明。
完成上面的設定以後,可以選擇FINISH完成工程的設定,ATL將創建相應的工程。
2.加入ATL類
完成工程的創建和設定以後,下一步就是向工程中加入一個新的ATL類。VisualStudio集成環境提供了嚮導工具“ATLObjectWizard”用於加入一個新的ATL類。操作過程並不複雜,只是一組對話框操作而已。
首先通過集成環境的“Insert”選單下的“NewATLObject…”命令進入“ATLObjectWizard”對話框。
這個對話框即為創建ATL對象的嚮導起始界面。對話框的左邊部分說明了待創建對象的基本類型,這裡主要有以下的幾種類型:
對象(Object)基本的COM對象類型;
控制(Control)ActiveXControl類型的ATL對象;
其他(Miscellaneous)輔助功能,如對話框的生成等;
數據訪問(DataAccess)數據訪問,支持MTS等。
右邊部分說明了每種類型的詳細內容,對於一般的COM服務程式,使用對象表中的簡單對象(SimpleObject)就可以了。
選定待創建對象的基本類型以後,單擊“Next>;”按鈕進入下一步,進入對象屬性設定對話框,如圖4和圖5所示。
對象屬性設定分為兩個過程:先是對象名字標識的設定,然後是對對象的基本屬性進行設定。首先是對象的名字標識設定。
在對象標識編輯框中輸入待創建對象的名字,ATL對象嚮導將同步地根據用戶輸入的對象標識設定該對象的C++標識和COM標識。對象的C++標識包括對象的類名,cpp檔案名稱和頭檔案名稱。COM標識包括對象在類型庫中的CoClass段和實現的主接口的名字,同時還有在系統註冊表中的類型名以及ProgID。
對象名字標識設定完成以後,選擇對象屬性頁(Attribute)進入對象的屬性設定頁面。
對象的屬性設定是ATL對象創建過程中最複雜的部分,包括以下幾個主要部分:
對象的執行緒模型(ThreadModel)
對象的執行緒模型是COM對象在多執行緒環境下被訪問時對訪問方式的控制,預設情況下在ATL中採用的是套間模型Apartment,由系統通過訊息佇列方式提供並發控制。
對象的接口模型(Interface)
COM對象的接口可以是雙接口(DualInterface)。雙接口不同於普通接口(CustomInterface)之處在於雙接口是從Automation基本接口IDispatch繼承的,而普通接口是從IUnknown接口直接繼承來的。預設的接口模型是雙接口。
對象的聚合模型(Aggregate)
COM規範不允許對象的實現繼承,但是可以通過聚合方式重用其它的COM對象。ATL對象屬性設定中的聚合模型可以指定待創建的COM對象是否支持聚合模型。預設的選項是支持對象的聚合。
對象對錯誤處理的支持(SupportISupportErrorInfo)
選取這個選項可以在對象的運行過程中支持錯誤處理。預設情況下這個選項不被選中。
對象對連線點的支持(SupportConnectionPoints)
連線點是COM對象的事件機制。選中這個選項可以使待創建的COM對象具有發出事件的能力。預設情況下該選項不被選中。
對象對自由執行緒調度的支持(FreeThreadMarshaller,簡稱FTM)
對象的自由執行緒調度是對象在處於自由執行緒模型狀態下,為了簡化對象的訪問過程而採用的一種最佳化策略。預設情況下該選項不被選中。
對於上述的任何一個選項的詳細描述都涉及到COM技術一些核心的內容,並且都已超出本文的範圍,因此本文只對ATL給出的預設選項加以說明,對這些內容感興趣的讀者可以參考Microsoft提供的文檔。
完成了上面的設定以後,就可以按“OK”按鈕完成對象的創建過程。下一步就是向所生成的ATL類的接口中加入成員函式的定義,以及接口成員函式的實現過程。
3.加入接口定義,實現接口函式
加入了ATL類定義之後,我們可以打開VisualC++集成環境下項目管理器(Workspace)中的ClassView來檢查生成的類定義的情況。我們可以看到一個新的類已經生成,同時,還生成了相應的接口定義。ATLObjectWizard為我們生成了類定義的.h和.cpp檔案,此外還有用於接口定義的IDL檔案。有了這些檔案以後,我們就可以為接口加入成員函式,完成類的定義。
首先在ClassView中選中相應的接口,顯示為接口IATLTest,單擊滑鼠右鍵打開選單,如圖7。此彈出式選單定義了為接口加入屬性和方法的操作。選取其中的“AddMethod...”項,可以為接口加入方法成員;選取“AddProperty...”則可以為接口加入新的屬性成員。
加入屬性和方法的對話框可以參看圖8和圖9。如果我們要在接口中加入一個方法,則選取“AddMethod...”選單命令。假設方法名為ABC,方法的返回類型為COM規定的HRESULT類型。我們也可以定義非HRESULT返回類型的函式,但是這需要手工修改接口定義的IDL檔案。我們定義ABC方法的一個參數為a,類型為整數型。完成了方法的定義以後,單擊“OK”按鈕則把此方法加入到接口中。
屬性的加入過程是類似的。屬性加入對話框要求指定屬性的類型、名字以及屬性的訪問方式。在屬性和方法的編輯對話框中都有一個“Attributes”按鈕,在給出了一個屬性或方法的基本定義之後,單擊此按鈕,可以對屬性和方法的一些高級特性進行設定。
方法成員加入以後,我們可以通過ClassView來檢查ATL為我們所做的工作。首先我們看到ATL在接口的定義中加入了該方法的定義;同時在對應的ATL類定義中,也加入了一個相應的方法的定義;在類對應的.cpp檔案中,加入了此方法的實現框架。此後,我們只要在這個函式框架中加入該方法的代碼邏輯,一個接口函式的定義和實現就基本完成了。依照這種方式,我們可以完成整個COM對象的定義和實現。
完成以上的步驟之後,我們就可以編譯連線套用了。
4.編譯連線套用、註冊COM服務程式
對ATL工程的編譯連線過程包括下面的幾個步驟:
使用MIDL編譯工程的IDL檔案,形成接口定義的頭檔案和用於調度(Marshalling)的代碼;
編譯工程的.cpp檔案形成目標檔案;
連線目標檔案,形成套用模組;
註冊COM服務程式。
關於工程編譯連線的其它部分同VisualC++中MFC工程的編譯連線過程相似,這裡只重點介紹一下COM服務程式的註冊過程。
在ATL中,COM服務程式的註冊是在工程編譯連線的最後階段,由ATL輔助完成的。在手工的COM編程中,服務程式的註冊是比較麻煩的工作。在ATL中,系統通過讀取在建立工程過程中形成的註冊腳本檔案來完成註冊工作。註冊腳本(RegisterScript簡稱RGS)是ATL提供的文本方式的註冊輔助檔案。下面是註冊腳本檔案的一個實例。
HKCR-表示註冊表中COM對象的註冊項,是HKEY_CLASS_ROOT的縮寫
{
AuthTest.ActiveXObject.1=s'ActiveXObjectClass'
{
CLSID=s''
}-對象的ProgID
AuthTest.ActiveXObject=s'ActiveXObjectClass'
{
CLSID=s''
}-對象的與版本無關的ProgID
NoRemoveCLSID-對象CLSID註冊項
{
ForceRemove=s'ActiveXObjectClass'
{
ProgID=s'AuthTest.ActiveXObject.1'
VersionIndependentProgID=s'AuthTest.ActiveXObject'
InprocServer32=s'%MODULE%-伺服器類型,表示DLL伺服器
{
valThreadingModel=s'both'-執行緒模型,這裡是BOTH型
}
}
}
}
RGS檔案包含註冊COM服務程式的各項內容,通常我們不必修改此RGS檔案,必要時我們也可以手工修改RGS檔案來定製模組的註冊過程。