ATL之深入淺出書評(潘愛民) (轉)

worldblog發表於2007-12-14
ATL之深入淺出書評(潘愛民) (轉)[@more@]

ATL之深入淺出

介紹一本關於ATL的書《ATL Internals》

潘愛民,5月7日,2000年

北京大學研究所,100871

引言

面對計算機圖書市場的繁榮景象,我經常感嘆今天學習計算機開發技術的同道們是多麼幸運。十年前,我們學習計算機語言非常不容易,要掌握各種開發工具只有靠自己的摸索和極少量的參考手冊。我記得,94年我學習Visual C++和MFC的時候,基本上只有靠自帶的聯機幫助;現在情形大不同了,書店中的計算機圖書琳琅滿目,關於Visual C++和MFC的書籍尤其多。有幾位編輯朋友勸我寫一點這方面的書,我覺得不大有必要了,因為Visual C++的好書已經不少了,適合各種讀者層次的書籍幾乎都可以買到,而且有一些書還相當不錯。不過,在98年的時候,我感覺關於COM書籍實在太少,幾乎沒有,於是我下定決心,自己寫一本關於COM的書籍,在99年底的時候由清華出版社出版。很快地,關於COM的書籍已經很多了,有些國外的名著也相繼引入國內。從這十多年的計算機開發技術歷史來看,一門技術只要有很多的書籍來介紹,那麼這項技術很快就會普及,否則就難以推廣。

說到COM,相信在平臺上有過開發的朋友一定接觸過,它是Windows操作的基本軟體模型,從93年建立以來,為Windows平臺的推廣和發展做出了不可磨滅的貢獻,而且其自身還在不斷髮展。但是要真正開發COM並不是很輕鬆,在Visual C++中,我們既可以使用MFC也可以使用ATL。MFC完全面向Windows應用,它用C++的封裝技術建立了一套適合於開發Windows應用的C++類庫,雖然在後期的版本中MFC提供了大量的COM支援,但是從基本的設計結構上講,MFC不適合於開發專業的COM元件,它適合於在Windows應用的基礎上提供相應的COM支援。

ATL則不同於MFC,它完全面向COM元件,其技術路線也不同於MFC,MFC使用的是C++中的繼承、封裝、巢狀等常規技術,而ATL使用了C++中模板、多繼承等高階技術,甚至還用到了STL。所以學習和使用ATL要求我們必須熟悉這些C++高階特性。另一方面,ATL結構完全針對COM中的諸多規範,這就要求使用人員必須非常瞭解COM規範,才有可能真正把ATL用好。

雖然目前關於MFC的書籍很多,但是完全介紹ATL的書籍非常少,甚至根本沒有,這不能不說是一個遺憾。我有幸在今年2月份看到一本ATL的英文原版書《ATL Internals》,本文將為大家介紹這本書。

在看這本書之前,我對ATL已經有了基本的瞭解,98年底由於寫作的需要,我曾經讀過ATL的部分原始碼,對於ATL的基本結構還算清楚。我剛開始看到這本書的時候,讀了一章,並沒有感覺這本書有多好,後來由於工作忙碌的原因,一直沒有得閒,直到最近,我才仔細把這本書讀了一遍,感覺這是一本不可多得的好書。以前我很少仔細閱讀開發技術類的書籍,一則是由於自己讀書太慢,二則是往往開發技術類的書籍不大值得精讀。但是這本書我讀得很仔細,因為這本書把ATL的精華幾乎全表述出來了,ATL中的許多內容都能讓你為之心動,作為一個員,這也是一個學習和提高的機會。

我寫這篇文章的意圖不僅僅是向讀者介紹這本書,我也希望能夠把我在閱讀過程中的心得與大家分享。同時我還希望能夠按照這本書的路線,向大家介紹一下ATL的結構和機理。

準備閱讀

《ATL Internals》由Addison-Wesley出版社出版,作者為Brent Rector和Chris Sells,出版時間為1999年2月,全書600多頁。關於這本書的背景知識可能對於許多COM迷和ATL迷來說很有意思。首先,這本書的序言是由ATL的發明人Jim field所撰寫,在序言中,Jim介紹了ATL的歷史,對於書中所介紹的內容大加讚賞,稱讚“閱讀此書可以學到許多閱讀原始碼所不能掌握的內容”,在序言的最後,Jim還談到了ATL的將來。

其次,在作者寫的自序中,他們提到了ATL離不開COM,要想掌握ATL,就一定要先掌握COM。ATL是一個產生C++/COM程式碼的,就如同C語言是一個產生程式碼的框架,這個觀點頗為新穎,仔細想來,確是如此。作者特別推薦了Don Box的書《Essential COM》,其實在英文書中,COM書籍不少,這顯示出兩位作者與Don Box的關係非同一般。我記得Don Box也曾經推薦過《ATL Internals》這本書,如果讀者有機會到亞馬遜網上書店()看看本書的書評就可以知道他們之間有很親密的關係,其中作者之一Chris Sells與Don Box以及另外兩人合作寫了一本COM的書《Effective COM》(本書文中有多處推薦了這本書)。我相信他們的互薦是基於相互之間絕對了解的基礎之上的,他們都是COM頂尖高手,也是ATL頂尖高手,讀者經常可以在MSJ( System Journal)雜誌上看到他們的文章。

誠如作者所言,閱讀此書需要極強的預備知識,按照我閱讀此書之後的理解,讀者在閱讀此書之前應該有以下幾方面的準備知識:

  1. 一定要懂COM。ATL完全針對COM,許多細節都是為了更好地實現COM而設計。如果讀者僅僅看過《Ins COM》(清華大學出版社出版的《COM技術內幕》),那麼要想通篇閱讀《ATL Internals》還不夠,建議讀者再找其他的資料看一看。

  2. 一定要懂C++的模板技術。ATL充分發揮模板的優勢,其整個體系結構完全建立在模板的基礎之上,如果讀者不熟悉模板,那麼幾乎無法閱讀ATL。

  3. 基本瞭解STL(可選)。ATL的集合類和列舉類用到了STL,當然,如果讀者不懂STL,基本不影響全域性的理解,但是STL中容器的思想和COM集合的思想是相通的,ATL把兩者有機地統一起來了。

所以說,《ATL Internals》是一本起點很高的書,原因在於ATL是一門起點很高的技術。據我所知,現在有許多程式設計師已經在使用ATL了,這是好現象,說明我們國內的程式設計師水平相當高,雖然我們的資料資訊還不夠豐富(至少對於ATL而言是這樣),但是我們仍然緊跟這些新的技術。儘管如此,要想真正用好ATL,一定要了解ATL的機理,這不同於MFC的情形。假如我們不懂MFC的機理,一樣可以做出很好的程式,利用MFC,在不懂OLE細節的情況下,一樣可以提供OLE的支援。ATL要求我們很細緻地調節它的類,根據需要選擇合適的模板類,必要時還要修正它的行為。ATL儘管已經到了3.0版本,但是仍然有不少的錯誤,這本書已經指出了一些錯誤,但我相信肯定還會有更多的錯誤,這對程式設計師提出了更高的要求,確實是這樣,因此結論是:使用ATL一定要懂ATL!

儘管我這樣說,但我還是認為ATL是一項好技術、是一個好的COM模板類庫。而且我也深感好書對於ATL程式設計師的重要性,有些東西是不能從原始碼和參考手冊獲得的,既然我看到了這本好書,那我應該把這本書介紹出來,讓大家知道這本書。也讓大家分享我的體會。如果有那家出版社能夠引進這本書的話,則是我們廣大ATL程式設計師的福音了。

內容介紹

下面我按照《ATL Internals》的敘述順序,逐項介紹ATL的內容,希望讀者不僅能夠了解本書的內容,也能夠藉此瞭解ATL的機理。原書共包括十一章,我按照每一章所介紹的內容把這十一章分成四個部分。

第一部分 ATL的使用和功能展示

這一部分篇幅很小,只有短短的一章內容,讀起來非常輕鬆。初時我以為整本書都是這樣的,所以感覺這也是一本指南性質的書,當時就沒有太重視。

在這一章裡,作者簡單介紹了ATL的概念,然後透過AppWizard和ATL Wizard建立COM服務程式和COM物件類的過程逐項介紹了Wizard中各個選項的含義。有了基本的工程框架和物件類框架之後,作者開始展示ATL的一些其他特性:

  • 新增屬性和方法。

  • 增加另外的介面。

  • 提供指令碼支援。

  • 提供永久特性,增加永久介面。

  • 加入事件支援,利用COM的可連線物件特性。

  • 對視窗的支援。

  • 實現元件類別(component category)支援。

  • 增加介面特性,用ATL開發控制。

  • 怎樣包容ActiveX控制。

作者介紹這些內容非常簡捷,但是能夠讓讀者有一些基本的印象,在閱讀後續內容之前對ATL有一個清晰的思路。如果讀者對COM比較熟悉而且曾經有過ATL開發經驗的話,讀這部分內容會非常輕鬆。

第二部分 ATL實現COM:基礎部分

這部分介紹了ATL實現COM的基本方法,包括四章內容,分別如下:ATL Smart Types、Objects in ATL、Servers、Interface Maps。這四章內容是ATL的精華所在,也是這本書的精華所在,如果說全書其他的內容都是可讀可不讀的話,那麼這部分的內容則是不可不讀的。

第二章介紹了ATL的智慧型別(smart type),包括字串型別、VARIANT型別以及介面指標型別。COM規範要求所有的字元必須用雙位元組字元,不是我們常用的ANSI字元,所以對於字串的處理往往是我們工作中所必須面對的任務,雖然談不上有多困難,但是往往要花掉我們不少時間來處理這些瑣事。作者在這一章首先介紹了與字元表達有關的許多概念以及多種轉換途徑,然後介紹了ATL的基本字元型別封裝類CComBSTR以及CComVariant。作者對於這兩個類的介紹非常細緻,指出了每個成員的一些細節,甚至個別缺陷。我在閱讀時,對這一部分很感興趣,雖然這些內容我基本上都知道,每個成員函式也可以透過ATL的原始碼得到其細節知識,但是讀下來仍然感覺受益匪淺。反過來,這一章最後部分介紹的智慧指標類,我的興趣並不大,大概是我對智慧指標一直存有偏見的原因吧,不過智慧指標在ATL中用得很廣泛,書中後面部分到處可見智慧指標的應用。ATL的智慧指標封裝類是一個比較典型的、功能全面的智慧指標模板類,有興趣的讀者可以讀一讀這兩個類(CComPtr和CComQIPtr)的原始碼。

第三章的開篇就是COM的套間(apartment)和COM的執行緒模型,雖然篇幅很短,但是這些內容很重要。因為ATL是一個支援多執行緒模型的COM類庫,為了支援多執行緒,代價是非常昂貴的,要求COM物件的每一個細節都要涉及到併發的可能性,ATL既要考慮到程式碼的大小,又要考慮到程式碼的執行,所以ATL用了許多技巧來保證其方案的有效性。介紹了執行緒模型之後,作者又講述了實現COM基本介面IUnknown的一些考慮要點。之後再給出ATL物件的層次圖,如果讀者對於ATL中類的結構還不瞭解的話,那麼這個層次圖可以讓你知道Wizard生成的類與其他的類有什麼樣的關係,這個圖可以指導你閱讀完後續的兩章。有了這些準備知識之後,作者詳述了ATL為物件提供的執行緒模型支援,限於本文的篇幅,我不能詳細講述這些內容。

講述了執行緒模型之後,作者進一步介紹COM物件的基類CComObjectEx實現IUnknown相關的方法,有了執行緒模型的基礎後實現引用計數非常簡單,但是QueryInterface成員函式不是基類就能夠完成的,它需要用到CComObjectRootEx派生類也就是Wizard生成的類所提供的介面資訊。ATL透過介面對映表的形式提供物件的介面資訊,透過多繼承的方式實現多介面的支援。

COM物件的例項化非常與眾不同,因為Wizard生成的類還只是一個抽象類,所以它不能夠直接被例項化,即我們不能用new運算子生成一個物件。真正的物件類應該是CComObject,它實現了IUnknown的所有方法,並提供了一個用於建立物件的函式。如果物件支援聚合模型的話,那麼最終的物件類應該是CComAggObject。為了統一兩種情況以便減小最終的程式碼量,ATL提供了CComPolyObject類作為最終的物件類。

ATL的建立過程並不複雜,但是它提供了多階段構造(multiphase construction),允許我們在建立過程中加入更多的控制程式碼,獲得更大的靈活性。作者對這一部分的介紹甚為細緻,還解釋了ATL_NO_VTBL宏即novtbl編譯指示符的含義,如果一個類宣告瞭novtbl指示符,那麼在派生類的構造過程中不為基類產生虛表(vtable)。

這一章的內容是ATL的基礎,讀起來並不難,但是一定要清楚ATL類的層次關係,否則很容易陷入ATL的複雜語法之中。

第四章介紹COM服務程式的ATL支援,作為一個COM服務程式,它的主要任務是管理物件的註冊、為每個物件提供一個類廠、以及自身的生存期管理。對於程式內元件和程式外元件還需要區別對待。ATL實現的物件分為可透過類廠建立的物件和不可建立的物件,不可建立的物件不需要類廠的支援,往往為服務程式中其他的物件所用。ATL實現這些功能主要透過物件對映表和CComModule類。

物件對映表是一個全域性表,其中包括當前服務程式所實現的所有物件類,表中的每一項包括物件的CLSID、註冊該物件資訊的函式、建立類廠的函式、建立物件的函式等等。有了這些資訊,服務程式就可以管理它所支援的每一個物件類。回過頭來,為了讓物件對映表管理好這些工作,每一個物件類也需要提供相應的函式或者資訊,比如物件的註冊函式、建立函式等。ATL的註冊功能很強,除了標準物件的註冊支援之外,它可以提取出內嵌在資源中的註冊指令碼(Registry Script File),實現更為靈活、功能強大的註冊操作。對於使用者來說,只要編寫資源指令碼再加上一個宏宣告即可。ATL對於類廠的支援在CComClasactory類中實現,物件類從CComCoClass繼承一個類廠建立類的定義_ClassFactoryCreatorClass。CComClassFactory類的實現沒有用到模板引數,而是內嵌一個建立函式,由該函式完成實際的建立工作。這個過程並不複雜,書中講得很清楚,而且書中還介紹了ATL實現IClassFactory2的方法。

CComModule是COM服務程式的主線,當我們建立一個ATL工程的時候,Visual C++都會為我們生成一個CComModule派生類,並且定義一個全域性變數_Module,這就如同我們在MFC工程中使用CWinApp應用類一樣。CComModule類的許多成員函式都對應了它所應該完成的任務,比如登錄檔的操作、獲取類廠物件、註冊類廠物件(對於程式外服務程式)等。

第四章介紹的內容對於我們理解ATL工程的總體思路非常有用,結合COM規範對COM實現中的所有細節要求,ATL給出了一種高效、針對小尺寸元件的實現方案。結合第三章的內容,就構成了ATL實現COM的基本技術框架。

第五章講述ATL的介面對映表,實際上這是對第三章內容的補充,但是因為ATL的介面對映表比較靈活,而且多介面支援對於COM物件非常重要。所以作者單獨用一章的篇幅來講述介面對映表。對於多介面的物件,COM有很嚴格的規範來指示客戶程式這些介面成員函式,特別是IUnknown::QueryInterface。為了遵循這些規範,並保持一定的靈活性,ATL使用了介面對映表的技術。介面對映表的原理非常簡單,它以表的形式記錄了每個介面的IID以及介面的vtable與物件類的this指標之間的偏移,但是ATL的介面對映表並沒有這麼簡單,它以函式的形式把這樣的邏輯封裝起來,從而允許使用者使用更為靈活的介面查詢策略。

ATL用多繼承的方法實現多介面的支援,如果兩個介面的方法名和引數重合的話,這時就會產生問題,書中介紹了一種避免名字衝突的方法,方法並不複雜,但很有效。這一章還介紹了一種被稱為“介面著色”的技術,其實很簡單,只不過是按照COM所要求的虛表結構另行構造而已,其好處是可以實現兩個介面語義完全相同但是IID卻不相同的介面。這也體現了COM介面實現的靈活性。

除了支援多繼承方式的介面之外,ATL有一個很強大的介面支援就是對於動態介面的支援,書中稱為“tear-off interface”。每一個動態介面類都應該從CComTearOffObjectBase派生,以後當客戶向物件請求該介面的時候,物件類會呼叫介面對映表中指定的建立函式建立該介面物件。

除了動態介面技術應用了介面對映表這種結構之外,還有物件類對聚合介面的支援,實現形式與動態介面非常類似,ATL對聚合的支援分有計劃聚合(planned aggregation)和盲聚合(blind aggregation),可以說,ATL對聚合的支援比較全面,但是我們在使用的時候一定要謹慎,COM中的有些特性往往隱含著潛在的出錯可能性,比如說盲聚合就是這樣的一種特性。

這一章最後介紹了介面對映表的一些訣竅,包括介面對映表的鏈結構、拒絕支援某個介面、利用介面請求進行、對介面對映表的擴充套件(比如,利用介面對映表設定,透過後門得到物件類的this指標;以及基於物件例項的介面請求)。這一章所講述的內容非常細節,涉及到COM規範中的許多細微的地方,讀懂這一部分並不難,但是要求讀者具有有關的COM背景知識。

以上四章內容是ATL的基石,即使把這一部分獨立出來也可以構成一本書“ATL深入淺出”,如果讀者要依靠ATL來編寫COM元件的話,那麼認真讀懂這一部分就可以奠定工作的基礎。如果有人說ATL使用C++語法非常花哨的話,那麼他們一定是指這一部分所講述的內容。由於C++模板語法本身的複雜性,加上ATL在許多模板類的定義中使用了“typedef”,再加上ATL也使用了類似MFC的宏結構,所以讀起原始碼來非常晦澀。儘管作者講述這一部分內容非常有條理,但我看這幾章的時候不免要前後翻動,偶爾還要檢視一下ATL的原始碼。但是一旦明白了ATL的思路,又不免為它的設計所折服。

第三部分 ATL實現COM:擴充套件部分

ATL實現COM的擴充套件部分包括三章內容,分別為:Persistence in ATL(ATL的永久特性支援)、Collections and Enumerators(集合物件和列舉器物件)、Connection Points(連線點物件)。這三章是面向COM應用層面的三個大方向,也是我們比較常用的一些COM特徵。如果讀者要全面掌握ATL的話,那麼應該讀一讀這部分。

第六章介紹ATL對COM永久機制的支援,相對來說,這一章內容的介紹讀起來要輕鬆得多,只要讀者對COM永久模型比較熟悉即可。由於COM永久模型的複雜性主要位於客戶程式一方,在物件一方只需要實現有關的幾個永久介面,當然這些永久介面與物件本身的邏輯是密切相關的。這一章前面部分回顧了IPersistPropertyBag、IPersistStream[Init]、IPersistStorage永久介面的定義和實現,然後介紹ATL對這些永久介面的實現,重點介紹了屬性對映表(Property Map)。ATL提供的永久介面的實現能夠自動對屬性對映表中的屬性進行永久處理,即提供Load和Save支援。對於屬性對映表不支援的永久內容(比如說書中所舉的屬性的例子),我們可以在適當的地方進行過載處理,ATL允許我們在多個地方過載這套機制。

在介紹了這幾個常用的永久介面之後,作者還介紹了IPersistMemory介面,並細緻說明了幾個永久介面公共的成員函式GetSizeMax的重要性以及作者補充的實現方法。在這一章的最後,作者還給出了一個用永久特性實現自定義列集(marshaling)的一種方法,如果讀者對自定義列集有興趣的話,可以看一看這一章最後幾頁的介紹。

第七章介紹了COM集合物件和列舉器物件(enumerator)的ATL實現。在講述集合和列舉器物件之前,作者先介紹了STL中的容器和迭代器(iterator),這是STL中資料組織和資料訪問的基本形式,然後作者以一個類比,指出雖然STL不能直接用於COM,但是COM提供了類似的物件組織和訪問機制,這就是COM集合物件和列舉器物件。

COM的集合物件是構成COM物件模型的基礎,為了在客戶程式一方特別是VB()或者VBA作為客戶程式時,它能夠方便有效地訪問集合物件,COM制定了集合物件的介面規範以及列舉物件的介面規範。ATL實現了這些規範,並且在ATL內部,還提供了多種途徑來管理這些成員資料或者成員物件。

ATL的集合物件實現起來比較簡單,只要按照COM規範,增加集合物件所特有的屬性:Count、Item、_NewEnum即可。_NewEnum屬性把集合物件和列舉物件聯絡起來了。在ATL中,列舉陣列類為CComEnum,它以陣列的形式管理其成員資料,值得一提的是,ATL在實現列舉介面的時候,為了方便對於資料的複製操作,專門抽象出一個被稱為“複製策略”的類,由該類的靜態成員函式實施成員複製操作。ATL真正實現列舉介面的類為CComEnumImpl,它是CComEnum的基類,CComEnumImpl的實現並不複雜,唯一值得注意的是CComEnumImpl內部儲存資料的方式,既可以是快照方式,也可以引用集合物件中的資料。有了這些基礎,加上上一部分介紹的ATL物件類,實現列舉物件就非常容易了,作者在書中用一個素數集合物件的例子講述了整個過程,最終透過素數集合物件的_NewEnum屬性把它與素數列舉物件聯絡起來。

第七章的後半部分講述了以STL作為資料組織方式來實現COM集合物件和列舉物件,如果讀者對於STL不是很熟悉的話,可以把這部分內容跳過去而無關大局。用STL實現集合物件和列舉物件與前面方法的主要不同在於物件的內部實現細節,基本的思路和模型仍然一致。對應於CComEnum的列舉器類為CComEnumOnSTL,對應於CComEnumImpl的列舉介面實現類為IEnumOnSTLImpl,這兩個類的用法可以非常靈活,書中舉例說明了這兩個類的用法。如果用STL實現列舉物件,則集合類也需要提供相應的支援,比較好的做法是用STL來實現集合類。緊接著作者就介紹了ICollectionOnSTLImpl,它與CComEnumOnSTL配合起來實現集合物件和列舉物件。

作者在這一章還介紹瞭如何把ATL的資料型別(也就是在第二章講述的一些類)封裝到STL的容器中,這部分內容對於那些STL迷來說可能非常有用,也非常有意思。在這一章的最後,作者用前面講到的內容構造了一個簡單的物件模型例子,讓讀者知道如何把集合物件和列舉物件應用到物件模型中,起到一個全域性指導的作用。

第八章講述可連線物件的ATL實現,作者首先回顧了COM的可連線物件機制,指出ATL利用兩個全域性函式AtlAdvise和AtlUnadvise建立源物件和接收器物件之間的連線或者撤銷兩者之間的連線。可連線物件是COM的雙向通訊機制,它並沒有應用複雜的技術,實際上就是COM的一個反向應用。

然後作者從一個例子開始講述ATL實現可連線物件的全過程(分為七個步驟),包括如何實現IConnectionPointContainer介面、如何實現每個連線點物件、如何增加連線點對映表以及如何編寫事件激發函式或者讓Visual C++的整合環境產生事件激發函式。在介紹了源物件的實現過程之後,作者又介紹了客戶程式一方實現事件接收器物件的過程,相對而言,這個過程涉及到的細節要多一些,因為ATL的模板類IDispatchImpl只支援雙介面,不支援dispinterface,源物件的出介面(outgoing interface)往往是dispinterface,所以接收器物件要透過其他的途徑來實現事件介面。ATL提供了兩個模板類IDispEventImpl和IDispEventSimpleImpl用於接收器物件的實現,IDispEventImpl要藉助於型別庫所提供的出介面型別資訊,這是最簡單的實現方法,而IDispEventSimpleImpl不需要型別庫的支援,這是效率最高的方法。這兩種方法都需要用到事件接收器對映表(event sink map),程式設計師可以把具體的事件函式以及對應的dispid等資訊透過ATL提供的一組宏提交給客戶類。配合前面給出的源物件例子,作者在講解過程中也提供了客戶端接收器物件的例子程式。

在介紹了ATL中可連線物件的用法之後,作者繼續講解這套機制的實現細節,包括以下一些要點:

  • 源物件實現IConnectionPointContainer介面的原理,包括如何操縱連線點物件列舉器、如何使用連線點對映表。

  • 連線點物件實現類IConnectionPointImpl。包括如何管理多個連線、如何操縱連線列舉器物件、如何在一個源物件上實現多個連線點物件等細節。

  • 事件接收器物件所涉及到的多個類。包括_IDispEvent、_IDispEventLocator、IDispEventSimpleImpl,同時也討論了事件接收器對映表的基本原理。

這一章介紹的內容相對要簡單一些,如果上一部分的基礎打得比較好的話,這些內容可以輕鬆地讀下來。儘管內容比較簡單,但是對於我們熟練應用ATL的可連線物件機制非常有幫助,因為在實際工作中,完全手工實現可連線物件機制非常繁瑣,即使ATL的Wizard中已經提供了連線點物件的支援,要讓可連線物件和接收器物件真正工作起來還需要許多手工工作。所以這部分內容很有實際意義。

以上三章內容是COM擴充套件的內容,但是我們在實際工作中經常會碰到這些內容,尤其是COM的永久特性和可連線物件特性,具有非常廣泛的應用背景,比如下一部分要講到的ActiveX控制就同時需要這兩種技術的支援,而集合物件和列舉器物件則是VBA程式所非常依賴的物件組織手段。

第四部分 ATL對視窗和ActiveX控制的支援

我們知道,COM是一個平臺無關的元件規範,但是COM的應用幾乎都是與Windows平臺相關的,這是由COM的歷史背景所決定的。本書第四部分討論的內容是ATL如何實現與使用者介面有關的功能,特別是如何封裝視窗、如何支援ActiveX控制。

在Visual C++提供的兩套類庫中,MFC側重於對Windows平臺上介面特性的封裝,包括各種風格的視窗程式、對話方塊、大量的控制類等,而ATL則側重於對COM的封裝。但是,如同MFC也提供了COM支援一樣,ATL也提供了對使用者介面的支援,這就是第九章所要討論的ATL視窗封裝,當然封裝的基礎仍然是 。

第九章討論的內容不涉及到COM,完全是Windows平臺上的與視窗有關的許多細節,作者從Windows視窗應用的基本講起,講到了視窗的三大要素:視窗類(WNDCLASSEX結構)、視窗控制程式碼(HWND)和視窗過程(WndProc),這也是封裝視窗類的幾個要點。ATL的視窗類層次結構比MFC要簡單得多,其中主要的類是CWindow、CWindowImpl、CDialogImpl和CContainedWindow,然後作者逐一介紹這些類。

CWindow類非常簡單,它只是對視窗控制程式碼HWND的封裝,幾乎所有與視窗有關的API函式都有對應的CWindow成員函式。這些成員函式僅僅是個簡單封裝而已。

CWindowImpl是ATL視窗類中的關鍵類,它一方面繼承自CWindow,同時它解決了視窗的兩個關鍵問題:視窗類的註冊和視窗訊息處理。視窗類的註冊是建立該類視窗的必要條件,CWindowImpl類把這個過程作了封裝,程式設計師只需使用簡單的宏就可以完成這些必要的任務。CWindowImpl類實現視窗過程則使用了一點技巧,因為視窗過程是以HWND作為視窗物件標識的,而CWindowImpl是以this指標作為物件標識的,所以如何在HWND和this指標之間建立對應關係是關鍵所在。CWindowImpl的基類CWindowImplBaseT以StartWindowProc作為視窗過程,在第一次被呼叫的過程中建立兩者的對映關係,它透過一個被稱為“thunk”的物件在執行過程中建立起來的一組機器指令。每個視窗物件都有一個thunk物件。thunk的任務是在呼叫CWindowImpl類的靜態成員函式WindowProc處理訊息之前先用this指標代替棧中的HWND。書中對這個過程作了詳細的說明,對於ATL的視窗底層封裝有興趣的讀者可以看一看這部分內容介紹,很有意思的。

完成了HWND到this指標的對映還只是一小步的工作,視窗過程的根本目標是處理視窗的訊息,ATL的訊息處理機制非常強大,首先它利用一組宏構造出ProcessWindowMessage成員函式,一旦把這些宏展開,其實就是一個規範的switch語句,以及每個case下的一大堆if語句。這種程式結構明顯不同於MFC的訊息對映表,MFC訊息對映表透過查表來處理每一個訊息。ATL的訊息支援非常靈活,我們可以按訊息碼指定訊息處理函式,也可以按訊息碼的範圍值指定處理函式,對於命令訊息和通知訊息有專門的宏提供支援。更為強大的是,ATL的訊息表可以構成鏈,也就是把派生類和基類的訊息錶連結起來,如果有多個基類的話,可以把所有這些訊息表都鏈起來。而且在每個類中,訊息表中的訊息處理項又可以分組,有的用於派生類,有的用於自身,為派生類提供了許多機會。

理解了CWindowImpl之後再來理解CDialogImpl則容易多了,同樣的thunk技術,同樣的訊息鏈技術,所不同的是底層Win32 API有不同的處理,而且對話方塊分為有模式對話方塊和無模式對話方塊。如果讀者用MFC編寫過對話方塊類的話,一定對其中的DDX/DDV函式有印象,Class Wizard產生的這些函式呼叫完成了對話方塊成員變數與對話方塊上控制之間的資料和資料有效性判斷,不幸的是,ATL沒有提供這樣的功能,我們只好自己解決,其實真要做起來並不難,而且自己實現可以更靈活。

我們知道MFC封裝了大量的控制類,而ATL的視窗類樹中並沒有這麼多的控制類,實際上,在ATL的atlcon例子中同樣給出了所有這些控制的封裝類,只是沒有正式的文件而已,這些類用起來很簡單,原始碼本身就是最好的文件,我們可以充分利用這些資源。

這一章最後還介紹了CContainedWindow,它的特殊之處在於,它接收到訊息之後交給父視窗處理這些訊息,父視窗既可以直接建立這樣的子視窗,也可以對一個已經被建立的視窗物件利用“子類化”的技術擷取其視窗過程。

第九章的內容與COM完全無關,但它是ATL庫中不同缺少的一部分,特別對於有使用者介面的ATL工程來說,更是非常重要。第十章則講述如何用ATL來建立ActiveX控制(大多數中文資料把“ActiveX control”稱為ActiveX)。ActiveX控制是COM技術的大整合,也是OLE技術的大整合,如果純粹從技術角度來講的話,幾乎還沒有一本書能夠全面講述ActiveX控制的各項技術。ATL對ActiveX控制的支援比較全面,而且它的應用非常靈活,程式設計師可以有所選擇地選取某一部分。

第十章作者首先回顧了ActiveX控制的各項功能,然後從一個例子BullsEye的功能需求分析出發,利用ATL Wizard建立一個初始的ActiveX控制框架,對建立過程中涉及到的選項逐一作了說明。有了初始的控制程式碼之後,接下去的任務是逐項完善BullsEye控制的功能,分別如下:

  • 首先是增加BullsEye控制的屬性和方法,因為ActiveX控制是自動化技術的超集,包容器(客戶)需要透過IDispatch介面或者雙介面與控制進行通訊,所以控制的屬性和方法是基礎,屬性和方法分兩種:庫存的(stock)和自定義的(custom)。ATL支援庫存屬性的類為CStockPropImpl,它繼承自IDispatchImpl類。而自定義的屬性和方法則可以透過VC整合環境提供的“Add Method”和“Add Property”加入,它會自動更新IDL介面定義檔案和相應的.h檔案。

  • 加入事件支援。首先在IDL檔案中加入出介面的事件定義,然後加入連線點支援以及IConnectionPointContainer介面的支援。ActiveX控制除了支援自定義的出介面之外,它還支援IPropertyNotifySink介面作為出介面,專門用於向包容器程式提供屬性變化通知。ATL的類IPropertyNotifySinkCP實現了相應的連線點物件。實現連線點支援的基本機制如上一部分所介紹,只是ATL為ActiveX控制提供了更多的便利。作為與連線點相關的內容,一個ActiveX控制也應該支援IProvideClassInfo2介面,相應的ATL類為IProvideClassInfo2Impl。

  • 作為ActiveX控制,在視窗中繪製必要的圖形資訊是它的任務之一,ATL只提供了繪製的框架,具體的繪製任務由派生類完成,對於我們編寫ATL控制而言,就是一個OnDraw函式。

  • 屬性的永久性。這是第六章內容的綜合應用,直接利用ATL提供的永久介面實現類,以及控制的屬性對映表即可。

  • 一個客戶友好的ActiveX控制應該實現IQuickActivate介面,ATL想得很周到,它實現了這個介面,把有關的邏輯交給控制類的IQuickActivate_QuickActivate成員。

  • 元件類別。利用ATL的類別對映表很容易實現控制的類別功能。

  • 屬性分類的功能。ATL沒有提供支援,但是我們可以很方便地實現ICategorizeProperties介面。

  • 針對屬性的瀏覽功能。這是介面IPerPropertyBrowsing的任務,ATL提供了介面實現IPerPropertyBrowsingImpl,我們只要過載有關的成員函式即可。

  • ActiveX控制的鍵盤處理。這是ActiveX控制與包容器之間的協作基礎,我們只要根據ActiveX控制的規範過載必要的函式即可。

這一章的內容覆蓋面比較廣,作者透過BullsEye例子程式展示了ATL實現全功能的ActiveX控制的諸多細節。有許多地方用到了前面講述的ATL類或者相應的各種支援,這個例子對於我們自己實現ActiveX控制很有啟發性。

最後第十一章介紹ActiveX被包容器程式使用的情況。作者首先介紹了ActiveX控制與包容器之間的協作概況,然後敘述ATL實現包容的基本技術。CAxHostWindow類是ATL實現包容控制的內部基本類。作者從ActiveX控制的建立過程解釋了CAxHostWindow類如何參與包容器視窗與控制之間的協作,我們知道,包容器程式透過控制站點物件來管理ActiveX控制,這裡的CAxHostWindow物件相當於控制的站點物件,它不是由客戶程式顯式建立,而是在包容器視窗建立控制的過程中被隱式建立,這個過程涉及到很多協作細節。作者花了很大的篇幅來講述這個過程,一旦這個過程講清楚了,那麼其他的細節就顯得非常簡明。

加入事件控制的過程很簡單,如第八章所述,利用IDispEventImpl類,加入接收器事件對映表,然後在適當的時候建立接收器與控制之間的連線即可。同樣地,我們可以在包容器上實現IPropertyNotifySink介面,並建立它與控制之間的連線,以便處理控制的屬性變化通知。對於屬性頁的處理也是包容器程式的一項任務,但處理起來比較簡單,只需呼叫OLE函式OleCreatePropertyFrame即可。控制的永久處理也不復雜,在包容器的儲存和恢復操作中分別呼叫控制永久介面的Save和Load成員即可。

除了在一般的視窗中包容ActiveX控制之外,對話方塊作為包容器視窗也是一種典型情況,對於客戶程式而言,在對話方塊中使用ActiveX控制更為簡便。在階段,整合環境往往能夠為我們做更多的事情,比如控制的初始狀態處理、控制的事件處理等。但是仍然有些工作需要我們在後期手工進行處理,如建立接收器物件與控制之間的連線、編寫事件函式等。

在實際應用中,用ActiveX控制或者Windows的標準控制構造新的控制是一項很有用的技術,這就是複合控制,ATL也支援複合控制,它把對話方塊的功能和ActiveX控制的功能結合起來。在構造複合控制的時候,我們可以指定一個對話方塊模板,把設計階段完成的介面模板引入到複合控制中,這是一個非常簡便的構造介面單元的方法。

另一個功能更為強大的構造介面單元的方法是HTML控制,它利用控制直接把HTML頁面封裝成一個新的ActiveX控制。由於它把HTML頁面作為介面內容,所以使用的時候非常靈活,我們可以在HTML頁面中嵌入指令碼,可以使用多種字型,可以訪問HTML文件的物件模型。

這一章的內容也比較廣泛,但是它把上一章介紹的ActiveX控制與實際的應用結合起來了。而且透過這些內容的介紹,我們可以拓寬視野把ActiveX控制應用得更加靈活,把Web引入到我們的桌面程式上來,或者把桌面程式的功能引入到Web當中去。這兩章的內容相對比較獨立,它們構成了用ATL開發和使用ActiveX控制的主體。如果讀者關注MSJ雜誌的話,可以在1999年的2、3、4月期上找到有關這些內容的一個連載,文章的名字為:“Write ActiveX Controls Using Custom Interfaces Provided by ATL 3.0”。

第十一章是全書的最後一章,我們跟隨作者從基本的嚮導開發學起,經過深入淺出的分析,終於達到了一個歇腳點,可以輕鬆一下了。但這不等於我們學習ATL的任務已經完成了,ATL還有很多內容有待於我們去挖掘。

結束語

對於COM應用的開發,ATL無疑是首選的工具,與MFC相比,ATL的規模還不算大,但是從上述的介紹我們可以看出,ATL涉及到了COM的方方面面。實際上,ATL的內容還要多得多,比如OLE 的支援、MTS的支援等,儘管如此,如果我們有了這本書中的內容為基礎,那麼再去學習這些擴充套件的內容就會容易得多,結合ATL中實現COM的基本手段加上這些應用技術的背景知識,我們可以很容易地掌握這些開發技術。

但是如果我們要想熟練掌握甚至精通ATL的話,那麼這只是一個開頭,前面還有漫長的路要走。原因有多方面,一則COM本身異常複雜,不下苦功難窺全貌;二則ATL確實奧妙很多,它體現了C++語法的博大精深;三則ATL還存在很多錯誤,雖然本書作者指出了一些錯誤,但實際的錯誤肯定更多,這就對ATL使用者提出了更高的要求,如果使用過程中不能發現這些錯誤或者避開這些錯誤,那麼用ATL反而會阻礙我們的工作。

雖然ATL比較精深,但是這本書的講解非常通俗易懂,語言比較簡練,條理非常清楚。即使在讀完這本書之後,它仍然可以作為參考書指導我們的開發和學習工作。我想,這就是好書的價值所在吧。

 

 

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993489/,如需轉載,請註明出處,否則將追究法律責任。

相關文章