摘要:目前MFC和ATL代表了兩種框架,,分別面向不同類型的基于Windows的開發(fā)。MFC代表了創(chuàng)建獨立的Windows應(yīng)用的一種簡單,、一致的方法,;ATL提供了一種框架來實現(xiàn)創(chuàng)建COM客戶機和服務(wù)器所必須的樣板文件代碼。這兩種框架在它們對于開發(fā)ActiveX的用途上會合了,。我們將看看這兩種框架是如何適用于創(chuàng)建ActiveX控件的——突出其優(yōu)缺點,,親自經(jīng)歷創(chuàng)建一個控件的過程——以便你能夠決定何時使用一種框架,何時使用另一種,。 如果你希望用C++來寫ActiveX?控件,,有兩個流行的框架,一個是Microsoft? Foundation Classes (MFC),,另一個是ATL,。我將深入的解釋這兩種框架對開發(fā)ActiveX控件所提供的支持,幫助你更好的決定哪種模型最適合你的開發(fā)環(huán)境和需求,。 ActiveX控件的完全形態(tài) ActiveX控件基于構(gòu)件對象模型COM,,使得ActiveX控件成為可能的COM的基本原則是一個對象的接口和其實現(xiàn)能夠而且應(yīng)該分開對待。只要COM的對象和它的客戶方代碼就接口細節(jié)達成了一致,,如何實現(xiàn)就不成其問題,。ActiveX控件展示了大量ActiveX控件包容器理解的接口。因為客戶方代碼和控件認可這些接口的外在表現(xiàn),,你可以編寫一個ActiveX控件然后簡單的將它放入包容器中,。包容器將通過良好定義的接口來驅(qū)動控件,而這些控件將以自己的方式做出合適的響應(yīng),。在更高的層次上,,一個ActiveX控件是實現(xiàn)了幾個主要ActiveX技術(shù)的一個COM對象,,包括通常的引入COM接口,OLE嵌入?yún)f(xié)議,,連接點和屬性頁,。在較低的編程層次上,ActiveX控件只是實現(xiàn)了某些類型接口的COM類,。當一些客戶方代碼成功的查詢到這些接口之一時,,它就知道如何使用一個ActiveX控件了。 一個ActiveX控件暴露的接口主要分為3類,。第一,,ActiveX控件是可嵌入的對象;就是說,,它們實現(xiàn)了大多數(shù)的OLE文檔,、in-place激活和嵌入?yún)f(xié)議。ActiveX控件實現(xiàn)了如下的接口: IOleObject, IPersistStorage, IDataObject, IOleInPlaceActiveObject, IOleInPlaceObject, IViewObject2和IRunnableObject (這一個很少用到). 第二ActiveX控件通常都支持屬性頁,,這樣客戶方就可以修改控件的屬性了,。最后,ActiveX控件通常都利用COM的連接點技術(shù),,實現(xiàn)了客戶方能發(fā)現(xiàn)的外出接口,。 為了幫助比較ATL和MFC框架,我們來看一下寫在每一種框架中的相同的控件,。此控件監(jiān)視創(chuàng)建它的線程上傳遞的消息流,。消息流控件是一個很不錯的例子,因為它演示了一個ActiveX控件所有主要的方面,,包括送入接口,、外出接口,屬性,,永久性以及屬性頁,。讓我們從研究這兩個框架提供的標準的COM支持開始吧。 MFC的基本COM支持 Microsoft建立MFC使得開發(fā)Windows?應(yīng)用程序比使用SDK容易多了,。有了MFC,,Microsoft接著增加了對即存框架的COM支持。這意味著MFC的開發(fā)者在增加越來越多的函數(shù)時必須保持框架的完整,。同時,,Visual C++?編譯器那時還不支持模板,因此,,它們不得不借助非模板的其它手段來將COM功能摻入它們的類中,。Microsoft通過加入一些虛函數(shù)到CcmdTarget類和一些宏中解決了這個問題,使得在MFC中實現(xiàn)COM接口有了可能。 MFC內(nèi)部的COM支持從CcmdTarget開始,,CcmdTarget類實現(xiàn)了Iunknown接口,,還包括了一個用于引用計數(shù)的成員變量(m_dwRef)以及用于實現(xiàn)IUnknown 的6個函數(shù):: InternalAddRef, InternalRelease, InternalQueryInterface, ExternalAddRef, ExternalRelease, 和 ExternalQueryInterface.,。QueryInterface的兩個版本——AddRef和Release支持COM聚合,。InternalAddRef, InternalRelease和InternalQueryInterface完成引用計數(shù)和QueryInterface操作,而ExternalAddRef, ExternalRelease和 ExternalQueryInterface代理控制聚合的對象(如果此對象參與聚合的話),。 MFC使用嵌套的類復(fù)合策略來實現(xiàn)COM接口,。在MFC中,想實現(xiàn)COM 接口的類是從CcmdTarget中派生的,。每個由CcmdTarget派生出的類實現(xiàn)的接口得到它自己的嵌套類,。MFC使用宏BEGIN_INTERFACE_PART和END_INTERFACE_PART來產(chǎn)生嵌套類。 最后,,MFC實現(xiàn)了表驅(qū)動的QueryInterface,。MFC的接口映射的工作機理同它的消息映射基本相同:MFC的消息映射把一個Windows消息和一個C++類中的函數(shù)相聯(lián)系;MFC的接口映射把一個接口的GUID和一個表示此接口的特定的vptr的地址相聯(lián)系,。每個基于CcmdTarget的類實現(xiàn)COM接口通過更多的宏:DECLARE_INTERFACE_MAP, BEGIN_INTERFACE_MAP, INTERFACE_PART,和 END_INTERFACE_MAP來增加一個接口映射,。 為了理解這些宏在實際中是什么樣子的,請看圖1,,它說明了實現(xiàn)ActiveX控件,,COleControl 的MFC類。當你細讀代碼時,,注意ColeControl帶有夾在一對BEGIN_INTERFACE_PART 和 END_INTERFACE_PART宏之間的每個接口的簽名,,還要注意ColeControl的接口映射表有22個條目。 除了實現(xiàn)了Iunknown接口,,MFC還包括IclassFactory的一個標準實現(xiàn),。再一次,MFC通過若干宏提供了此支持,。MFC有兩個宏來提供類對象:DECLARE_OLECREATE_EX 和 IMPLEMENT_OLECREATE_EX.,。在一個基于CcmdTarget的類中使用這些宏增加一個ColeObjectFactory類型的靜態(tài)成員到該類中。如果你看一下AFXDISP.H中 ColeObjectFactory的定義,,你將會看到用在COleObjectFactory 中的MFC的嵌套類宏為實現(xiàn)IClassFactory2定義了一個嵌套類,。IClassFactory::CreateInstance的MFC版本使用MFC的動態(tài)創(chuàng)建機制(DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREATE宏打開此功能)來實例化COM類,因此買入MFC的COM支持同樣意味著買入它的動態(tài)創(chuàng)建機制,。 最后幾個由MFC提供的在ActiveX控件內(nèi)的基本COM支持是對Idispatch的支持,。用Visual C++ 和 MFC實現(xiàn)一個分發(fā)接口幾乎是微不足道的。在MFC中實現(xiàn)一個分發(fā)接口,,只需要使用ClassWizard就可以了,。ClassWizard中的自動創(chuàng)建板有一個按鈕用于添加屬性,另一個用于添加方法。在MFC中,,Idispatch支持來自CcmdTarget類,。IDispatch 的MFC的實際實現(xiàn)在一個叫做COleDispatchImpl 的類中,ColeDispatchImpl派生自Idispatch,,實現(xiàn)了所有4個Idispatch函數(shù):GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, 和 Invoke.,。由CcmdTarget派生的類通過調(diào)用EnableAutomation,將IDispatch vptr加入到它們的接口映射中,。當客戶在基于MFC的ActiveX控件上調(diào)用IDispatch 的QueryInterface時,,CcmdTarget交出鏈接在ColeDispatchImpl上的vptr。 每次你使用ClassWizard將一個自動屬性或者方法加入到一個類中時,,你同時也在該類的分發(fā)映射表中加入了一項,。一個分發(fā)映射表是一個將DISPIDs(用來調(diào)用分發(fā)成員的符號)和它們的供人讀的名字以及和實際完成這個工作的某些C++代碼聯(lián)系起來的簡單的表格。ColeDispatchImpl的調(diào)用以及GetIDsOfNames函數(shù)通過在類的分發(fā)映射表中查找分發(fā)成員并分發(fā)DISPID相對應(yīng)的函數(shù)來工作,。MFC能為某些基于COM的高級技術(shù)如OLE文檔,、OLE拖放和自動操作提供非常好的支持,然而,,如果你想更改框架——比如說,,你想將分發(fā)接口編程雙接口的——你就得大動手腳了。另一方面,,ATL更加是COM中心的,。 ATL的目標是使開發(fā)者不必重寫IUnknown, IDispatch, IclassFactory和其它的分支以將常規(guī)的DLL和EXE變成基于COM的DLL和EXE。從這個角度講,,ATL是一個比MFC精簡的多的框架,,它設(shè)計和生成時就考慮了COM支持。它使用基于模板的方法,,通過繼承ATL提供的模板,,開發(fā)者可以加入各種COM功能片斷。 ATL的原始COM支持是從對Iunknown的支持開始的,。ATL的Iunknown實現(xiàn)分成兩個部分:CcomObjectRootEx類,,用來處理Iunknown部分的引用計數(shù);CcomObjectRootBase類,,用來處理QueryInterface,。 CcomObjectRootEx是一個基于模板的類,將線性模型作為其唯一參數(shù),。這是一個真正有趣的說明ATL怎樣使用模板將算法作為模板參數(shù)傳遞的例子,。ATL有兩個處理引用計數(shù)的類,用于處理不同的線性模型: CComSingleThreadModel 和 CcomMultiThreadModel,。這些類每個都有一個遞增和一個遞減函數(shù),。它們之間的區(qū)別是CcomSingleThreadModel用標準C++操作符(++和——)實現(xiàn)遞增和遞減,;而CcomMultiThreadModel使用線程安全的InterlockedIncrement 和 InterlockedDecrement函數(shù)來實現(xiàn)這兩個功能。根據(jù)用來實例化CcomObjectRootEx的模板參數(shù),,它能正確的運行給定的組件類型,。你很快將會看到它的用法的一個例子。象MFC,,ATL使用基于表的查找機制實現(xiàn)QueryInterface.,。CComObjectRootBase 通過一個接口映射處理類的QueryInterface函數(shù)。BEGIN_ COM_MAP 和 END_COM_MAP 宏定義了一個接口映射的開始和結(jié)束,。然而,,不像MFC,,ATL提供了17種途徑來組成一個接口映射,,例如使用從ATL的基于模板的接口實現(xiàn)類如IOleObjectImpl 來的vptrs。這包括了那些從tear-off 的類或者由聚合提供的類來的vptrs,。 在ATL里,,C++類通過繼承CcomObjectRootEx,指定它們想用的組件模型(記住,,MFC的Iunknown支持是內(nèi)建在CcmdTarget中的)變成了COM類,。 ATL的類對象(以及IClassFactory)支持也來自模板,而MFC的類對象支持通過ColeObjectFactory和一些宏而有效,。ATL的類對象支持來自CComCoClass/CcomClassFactory類家族和CcomCreator類家族,。CcomCoClass包含了類的GUID,定義了COM類的錯誤處理設(shè)施,。CcomCreator類提供了CreateInstance的實現(xiàn),供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實現(xiàn),,而ATL使用更加標準的方法來加載一個接口的類型信息并代表標準的類型庫編譯器,。 圖2顯示了一個標準的基于ATL的控件,。最值得注意的一點是MFC和ATL各是怎樣引入實現(xiàn)一個控件所需的必要的各種接口的,。MFC對標準控件接口的支持是內(nèi)建在ColeControl類中的,。你從ColeControl中派生出你的控件并且一次性繼承所有的函數(shù)調(diào)用,。注意ATL通過模板繼承以零碎的方式逐個引入每個功能片斷。這是一個非常重要的差異,,因為這意味著用ATL你可以忽略一些接口實現(xiàn)模板(例如,,使你的控件更為精簡)剝掉不希望的功能。對MFC,,你不能完成同樣的動作——不管你想不想,你將獲得所有接口。 關(guān)于例子應(yīng)用 這里我將使用的例子是一個通過一個分支過程監(jiān)控消息流的ActiveX控件,,它實時的顯示消息流圖,。這兩個控件實際上有著相同的功能。它們都把圖表提交到屏幕,。它們都帶流入接口以便包容器能通知控件開始和停止該圖表,。它們都支持圖表線的顏色和消息間隔長度作為屬性而可以永久存在。最后,,它們都支持缺省事件集,,將關(guān)于在一個特定時間段里處理的消息的數(shù)量通知包容器。圖3顯示了這兩個控件,。 Figure 3 監(jiān)視 ActiveX 控件消息 用MFC開發(fā)一個控件 用MFC開發(fā)一個ActiveX控件涉及到在Visual Studio?.中使用ActiveX ControlWizard,。為了開始一個新的控件,從File菜單中選擇New,,然后從工程類型列表中選擇MFC ActiveX控件Wizard,。首先,ControlWizard要求你決定在DLL中包括多少個控件,。接著你就可以選擇你打算怎樣實現(xiàn)你的控件,。 ControlWizard提供的第一批選項總體上適用于控件的DLL。它們包括了許可支持,、源碼注釋和在線幫助,。選擇許可使得ControlWizard使用BEGIN_OLEFACTORY和END_OLEFACTORY (而非DECLARE__OLECREATE).。BEGIN_OLEFACTORY 和 END_OLEFACTORY宏覆蓋了VerifyUserLicense和GetLicenseKey,,因而為你的控件提供許可支持,。請求ControlWizard包括注釋將所有的TODO注釋加入代碼中。最后,,請求ControlWizard包括在線幫助將為DLL創(chuàng)建樣板HELP文件源代碼,。 一旦你通過了第一個對話框,ControlWizard就顯示一個對話框用來配置DLL中的控件,。這些配置選擇包括使控件在運行時可見的選項,,使得控件在可見時激活的選項,使得對象可以被插入的選項,,給控件一個About框的選項,,使得控件像一個簡單的框架控件那樣行為的選項。圖4解釋了不同的選項是如何影響ControlWizard生成的代碼的,。 ControlWizard還有一個將控件實現(xiàn)為一個標準的Windows控件的選項,,就像一個編輯框或者一個按鈕。這是一個有趣的選項,。例如,,如果你選擇按照一個按鈕將你的控件分成子類,,控件的窗口實際上是一個按鈕。此時,,PreCreateWindow攔截控件的窗口創(chuàng)建,,當創(chuàng)建控件的窗口時使用BUTTON窗口類。ControlWizard使你可以選擇一些高級的選項,,包括無窗口的激活,,使你的控件具有完整的設(shè)備上下文,實現(xiàn)無抖動的激活,,使你的控件在非激活狀態(tài)也接受鼠標消息,,使你的控件異步的加載自己的屬性。這里有一個每個選項如何影響ControlWizard生成的代碼的綱要,。 無窗口的激活此選項覆蓋COleControl::GetControlFlags,,將windowlessActivate標志附加到控件標志中。一旦使此選項有效,,包容器就將輸入消息送交到控件的IoleInPlaceObjectWindowless接口,。此接口ColeControl的實現(xiàn)通過你的消息映射分發(fā)消息。你就能通過簡單的添加相應(yīng)的入口到消息映射表,,像處理一般windows消息那樣處理消息了,。 無省略的設(shè)備上下文選擇了此選項覆蓋COleControl::GetControlFlags,關(guān)閉clipPaintDC位,,從而在ColeControl的 OnPaint函數(shù)中去掉了IntersectClipRect調(diào)用,。如果你確定你的控件并不需要在客戶區(qū)外部繪圖,這個選項就有用了,,因為使對IntersectClipRect的調(diào)用失效后,,有一個明顯的速度的提高。 無抖動的激活選擇此選項覆蓋COleControl::GetControlFlags,,將缺省控件標志與noFlickerActivate逐位相或,。控件在激活的時候檢查此標志以阻止控件在激活和非激活狀態(tài)轉(zhuǎn)換時被重畫,。如果你的控件在激活和非激活狀態(tài)外觀一樣,那么這個選項就是有用的,。 非激活時的鼠標指針通知這個選項覆蓋COleControl::GetControlFlags,,附加了pointerInactive位。IpointerInactive接口使得一個對象大多數(shù)時間保持非激活,,然而仍然參加與鼠標的某些操作的交互,,例如拖放。 優(yōu)化的繪圖碼這個選項覆蓋COleControl::GetControlFlags,,打開canOptimizeDraw位,,具有優(yōu)化繪圖代碼的控件檢查這個標志(通過COleControl’的IsOptimizedDraw函數(shù))來確定控件是否需要在完成繪畫后將舊的對象復(fù)原回設(shè)備上下文,。 異步加載屬性此選項將stock ReadyState屬性和stock ReadyStateChange事件加入到控件中去。這將使得控件異步的加載其屬性,。例如,,一個加載大量的數(shù)據(jù)作為其屬性之一的控件會需要很長的時間來加載,而鎖住了控件,。這個stock屬性和事件使得此控件立刻開始加載過程,。包容器使用此事件和屬性判斷控件何時完成加載。 當ControlWizard完成這些事情后,,你就得到了編譯到一個包含此控件的DLL的源代碼(擴展名是.OLX),。由wizard產(chǎn)生的源代碼包括一個從ColeControlModule(它是從CwinApp中派生的)中派生的類。這個類包含整個控件模塊的初始化代碼,。接著,,wizard為基于ColeControl的表示每個控件的類生成源代碼。最后,,wizard生成一些ODL代碼用來建立類型信息,。 一旦wizard產(chǎn)生了控件DLL,你就面臨完善這個控件的任務(wù)了,。這意味著添加渲染代碼,,開發(fā)一個引入接口(方法和屬性),rigging up屬性頁,,展示某些事件,。但是,在我向你說明所有這些都是如何工作之前,,我們先來看一下使用ATL創(chuàng)建一個控件都需要什么,。 用ATL開發(fā)一個控件 有了基于MFC的控件,你就可以用ATL COM App Wizard得到一個開發(fā)基于ATL的控件的觸發(fā)器,。使用ATL來創(chuàng)建控件可以分為兩步,。雖然MFC的Control Wizard要求你預(yù)先確定你希望在DLL中包含多少個控件,ATL COM Wizard簡單的創(chuàng)建DLL——你可以以后使用ATL對象選項從Insert菜單添加控件,。當創(chuàng)建一個新的基于ATL的DLL時,,你可以選擇混用MFC支持。你還可以選擇在控件的DLL中合并任何proxy/stub代碼,。這使得如果有人希望遠程控制你的控件實現(xiàn)的代碼,,你只要發(fā)布一個文件。 一旦生成了基于ATL的DLL,,你就可以開始添加COM類了,。Insert New ATL Object菜單條使得這項工作變得十分容易。選擇此菜單項顯示一個用來創(chuàng)建任何一個COM類的對話框,包括無格式的COM對象,,ActiveX控件以及Microsoft事務(wù)服務(wù)器組件(Windows NT Server的一部分),。 當添加基于ATL的控件到你的工程的時候,ATL Object Wizard比MFC Object Wizard提供了更大范圍的選項,。對于新手來說,,ATL使得你可以選擇使用任何現(xiàn)有的線性模型實現(xiàn)你的控件。你可以將你的類標記為或者單線程或者單元線程的,。ATL Object Wizard限制你創(chuàng)建一個自由的或者混合線程的控件,,因為控件通常是面向UI的。 如果你創(chuàng)建了一個單線程的控件,,一個包容的控件的客戶將總是將它加載到主,、單線程的單元(STA)中。結(jié)果,,只有在客戶進程空間運行的單主線程才會接觸到你的對象,,這樣就免除了你保護你的控件狀態(tài)不受并發(fā)訪問干擾的責(zé)任。另外,,因為你的對象的所有實例將只會被一個線程接觸,,你將不必擔(dān)心DLL中的全局數(shù)據(jù)。如果你的控件是單元線程的,,你還免除了保護你的控件的內(nèi)部狀態(tài)的大部分負擔(dān),。然而,你仍然不得不保護DLL中的全局數(shù)據(jù),。為什么呢,?首先,設(shè)想你的控件是由客戶的單線程創(chuàng)建的?,F(xiàn)在假定客戶試圖創(chuàng)建該控件的另一份拷貝——但是是從一個云向在當前進程的多線程的單元中,。通過將你的對象標記為單元線程的,COM被告知你希望你的控件保護免遭并發(fā)訪問,。COM在它加載時為你的控件創(chuàng)建一個新的STA?,F(xiàn)在當線程調(diào)用到你的對象時,它們只能通過單元邊界來訪問它,,遠程層將同步對此對象的調(diào)用,。然而,當某個控件的狀態(tài)被保護不被并發(fā)訪問,,作為在一個STA中的副產(chǎn)品,,由控件的實例所共享的數(shù)據(jù)(象在DLL中的全局數(shù)據(jù)一樣)是脆弱的。這是因為你的全局DLL數(shù)據(jù)(同時為幾個對象服務(wù),,分別運行在獨立的線程中)會被那些多線程同時接觸到。 雖然基于MFC的COM類總是可聚合的(內(nèi)置了對它的支持),ATL ObjectWizard使得你可以指定你的控件支持聚合,,只是可聚合的,,或者是獨立的對象。根據(jù)你選擇的聚合選項,,ATL ObjectWizard使用一個宏來執(zhí)行聚合策略,。例如,缺省的COM類的實現(xiàn)是可聚合的——對象將既運行在獨立的模式,,又作為一個聚合的一部分,。如果你使你的COM對象不可聚合,ObjectWizard把DECLARE_NOT_ AGGREGATABLE宏加到你的類定義中,。如果你選擇了僅是可聚合的,,ObjectWizard把DECLARE_ ONLY_AGGREGATABLE宏加入到類定義中。 這里是宏如何工作的,。缺省的對象創(chuàng)建在一個名為_CreatorClass的類中發(fā)生,。_CreatorClass當被加入到服務(wù)器范圍的對象映射后(這是OBJECT_ENTRY宏所做的工作的一部分),就成為你的COM類的創(chuàng)建機制,。_CreatorClass其實只是一個名為CComCreator2類的別名,,此類將兩個從CcomCreator類中定制的類作為參數(shù)。此宏根據(jù)選擇的聚合模式來特制CcomCreator類,,分別使用CComObject, CComAggObject, CComFailCreator, 或者CcomPolyObject:
ATL ObjectWizard Attributes頁中最后三個檢查框包括對COM例外處理的支持(例如,,IsupportErrorInfo接口),連接點以及自由線程集(FTM),。你也可以添加IsupportErrorInfo到控件的繼承列表中,,提供ISupportErrorInfo::InterfaceSupportsErrorInfo的一個實現(xiàn)。打開連接點將添加IConnectionPointImpl 模板類到控件的繼承列表中,。 將你的控件聚合到FTM使得單元間(以及Windows 2000的上下文間)的調(diào)用更為頻繁的發(fā)生,,如果兩個對象正好位于同一個進程中。然而,,你在編寫控件時不應(yīng)該檢查這一點,,因為當你使用FTM的時候,你多少都違反了單元(以及Windows 2000的上下文)規(guī)則,。 除了你可以應(yīng)用到所有COM對象的一般選項,,ATL ObjectWizard還提供了幾個控件創(chuàng)建特定的選項。首先,,ATL ObjectWizard讓你從一個常規(guī)控件(例如一個按鈕或是一個編輯控件)中進行子類劃分,。你可以為你的空間指定其它幾個選項使得它更加不透明,給它一個更實心的背景,,在運行時不可見,,或者是你的控件象一個按鈕那樣的工作,。下面是控件屬性頁提供的選項的一個大綱: 不透明和實心背景如果你希望使所有的包容器都不顯示在控件邊界之后,選擇"opaque"檢查框,,這是控件傳給它的包容器的狀態(tài)信息,。結(jié)果是,控件在說明它將畫出它的完整矩形,。選擇此選項設(shè)置VIEWSTATUS_OPAQUE位以便IViewObjectExImpl::GetViewStatus向包容器指示一個不透明的控件,。你可能還想選擇一個實心的背景。這個選項設(shè)置VIEWSTATUS_ SOLIDBKGND 位以便GetViewStatus指示控件有一個實心的背景,。 運行時不可見此選項使你的控件在運行時不可見,。你可以使用不可見控件在后臺完成某些操作,例如周期性的激發(fā)事件,。此選項在它加入到注冊表中后使得控件翻轉(zhuǎn)OLEMISC_INVISIBLEATRUNTIME 位,。 仿按鈕此選項使你的控件象一個按鈕那樣工作。此時,,控件將在包容器周圍屬性DisplayAsDefault的基礎(chǔ)上顯示為缺省的按鈕,。如果控件的位置標記為缺省按鈕,控件將顯示為一個較厚的框架,。選擇此選項在它加入到注冊表中后使得控件翻轉(zhuǎn)OLEMISC_ACTSLIKEBUTTON ,。 仿標簽選擇此選項使得你的控件取代包容器的內(nèi)部標簽。這使得控件在它加入到注冊表中后標記OLEMISC_ACTSLIKELABEL,。 在超類基礎(chǔ)上添加控件選擇此選項使得你的控件根據(jù)一種標準window類進行子類劃分,。下拉列表包含了Windows定義的window類。當你選擇這些類名中的一個時,,向?qū)砑右粋€CcontainedWindow成員變量到你的控件類中,。CContainedWindow::Create將你指定的window類超類化。 規(guī)格化DC選擇此選項使得你的控件在被調(diào)用來畫自己時創(chuàng)建一個規(guī)格化的設(shè)備上下文,。這標準化了控件的外觀,,但是效率降低了。此選項生成的代碼覆蓋了OnDrawAdvanced方法(而不是常規(guī)的OnDraw方法),。 可插入的選擇此選項使得你的控件顯示在象Microsoft Excel 和Word 這樣的應(yīng)用的Insert Object對話框中,。你的控件就能夠被插入到任何支持嵌入對象的應(yīng)用中了。選擇此選項在注冊表項中增加了Insertable鍵,。 僅為窗口化的選擇此選項迫使你的控件窗口化,,即使在支持無窗口對象的包容器中。如果你不選擇此選項,,你的控件將會自動的適應(yīng)包容器:在支持無窗口對象的包容器中是無窗口的,,在不支持無窗口對象的包容器中是有窗口的。這將使CComControlBase::m_bWindowOnly標志設(shè)置為TRUE,。ATL使用此標志來決定在控件激活過程中是否要查詢包容器的IoleInPlaceSiteWindowless接口,。 ATL要求你預(yù)先在Stock Properties頁中決定你的對象的stock屬性,,你可以選擇Caption或者 Border Color這樣的屬性,或者通過點擊>>按鈕一次性選擇所有的stock屬性,。這將向控件的屬性映射中添加屬性,。 在運行ATL COM App Wizard 和 ObjectWizard之后,你就得到了一個完整的DLL,,它具有一個COM DLL所必需的所有分支。此控件顯示的眾所周知的出口包括DllGetClassObject, DllCanUnloadNow, DllRegisterServer,和 DllUnregisterServer,。另外,,你得到了一個滿足COM主要需求的對象——包括一個主流入接口和一個類對象。 一旦你已經(jīng)使用一個向?qū)ч_始了一個工程,,下一步就是使控件做點有趣的事情了,。通常出發(fā)點是控件的翻譯代碼。你立刻得到一些可視化的反饋,。讓我們來看一下一個基于MFC的控件的翻譯是怎樣發(fā)生的,。 控件翻譯 MFC和ATL在翻譯處理上是相似的。在每一個框架里,,實現(xiàn)控件的類具有一個名為OnDraw的虛函數(shù),。你只需將你的翻譯代碼添加到OnDraw函數(shù)里。然而,,在各框架里,,OnDraw函數(shù)得工作有所不同。 MFC的OnDraw在兩種上下文下調(diào)用,。第一個上下文發(fā)生在控件響應(yīng)一個WM_PAINT消息時,。此時,傳遞給OnDraw函數(shù)的設(shè)備上下文代表了真實的設(shè)備上下文,。如果控件正被要求render它自己作為對客戶調(diào)用IViewObjectEx::Draw的響應(yīng),,設(shè)備上下文或者是一個元設(shè)備上下文,或者是一個常規(guī)設(shè)備上下文,。下面的代碼說明了基于MFC的控件是怎樣被render的:
COleControl::OnDraw的簽名包括一個代表控件大小的矩形和一個代表控件非法區(qū)域的矩形,。MFC調(diào)用控件的OnDraw函數(shù)來響應(yīng)一個WM_PAINT消息。此時,,OnDraw函數(shù)接受一個真實的設(shè)備上下文來繪圖,。MFC還調(diào)用控件的OnDraw函數(shù)來響應(yīng)IViewObject::Draw中的一個調(diào)用。MFC的實現(xiàn)調(diào)用COleControl::OnDrawMetafile,,它的缺省OnDrawMetafile調(diào)用COleControl::OnDraw,。當然,這暗示了控件的實時翻譯是與控件的元文件表示相同的,,該元文件在設(shè)計時與包容器一塊存儲,。你可以使得控件的實時的翻譯與設(shè)計時的翻譯不同,,這通過重載COleControl::OnDrawMetafile來實現(xiàn)。通過調(diào)用你的控件的InvalidateControl方法,,你可以強制進行一個重繪,。ATL的翻譯機制非常類似于MFC。CComControlBase::OnPaint建立一個ATL_DRAWINFO結(jié)構(gòu),,包括創(chuàng)建一個繪圖設(shè)備上下文,。然后ATL調(diào)用控件的OnDrawAdvanced函數(shù)。OnDrawAdvanced生成元文件,,接著調(diào)用你的控件的OnDraw方法,,它使用ATL_DRAWINFO結(jié)構(gòu)中的信息來知道怎樣在屏幕上繪圖。下面是ATL_DRAWINFO結(jié)構(gòu):
ATL為你填寫此結(jié)構(gòu),。當你正在屏幕上繪圖時,,你所感興趣的最重要的域是hdcDraw 和 prcBounds。如果你對在一個元文件里繪圖感興趣,,或者你需要注意縮放因子等等,,那么其它域也是重要的。下面的代碼顯示了基于ATL的消息流控件是怎樣處理繪圖的:
注意當你使用ATL的時候,,你必須處理設(shè)備和GDI句柄,。在ATL中,你調(diào)用你的控件的FireViewChange函數(shù)來強制控件的一次重畫,。 開發(fā)一個流入接口 當開發(fā)一個基于MFC的ActiveX控件時,,缺省的流入接口是一個分發(fā)接口。Visual C++ 和 MFC使得開發(fā)一個流入分發(fā)接口變得十分簡單——只需使用ClassWizard來生成方法和屬性,。每次你使用ClassWizard添加一個新的屬性或方法,,它就插入一個入口到你的控件的分發(fā)映射中。MFC使用分發(fā)映射來滿足客戶的調(diào)用請求,。 MFC的缺點是在你的控件中增加一個常規(guī)的COM接口是一個枯燥無味的過程,。此過程包括使用MFC的COM宏來建立實現(xiàn)接口的嵌套的類。 當為你的基于ATL的COM控件開發(fā)主流入接口時,,類視是添加屬性和方法的最好的手段,。一當你為控件生成了代碼,ATL ObjectWizard即添加一個缺省的流入接口,。這可以是一個雙端接口,,也可以是一個常規(guī)的自定義接口,取決于你先前設(shè)定的工程選項,。 Visual Studio的類視向你顯示了你的工程中包含的所有的類和接口,,在類視中右擊一個接口的定義時,即可添加一個屬性或者方法,。使用類視來定義接口非常方便,,因為每次你添加一個方法或者屬性的時候,,類視都會更新IDL,類源代碼以及頭文件,。 不象MFC,,給控件添加一個常規(guī)COM接口是非常容易的。在ATL中,,你只要簡單的添加新的接口樣板文件連接(goo)(一個GUID,,關(guān)鍵字對象和關(guān)鍵字接口)。類視將會顯示新的接口,,你可以繼續(xù)添加新的成員,。 添加屬性 一個ActiveX控件經(jīng)常包含屬性,它們是描述控件的狀態(tài)的成員變量,。給一個基于MFC的控件添加屬性的最好的手段是利用ClassWizard。ClassWizard的自動為你添加成員變量,,將它們映射到缺省的分發(fā)接口,。ClassWizard給你提供了兩種選擇:你可以添加一個成員變量,包括一個變化通告函數(shù),,或者你可以添加一對Get/Set函數(shù),,手動的添加成員變量。除了給控件添加你自己的定制屬性,,ClassWizard使你象添加背景和標題一樣的添加庫存,。ClassWizard甚至自動為你的類添加一個成員變量。 為一個基于ATL的控件添加屬性有一點不同,,你為控件中的每個屬性添加單獨的存取程序和變異因子函數(shù)(propget 和 propput函數(shù)),。然而,類視只是定義了接口函數(shù),。你還要手工添加數(shù)據(jù)成員到類中,,然后簡單的實現(xiàn)這些函數(shù)。 基于ATL的控件還支持stock屬性,,ATL ControlWizard預(yù)先要求你確定希望哪些stock屬性包括在你的控件中,。添加至少一個stock屬性到控件中使得控件繼承自ATL的CstockPropImpl類。CstockPropImpl是Idispatch的一個實現(xiàn),,優(yōu)化來顯示ActiveX控件的stock屬性,,為每個標準的stock屬性包含了兼容Idispatch的get 和 put函數(shù)。 ControlWizard還給控件添加代表stock屬性的數(shù)據(jù)成員,,例如,,如果你添加了背景顏色的stock屬性,ControlWizard添加一個名為m_clrBackColor的數(shù)據(jù)成員到你的類中,。CstockPropImple一次性的為所有標準的stock屬性的get 和 put函數(shù)添加實現(xiàn),。所有這些函數(shù)期望在你的類中看到合適的成員變量(象對應(yīng)背景顏色的m_clrBackColor),。 編譯器將在stock屬性沒有包括的那些get和put函數(shù)上阻塞。實現(xiàn)過程希望在你的類中看到成員變量,。為了消除編譯器錯誤,,CcomControlBase添加了一個聯(lián)合結(jié)構(gòu),它包括了stock的get 和 put函數(shù)希望看到的所有成員的名字,。然而,,給控件添加數(shù)據(jù)成員重載了聯(lián)合類型中的名字,CstopPropImpl類在它的get 和 put函數(shù)中使用控件的成員變量 如果你忘記了使用ControlWizard預(yù)先添加stock屬性,,你總可以手工添加相關(guān)代碼——即,,從CstockPropImpl繼承,然后為你想要顯示的屬性添加成員變量,。 屬性持續(xù) MFC的屬性持續(xù)機制是非常直觀易懂的,。從編程的觀點來看,所有你要做的是填寫ControlWizard已經(jīng)提供的DoPropExchange函數(shù),。DoPropExchange將控件屬性的狀態(tài)從某些成員變量移動到持續(xù)媒體中,。 MFC具有3個屬性持續(xù)機制,內(nèi)置于ColeControl:IPersistPropertyBag, IPersistStorage和 IPersistStream[Init],。所有這些持續(xù)機制都封裝在MFC的CpropExchange類中,,與當你需要serialize 一個文檔時Carchive為你包裝一個文件非常相似??蛻舴竭x擇使用3個接口中的一個保持對象,。不管使用了哪種持續(xù)機制,執(zhí)行總落在控件的DoPropExchange函數(shù)中,。 下面的代碼顯示了MFCMsgTraffic控件是怎樣將它的顏色和時間間隔屬性保存起來的:
MFC包括了若干PX_函數(shù)在控件和存儲媒體間轉(zhuǎn)移數(shù)據(jù),,它們是:
在ATL中管理控件屬性持續(xù)涉及到兩個步驟。第一步是添加你希望客戶能夠使用的持續(xù)接口的ATL實現(xiàn),。ATL包括了類IPersistStorageImpl, IPersistStreamInitImpl, 和 IPersistPropertyBagImpl, 它們實現(xiàn)了三個主COM持續(xù)機制,。 第二步是在控件的屬性映射中插入屬性。當一個客戶請求保存或者加載基于ATL的控件時,,ATL檢查控件的屬性映射表,,將控件的屬性輸出到存儲媒介,或者從存儲媒介輸入,。屬性映射表是屬性名字,、DISPIDs的一個表,有時還包括一個屬性頁面GUID,。ATL遍歷詞表查找該持續(xù)哪個屬性,,并將其持續(xù)到合適的媒體。圖5顯示了繼承了持續(xù)接口實現(xiàn)和一個屬性映射的ATLMsgTraffic控件。 屬性頁 ActiveX控件經(jīng)常在開發(fā)者將控件放到各類容器時提供屬性頁幫助開發(fā)者,。將消息流控件放入一個對話框的開發(fā)者可能想要配置控件的各個方面,,象控件的取樣間隔,或者繪圖線條的顏色,。例如,,當控件放在一個對話框中,你想通過右擊鼠標得到控件的屬性時,,Visual Studio顯示了一個突出的對話框,。這里將說明其工作過程。 Visual Studio請求控件在一個對話框框架里顯示屬性頁(Visual Studio IspecifyPropertyPages接口請求控件提供一個屬性頁的清單),,屬性頁顯示在Visual Studio中,,但是通過控件提供的一個COM接口,保持與控件的連接,。每當你完成了屬性編輯并從Visual Studio中關(guān)閉了對話框,,它就會要求屬性頁更新控件中的屬性。 當你生成一個MFC的控件時,,wizard給你一個對話框模板和一個從ColePropertyPage中派生的代表此控件的缺省屬性頁的類,。Visual Studio使得實現(xiàn)一個控件的屬性和此屬性頁中的屬性的連接變得容易了。當你使用ControlWizard的Automation tab添加屬性到你的基于MFC的控件中的時候,,你給了屬性一個外部名字。這個名字是外部客戶方(包括屬性頁)用來識別該屬性的,。 你按照開發(fā)其它任何對話框的方法來開發(fā)屬性頁——將控件添加到對話框模板,,將對話框成員變量和控件聯(lián)系起來。ControlWizard添加DDX/DDV代碼在對話框控件和成員變量之間交換數(shù)據(jù),。然而,,當你將成員變量和對話框控件相關(guān)聯(lián)時,ControlWizard給你提供了這樣一個機會,,你可以將外部屬性名字用于對話框的成員變量,。此外部名字是你給控件添加屬性時鍵入的字符串。 當屬性頁需要將變化應(yīng)用于控件時(例如當按下Apply按鈕時),,屬性頁使用控件的Idispatch接口以及外部名字來修改控件的屬性,。在MFC中,你可以通過ClassWizard來添加一個新屬性,,添加一個新的對話框模板到工程中,,讓ClassWizard創(chuàng)建一個類——要確保是從ColePropertyPage中派生出來的類。然后,,為了使新的屬性頁可以被外界訪問到,,將它的GUID添加到控件的屬性頁映射中(在控件的.CPP文件中查找BEGIN_ PROPPAGEIDS 和 END_PROPPAGEIDS兩個宏)。不象MFC的ActiveX ControlWizard,,ATL COM App Wizard并不向DLL中添加缺省的屬性頁,。這意味著你要自己完成此工作,。幸運的是,又一個wizard可以向?qū)傩皂撝刑砑踊贏TL的DLL,。只要選擇Insert ATL Object,,然后找到屬性頁對象。Wizard將一個對話框模板和一個C++類與所有必要的COM goo一起添加到一個屬性頁中,。讓它們完成什么工作是你的事情,。 不幸的是,ATL屬性頁的wizard驅(qū)動特性不如基于MFC的屬性頁,,你得手工完成應(yīng)用和顯示操作,。這就意味著提供Apply 和 Show的函數(shù)實現(xiàn)到你的屬性頁類中。Apply函數(shù)只是提取對話框中控件的狀態(tài),,遍歷屬性頁擁有的指向控件的接口指針列表,,使用接口指針來修改控件屬性。Show函數(shù)通常提取控件的狀態(tài),,然后以次來組織對話框的控件,。下面的代碼顯示了基于ATL的屬性頁是怎樣處理Apply函數(shù)的:
為基于ATL的控件提供一個屬性頁的第二步是確保屬性頁的CLSID出現(xiàn)在控件的屬性映射中,圖5中列出的屬性持續(xù)代碼提供了它的一個例子,。消息映射表明了控件的圖線顏色,,被標準的顏色屬性頁管理??丶娜娱g隔由控件的主屬性頁來管理,。 Window 消息 MFC和ATL在它們處理window消息方面有很多共同之處,都使用消息映射,,都有wizards來生成代碼處理window消息,。在MFC中,消息映射可以添加到任何一個CcmdTarget派生的類中,,然后你就可以用ClassWizard來建立你的控件的事件處理器了,。圖6顯示了基于MFC的控件怎樣處理WM_ TIMER消息。另外,,MFC提供了處理命令和控件通告的宏,。象MFC一樣,ATL通過消息映射來處理window消息,,只要你的類是從CwindowImpl派生的,,而且包含ATL的消息映射宏,你就可以使用類視來建立事件處理器,。圖7顯示了ATL消息流控制是怎樣處理WM_TIMER消息的,。 ATL使用MESSAGE_HANDLER宏將標準的window消息映射到一個C++類。此宏簡單的產(chǎn)生一個將window消息和類的成員函數(shù)關(guān)聯(lián)的表。除了常規(guī)消息,,消息映射還可以處理其它類型的事件,。圖8顯示了能參與消息映射的各種宏。 連接和事件 最后要進行的比較是MFC和ATL各是怎樣處理連接點和事件集的,。為了管理連接點和事件集,,需要一個COM類來實現(xiàn)IconnectionPointContainer,然后創(chuàng)建一種提供指向IconnectionPoint的指針給客戶的方法,。MFC的主控件類,,ColeControl,已經(jīng)有了內(nèi)置的IconnectionPointContainer,,MFC通過連接映射提供了連接點,。MFC已經(jīng)為IPropertyNotifySink定義了連接點和控件的缺省事件集。 為了完善一個基于MFC的控件的缺省事件集,,你只要簡單的使用ClassWizard’s ActiveX Event tab,。在你使用ClassWizard添加事件的時候,Visual Studio更新你的控件的.ODL文件,,為潛在的包容器描述外出事件,。另外,Visual Studio添加一個函數(shù)到你的類中,,你可以調(diào)用它反過來向包容器激發(fā)事件,。圖9演示了基于MFC的控件的事件觸發(fā)機制?;贛FC的控件的事件觸發(fā)函數(shù)只是由包容器在它和控件建立連接點時提供的一個Idispatch指針的一些簡單的包裹器,。 在基于ATL的控件中建立事件則有所不同。在基于ATL的控件中,,你從定義控件的.IDL文件中的事件開始。接著你建立了類型庫的編譯工程文件,。圖10顯示了在IDL中描述的基于ATL的控件的事件集,。 一但類型庫編譯通過,你就可以通過在類視中選擇控件的類,,在類上右擊,,然后選擇Implement Connection Point,讓類視來為你創(chuàng)建一個回調(diào)代理了,。Visual Studio彈出一個對話框,,列出控件類型庫中所有可訪問的事件接口。你選擇那些你希望回調(diào)代理做的,,Visual Studio就為你寫一個代理,。圖11顯示了基于ATL的消息流控制的回調(diào)代理。Visual Studio產(chǎn)生的回調(diào)代理代表了一個C++友好的函數(shù)集,被客戶實現(xiàn)的接口所調(diào)用,。 MFC的IconnectionPointContainer實現(xiàn)是硬分布到ColeControl中,,并且每個連接點是由一個連接映射處理的,而ATL的實現(xiàn)是用多重繼承處理的,。你的控件類繼承IconnectionPointContainerImpl和類視生成的代理,。如果你開始一個工程的時候,選擇了"Supports connection points",,ObjectWizard就為你添加IconnectionPointContainerImpl,。如果你忘了標記檢查框,你可以寫進去,。此代碼顯示了連接點機制是怎樣加入一個控件中的,。類ATL_NO_VTABLE CATLMsgTrafficCtl :
作為一個應(yīng)用框架ATL和MFC的比較 最近,許多開發(fā)者開始對使用ATL作為框架來開發(fā)應(yīng)用和控件感興趣了,。當然,,MFC已經(jīng)使用了很長時間了,是一個能夠開發(fā)可雙擊的基于Windows的應(yīng)用的非常成熟的框架,。例如,,MFC包括了這樣的特性作為一個總體文檔/視體系結(jié)構(gòu):Object Linking 和 Embedding支持, 以及工具條和狀態(tài)欄。 然而,,所有這些功能都是有代價的,。一些更加普遍的抱怨是MFC的比較深的足跡(無論是在DLL中,或是在靜態(tài)連接的版本中),,以及自身的某種相互依存性,。例如,,買入MFC的一種特性意味著買入MFC的對象連接和嵌入,,這意味著買入MFC的文檔/視結(jié)構(gòu)。另一方面,,ATL是一個原始的框架,,沒有任何應(yīng)用框架goodies。 正象你已經(jīng)看到的,,兩個框架都提供創(chuàng)建控件的可行途徑,。然而,兩者都各有千秋,,也各有弊病,。用MFC編寫控件通常更加容易——尤其是如果你不是在開發(fā)COM集中的應(yīng)用并且你需要windowing和drawing支持。ATL的體系架構(gòu)更加靠近COM的核心,,你還會經(jīng)常發(fā)現(xiàn)自己在編寫很多的SDK類型的代碼——就是說,,你在回過頭來用window和設(shè)備上下文句柄,。ATL為更廣范圍的控件類型提供了很大的支持,象復(fù)合控件,,基于HTML的控件,,沒有design-time接口的輕量級的控件等等。MFC僅提供完全成熟的控件,。 對ATL進行分支的實現(xiàn)是非常直接的,。例如,增加一個接口通常是一件添加接口到繼承表,,在COM映射中添加一個入口然后實現(xiàn)接口函數(shù)的工作,。分支MFC的實現(xiàn)通常是一種折磨。例如,,添加一個接口到基于MFC的控件意味著處理所有那些接口映射宏,。 最后,ATL提供了大量的調(diào)試支持,,包括接口引用計數(shù)以及QueryInterface調(diào)試支持,,這在MFC中是沒有的。 這兩種體系架構(gòu)的區(qū)別是非常明顯的,。通常,,MFC使得你很快完成你的工程并更快的運行起來,但是犧牲了靈活性,。ATL沒有那樣快,,那樣容易使用,但是它是COM友好的,。而且,,好像隨著ATL的成熟,它將會越來越容易使用,。 |
|