應該使用哪個框架?用ATL和MFC來創建ActiveX控件1

本文假定你熟悉MFC, ATL, and COM
摘要目前MFC和ATL代表了兩種框架,分別面向不同類型的基於Windows的開發。MFC代表了創建獨立的Windows應用的一種簡單、一致的方法;ATL提供了一種框架來實現創建COM客戶機和服務器所必須的樣板文件代碼。這兩種框架在它們對於開發ActiveX的用途上會合了。我們將看看這兩種框架是如何適用於創建ActiveX控件的——突出其優缺點,親自經歷創建一個控件的過程——以便你能夠決定何時使用一種框架,何時使用另一種。
--------------------------------------------------------------------------------
如果你希望用C++來寫ActiveX®控件,有兩個流行的框架,一個是Microsoft® Foundation Classes (MFC),另一個是ATL。我將深入的解釋這兩種框架對開發ActiveX控件所提供的支持,幫助你更好的決定哪種模型最適合你的開發環境和需求。
ActiveX控件的完全形態

ActiveX控件基於構件對象模型COM,使得ActiveX控件成爲可能的COM的基本原則是一個對象的接口和其實現能夠而且應該分開對待。只要COM的對象和它的客戶方代碼就接口細節達成了一致,如何實現就不成其問題。ActiveX控件展示了大量ActiveX控件包容器理解的接口。因爲客戶方代碼和控件認可這些接口的外在表現,你可以編寫一個ActiveX控件然後簡單的將它放入包容器中。包容器將通過良好定義的接口來驅動控件,而這些控件將以自己的方式做出合適的響應。在更高的層次上,一個ActiveX控件是實現了幾個主要ActiveX技術的一個COM對象,包括通常的引入COM接口,OLE嵌入協議,連接點和屬性頁。在較低的編程層次上,ActiveX控件只是實現了某些類型接口的COM類。當一些客戶方代碼成功的查詢到這些接口之一時,它就知道如何使用一個ActiveX控件了。

一個ActiveX控件暴露的接口主要分爲3類。第一,ActiveX控件是可嵌入的對象;就是說,它們實現了大多數的OLE文檔、in-place激活和嵌入協議。ActiveX控件實現瞭如下的接口:

IOleObject, IPersistStorage, IDataObject, IOleInPlaceActiveObject, IOleInPlaceObject, IViewObject2和IRunnableObject (這一個很少用到). 第二ActiveX控件通常都支持屬性頁,這樣客戶方就可以修改控件的屬性了。最後,ActiveX控件通常都利用COM的連接點技術,實現了客戶方能發現的外出接口。

爲了幫助比較ATL和MFC框架,我們來看一下寫在每一種框架中的相同的控件。此控件監視創建它的線程上傳遞的消息流。消息流控件是一個很不錯的例子,因爲它演示了一個ActiveX控件所有主要的方面,包括送入接口、外出接口,屬性,永久性以及屬性頁。讓我們從研究這兩個框架提供的標準的COM支持開始吧。

MFC的基本COM支持

Microsoft建立MFC使得開發Windows®應用程序比使用SDK容易多了。有了MFC,Microsoft接着增加了對即存框架的COM支持。這意味着MFC的開發者在增加越來越多的函數時必須保持框架的完整。同時,Visual C++®編譯器那時還不支持模板,因此,它們不得不借助非模板的其它手段來將COM功能摻入它們的類中。Microsoft通過加入一些虛函數到CcmdTarget類和一些宏中解決了這個問題,使得在MFC中實現COM接口有了可能。

MFC內部的COM支持從CcmdTarget開始,CcmdTarget類實現了Iunknown接口,還包括了一個用於引用計數的成員變量(m_dwRef)以及用於實現IUnknown 的6個函數:: InternalAddRef, InternalRelease, InternalQueryInterface, ExternalAddRef, ExternalRelease, 和 ExternalQueryInterface.。QueryInterface的兩個版本——AddRef和Release支持COM聚合。InternalAddRef, InternalRelease和InternalQueryInterface完成引用計數和QueryInterface操作,而ExternalAddRef, ExternalRelease和 ExternalQueryInterface代理控制聚合的對象(如果此對象參與聚合的話)。

MFC使用嵌套的類複合策略來實現COM接口。在MFC中,想實現COM 接口的類是從CcmdTarget中派生的。每個由CcmdTarget派生出的類實現的接口得到它自己的嵌套類。MFC使用宏BEGIN_INTERFACE_PART和END_INTERFACE_PART來產生嵌套類。

最後,MFC實現了表驅動的QueryInterface。MFC的接口映射的工作機理同它的消息映射基本相同:MFC的消息映射把一個Windows消息和一個C++類中的函數相聯繫;MFC的接口映射把一個接口的GUID和一個表示此接口的特定的vptr的地址相聯繫。每個基於CcmdTarget的類實現COM接口通過更多的宏:DECLARE_INTERFACE_MAP, BEGIN_INTERFACE_MAP, INTERFACE_PART,和 END_INTERFACE_MAP來增加一個接口映射。

爲了理解這些宏在實際中是什麼樣子的,請看圖1,它說明了實現ActiveX控件,COleControl 的MFC類。當你細讀代碼時,注意ColeControl帶有夾在一對BEGIN_INTERFACE_PART 和 END_INTERFACE_PART宏之間的每個接口的簽名,還要注意ColeControl的接口映射表有22個條目。

除了實現了Iunknown接口,MFC還包括IclassFactory的一個標準實現。再一次,MFC通過若干宏提供了此支持。MFC有兩個宏來提供類對象:DECLARE_OLECREATE_EX 和 IMPLEMENT_OLECREATE_EX.。在一個基於CcmdTarget的類中使用這些宏增加一個ColeObjectFactory類型的靜態成員到該類中。如果你看一下AFXDISP.H中 ColeObjectFactory的定義,你將會看到用在COleObjectFactory 中的MFC的嵌套類宏爲實現IClassFactory2定義了一個嵌套類。IClassFactory::CreateInstance的MFC版本使用MFC的動態創建機制(DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREATE宏打開此功能)來實例化COM類,因此買入MFC的COM支持同樣意味着買入它的動態創建機制。

最後幾個由MFC提供的在ActiveX控件內的基本COM支持是對Idispatch的支持。用Visual C++ 和 MFC實現一個分發接口幾乎是微不足道的。在MFC中實現一個分發接口,只需要使用ClassWizard就可以了。ClassWizard中的自動創建板有一個按鈕用於添加屬性,另一個用於添加方法。在MFC中,Idispatch支持來自CcmdTarget類。IDispatch 的MFC的實際實現在一個叫做COleDispatchImpl 的類中,ColeDispatchImpl派生自Idispatch,實現了所有4個Idispatch函數:GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, 和 Invoke.。由CcmdTarget派生的類通過調用EnableAutomation,將IDispatch vptr加入到它們的接口映射中。當客戶在基於MFC的ActiveX控件上調用IDispatch 的QueryInterface時,CcmdTarget交出鏈接在ColeDispatchImpl上的vptr。

每次你使用ClassWizard將一個自動屬性或者方法加入到一個類中時,你同時也在該類的分發映射表中加入了一項。一個分發映射表是一個將DISPIDs(用來調用分發成員的符號)和它們的供人讀的名字以及和實際完成這個工作的某些C++代碼聯繫起來的簡單的表格。ColeDispatchImpl的調用以及GetIDsOfNames函數通過在類的分發映射表中查找分發成員並分發DISPID相對應的函數來工作。MFC能爲某些基於COM的高級技術如OLE文檔、OLE拖放和自動操作提供非常好的支持,然而,如果你想更改框架——比如說,你想將分發接口編程雙接口的——你就得大動手腳了。另一方面,ATL更加是COM中心的。

ATL的基本的COM支持

ATL的目標是使開發者不必重寫IUnknown, IDispatch, IclassFactory和其它的分支以將常規的DLL和EXE變成基於COM的DLL和EXE。從這個角度講,ATL是一個比MFC精簡的多的框架,它設計和生成時就考慮了COM支持。它使用基於模板的方法,通過繼承ATL提供的模板,開發者可以加入各種COM功能片斷。

ATL的原始COM支持是從對Iunknown的支持開始的。ATL的Iunknown實現分成兩個部分:CcomObjectRootEx類,用來處理Iunknown部分的引用計數;CcomObjectRootBase類,用來處理QueryInterface。

CcomObjectRootEx是一個基於模板的類,將線性模型作爲其唯一參數。這是一個真正有趣的說明ATL怎樣使用模板將算法作爲模板參數傳遞的例子。ATL有兩個處理引用計數的類,用於處理不同的線性模型: CComSingleThreadModel 和 CcomMultiThreadModel。這些類每個都有一個遞增和一個遞減函數。它們之間的區別是CcomSingleThreadModel用標準C++操作符(++和——)實現遞增和遞減;而CcomMultiThreadModel使用線程安全的InterlockedIncrement 和 InterlockedDecrement函數來實現這兩個功能。根據用來實例化CcomObjectRootEx的模板參數,它能正確的運行給定的組件類型。你很快將會看到它的用法的一個例子。象MFC,ATL使用基於表的查找機制實現QueryInterface.。CComObjectRootBase 通過一個接口映射處理類的QueryInterface函數。BEGIN_ COM_MAP 和 END_COM_MAP 宏定義了一個接口映射的開始和結束。然而,不像MFC,ATL提供了17種途徑來組成一個接口映射,例如使用從ATL的基於模板的接口實現類如IOleObjectImpl 來的vptrs。這包括了那些從tear-off 的類或者由聚合提供的類來的vptrs。

在ATL裏,C++類通過繼承CcomObjectRootEx,指定它們想用的組件模型(記住,MFC的Iunknown支持是內建在CcmdTarget中的)變成了COM類。

ATL的類對象(以及IClassFactory)支持也來自模板,而MFC的類對象支持通過ColeObjectFactory和一些宏而有效。ATL的類對象支持來自CComCoClass/CcomClassFactory類家族和CcomCreator類家族。CcomCoClass包含了類的GUID,定義了COM類的錯誤處理設施。CcomCreator類提供了CreateInstance的實現,供CcomClassFactory使用。對於MFC,你可以通過若干宏,使所有這種支持有效。ATL包括 DECLARE_CLASS_FACTORY, DECLARE_CLASS_ FACTORY2, DECLARE_CLASS_FACTORY_AUTO_THREAD, 以及 DECLARE_CLASS_FACTORY_SINGLETON等宏用來使各種具體的類工廠支持有效。

最後,ATL 對IDispatch的支持還來自模板類,——其名字是IDispatchImpl.。比起MFC的Idispatch支持來,ATL對Idispatch的支持更加是COM中心的。MFC使用了一種hand-rolled 的IDispatch實現,而ATL使用更加標準的方法來加載一個接口的類型信息並代表標準的類型庫編譯器。

圖2顯示了一個標準的基於ATL的控件。最值得注意的一點是MFC和ATL各是怎樣引入實現一個控件所需的必要的各種接口的。MFC對標準控件接口的支持是內建在ColeControl類中的。你從ColeControl中派生出你的控件並且一次性繼承所有的函數調用。注意ATL通過模板繼承以零碎的方式逐個引入每個功能片斷。這是一個非常重要的差異,因爲這意味着用ATL你可以忽略一些接口實現模板(例如,使你的控件更爲精簡)剝掉不希望的功能。對MFC,你不能完成同樣的動作——不管你想不想,你將獲得所有接口。

關於例子應用

這裏我將使用的例子是一個通過一個分支過程監控消息流的ActiveX控件,它實時的顯示消息流圖。這兩個控件實際上有着相同的功能。它們都把圖表提交到屏幕。它們都帶流入接口以便包容器能通知控件開始和停止該圖表。它們都支持圖表線的顏色和消息間隔長度作爲屬性而可以永久存在。最後,它們都支持缺省事件集,將關於在一個特定時間段裏處理的消息的數量通知包容器。上圖顯示了這兩個控件。

用MFC開發一個控件

用MFC開發一個ActiveX控件涉及到在Visual Studio®.中使用ActiveX ControlWizard。爲了開始一個新的控件,從File菜單中選擇New,然後從工程類型列表中選擇MFC ActiveX控件Wizard。首先,ControlWizard要求你決定在DLL中包括多少個控件。接着你就可以選擇你打算怎樣實現你的控件。

ControlWizard提供的第一批選項總體上適用於控件的DLL。它們包括了許可支持、源碼註釋和在線幫助。選擇許可使得ControlWizard使用BEGIN_OLEFACTORY和END_OLEFACTORY (而非DECLARE__OLECREATE).。BEGIN_OLEFACTORY 和 END_OLEFACTORY宏覆蓋了VerifyUserLicense和GetLicenseKey,因而爲你的控件提供許可支持。請求ControlWizard包括註釋將所有的TODO註釋加入代碼中。最後,請求ControlWizard包括在線幫助將爲DLL創建樣板HELP文件源代碼。

一旦你通過了第一個對話框,ControlWizard就顯示一個對話框用來配置DLL中的控件。這些配置選擇包括使控件在運行時可見的選項,使得控件在可見時激活的選項,使得對象可以被插入的選項,給控件一個About框的選項,使得控件像一個簡單的框架控件那樣行爲的選項。圖4解釋了不同的選項是如何影響ControlWizard生成的代碼的。

ControlWizard還有一個將控件實現爲一個標準的Windows控件的選項,就像一個編輯框或者一個按鈕。這是一個有趣的選項。例如,如果你選擇按照一個按鈕將你的控件分成子類,控件的窗口實際上是一個按鈕。此時,PreCreateWindow攔截控件的窗口創建,當創建控件的窗口時使用BUTTON窗口類。ControlWizard使你可以選擇一些高級的選項,包括無窗口的激活,使你的控件具有完整的設備上下文,實現無抖動的激活,使你的控件在非激活狀態也接受鼠標消息,使你的控件異步的加載自己的屬性。這裏有一個每個選項如何影響ControlWizard生成的代碼的綱要。

無窗口的激活此選項覆蓋COleControl::GetControlFlags,將windowlessActivate標誌附加到控件標誌中。一旦使此選項有效,包容器就將輸入消息送交到控件的IoleInPlaceObjectWindowless接口。此接口ColeControl的實現通過你的消息映射分發消息。你就能通過簡單的添加相應的入口到消息映射表,像處理一般windows消息那樣處理消息了。

無省略的設備上下文選擇了此選項覆蓋COleControl::GetControlFlags,關閉clipPaintDC位,從而在ColeControl的 OnPaint函數中去掉了IntersectClipRect調用。如果你確定你的控件並不需要在客戶區外部繪圖,這個選項就有用了,因爲使對IntersectClipRect的調用失效後,有一個明顯的速度的提高。

無抖動的激活選擇此選項覆蓋COleControl::GetControlFlags,將缺省控件標誌與noFlickerActivate逐位相或。控件在激活的時候檢查此標誌以阻止控件在激活和非激活狀態轉換時被重畫。如果你的控件在激活和非激活狀態外觀一樣,那麼這個選項就是有用的。

非激活時的鼠標指針通知這個選項覆蓋COleControl::GetControlFlags,附加了pointerInactive位。IpointerInactive接口使得一個對象大多數時間保持非激活,然而仍然參加與鼠標的某些操作的交互,例如拖放。

優化的繪圖碼 這個選項覆蓋COleControl::GetControlFlags,打開canOptimizeDraw位,具有優化繪圖代碼的控件檢查這個標誌(通過COleControl'的IsOptimizedDraw函數)來確定控件是否需要在完成繪畫後將舊的對象復原回設備上下文。

異步加載屬性此選項將stock ReadyState屬性和stock ReadyStateChange事件加入到控件中去。這將使得控件異步的加載其屬性。例如,一個加載大量的數據作爲其屬性之一的控件會需要很長的時間來加載,而鎖住了控件。這個stock屬性和事件使得此控件立刻開始加載過程。包容器使用此事件和屬性判斷控件何時完成加載。

當ControlWizard完成這些事情後,你就得到了編譯到一個包含此控件的DLL的源代碼(擴展名是.OLX)。由wizard產生的源代碼包括一個從ColeControlModule(它是從CwinApp中派生的)中派生的類。這個類包含整個控件模塊的初始化代碼。接着,wizard爲基於ColeControl的表示每個控件的類生成源代碼。最後,wizard生成一些ODL代碼用來建立類型信息。

一旦wizard產生了控件DLL,你就面臨完善這個控件的任務了。這意味着添加渲染代碼,開發一個引入接口(方法和屬性),rigging up屬性頁,展示某些事件。但是,在我向你說明所有這些都是如何工作之前,我們先來看一下使用ATL創建一個控件都需要什麼。

用ATL開發一個控件

有了基於MFC的控件,你就可以用ATL COM App Wizard得到一個開發基於ATL的控件的觸發器。使用ATL來創建控件可以分爲兩步。雖然MFC的Control Wizard要求你預先確定你希望在DLL中包含多少個控件,ATL COM Wizard簡單的創建DLL——你可以以後使用ATL對象選項從Insert菜單添加控件。當創建一個新的基於ATL的DLL時,你可以選擇混用MFC支持。你還可以選擇在控件的DLL中合併任何proxy/stub代碼。這使得如果有人希望遠程控制你的控件實現的代碼,你只要發佈一個文件。

一旦生成了基於ATL的DLL,你就可以開始添加COM類了。Insert New ATL Object菜單條使得這項工作變得十分容易。選擇此菜單項顯示一個用來創建任何一個COM類的對話框,包括無格式的COM對象,ActiveX控件以及Microsoft事務服務器組件(Windows NT Server的一部分)。

當添加基於ATL的控件到你的工程的時候,ATL Object Wizard比MFC Object Wizard提供了更大範圍的選項。對於新手來說,ATL使得你可以選擇使用任何現有的線性模型實現你的控件。你可以將你的類標記爲或者單線程或者單元線程的。ATL Object Wizard限制你創建一個自由的或者混合線程的控件,因爲控件通常是面向UI的。

如果你創建了一個單線程的控件,一個包容的控件的客戶將總是將它加載到主、單線程的單元(STA)中。結果,只有在客戶進程空間運行的單主線程纔會接觸到你的對象,這樣就免除了你保護你的控件狀態不受併發訪問干擾的責任。另外,因爲你的對象的所有實例將只會被一個線程接觸,你將不必擔心DLL中的全局數據。如果你的控件是單元線程的,你還免除了保護你的控件的內部狀態的大部分負擔。然而,你仍然不得不保護DLL中的全局數據。爲什麼呢?首先,設想你的控件是由客戶的單線程創建的。現在假定客戶試圖創建該控件的另一份拷貝——但是是從一個雲向在當前進程的多線程的單元中。通過將你的對象標記爲單元線程的,COM被告知你希望你的控件保護免遭併發訪問。COM在它加載時爲你的控件創建一個新的STA。現在當線程調用到你的對象時,它們只能通過單元邊界來訪問它,遠程層將同步對此對象的調用。然而,當某個控件的狀態被保護不被併發訪問,作爲在一個STA中的副產品,由控件的實例所共享的數據(象在DLL中的全局數據一樣)是脆弱的。這是因爲你的全局DLL數據(同時爲幾個對象服務,分別運行在獨立的線程中)會被那些多線程同時接觸到。  

更多分享請關注:軟信網-編程-http://www.iis365.net.cn

發佈了13 篇原創文章 · 獲贊 5 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章