翻譯:賴儀靈

出處: http://blog.csdn.net/laiyiling

聲明:版權(quán)歸原作者擁有,請勿隨意轉(zhuǎn)載此翻譯文檔,保留一切權(quán)利。

?

1.1??? 什么是 ATL

?

ATL 的全稱并不能完整的說明 ATL 是什么,提供了什么功能。 Active 一詞實(shí)際上是過去 Microsoft 市場時(shí)期的遺留物,當(dāng)時(shí)的“ ActiveX ”表示 COM 。而現(xiàn)在,“ ActiveX ”表示控件。 ATL 對建立控件提供了非常強(qiáng)大的支持,但是它實(shí)際的功能遠(yuǎn)不止這些。

?

ATL 的主要功能:

?????? 1 對復(fù)雜的數(shù)據(jù)類型提供包裝類,比如智能指針、 VARIANT(S) BSTR(S) HWND(S)

?????? 2 、提供一些基本 COM 接口的實(shí)現(xiàn)類,比如 IUnknown, IClassFactory, IDispatch, IPersistXxx, IConnectionPointContainer, IEnumXxx

?????? 3 、提供 COM 服務(wù)器的管理類,比如暴露類對象、實(shí)現(xiàn)自我注冊以及管理服務(wù)器生命期。

?????? 4 、提供建立 COM 控件和控件容器的支持類,以及建立舊的空白窗口程序。

?????? 5 、龐大的類庫支持建立 WEB 應(yīng)用程序和 XML Web Services

?????? 6 、快速建立應(yīng)用程序的向?qū)Чδ堋?/span>

?

ATL 的實(shí)現(xiàn)啟發(fā)于當(dāng)今 C++ 領(lǐng)域的類庫標(biāo)準(zhǔn)——標(biāo)準(zhǔn)模板庫( STL )。 ATL 由一些短小、高效、靈活的類組成。權(quán)利與職責(zé)同在,與 STL 一樣,只有具有一定經(jīng)驗(yàn)的 C++ 程序員才能很好的使用 ATL

?

當(dāng)然,我們的主要目的編寫 COM 應(yīng)用程序。使用、實(shí)現(xiàn) COM 對象、 COM 服務(wù)器的經(jīng)驗(yàn)也是要求的。對于沒有任何 COM 知識(shí)、希望建立 COM 對象的人, ATL 并不適合(這種情況其他的任何工具如 Visual Basic MFC 都是不適合的)。使用 ATL 要求非常熟悉 C++ COM 如何實(shí)現(xiàn),以及一些 ATL 本身的實(shí)現(xiàn)細(xì)節(jié)。

?

事實(shí)上, ATL 也實(shí)現(xiàn)了一些向?qū)椭覀兩沙跏嫉拇a。在本章的剩余部分,展示了在 Visual Studio 2005 中如何使用 ATL 向?qū)АR黄饋眢w驗(yàn)吧!

?

1.2??? 創(chuàng)建 COM 服務(wù)器

?

1.2.1 創(chuàng)建 ATL 工程

?????? Visual Studio 中任何開發(fā)的第一步就是建立解決方案,初始化工程。選擇 File => New Project 菜單,顯示圖 1 1 所示的新建工程對話框,然后選擇 Visual C++ 工程類型。

?

?o_1-1.JPG

1 1 創(chuàng)建新的 Visual Studio 工程

?

選擇 Visual C++ 工程文件夾顯示所有可用的 C++ 工程模板類型。工程名稱(圖 1-1 中是 PiSvr )是生成的 DLL 或者 EXE 服務(wù)器名稱。

?

ATL 工程模板的工作就是為你的 COM 服務(wù)器建立一個(gè)工程。 COM 服務(wù)器既可以是 DLL 也可以是 EXE EXE 又可以進(jìn)一步分為標(biāo)準(zhǔn)應(yīng)用程序惡化 NT 服務(wù)。 ATL 工程模板支持所有的三種服務(wù)器類型。默認(rèn)情況下,初始選擇 DLL 服務(wù)器類型,如圖 1-2 所示。

?

?o_1-2.JPG

1-2 ?? ATL 工程設(shè)置

?

1.2.2 ATL 工程向?qū)нx項(xiàng)

?????? 1-2 的工程向?qū)е辛谐龅牟糠诌x擇我們需要做進(jìn)一步的解釋。第一個(gè)是選擇工程建立使用 ATL 屬性。正如在前言中所述,在 ATL 8 中首選使用非屬性工程,所以本書也集中討論非屬性工程。如果你需要使用屬性,請參考附錄 D 的“ ATL 屬性”,介紹了屬性工程的知識(shí)。

?

在附加選擇中,第一個(gè)選項(xiàng)允許綁定自定義的代理 / 存根代碼到 DLL 服務(wù)器中。默認(rèn)不選擇。選中后, Visual Studio 會(huì)為代理 / 存根生成一個(gè)單獨(dú)的工程,名稱為 <ProjectName>PS.vcproj 。同時(shí)添加到服務(wù)器的解決方案中。此工程編譯生成一個(gè)代理 / 存根 DLL ,把這個(gè) DLL 分發(fā)到所有需要列集和散列自定義接口的客戶、服務(wù)器機(jī)器上。注意,在解決方案的默認(rèn)編譯配置中( Debug, Release ),都沒有選中代理 / 存根 DLL 的同時(shí)編譯,在圖 1-3 所示的解決方案屬性頁中,選中 PiSvrPS 工程最后 Build 下的選擇框,然后在你編譯解決方案的時(shí)候代理 / 存根 DLL 工程也會(huì)同時(shí)編譯發(fā)布。

?

?o_1-3.JPG

1-3 新建 ATL COM 服務(wù)器的應(yīng)用程序設(shè)置

?

如果希望把代理 / 存根 DLL 捆綁到服務(wù)器 DLL 一起(要求服務(wù)器安裝在客戶計(jì)算機(jī)),可以把 Allow Merging of Proxy/Stub Code 選項(xiàng)選中(非屬性工程)。這樣做以后解決方案就只有一個(gè)單獨(dú)的工程(主服務(wù)器工程),同時(shí)為了合并代理 / 存根 DLL 的代碼,部分條件編譯語句會(huì)添加到服務(wù)器工程中。預(yù)編譯符號(hào) _MERGE_PROXYSTUB會(huì)控制是否把 代理 / 存根的代碼編譯到服務(wù)器,這個(gè)定義符號(hào)默認(rèn)會(huì)添加到所有的配置文件中。

?

如果沒有足夠的理由(已經(jīng)超出了本書的討論范圍),應(yīng)該盡量避免把代理 / 存根代碼插入到服務(wù)器中,使用雙重接口或者 OLE 自動(dòng)化兼容的自定義接口更合適。

?

ATL 工程向?qū)У牡诙€(gè)選項(xiàng)可以給工程添加微軟基礎(chǔ)類庫( MFC )支持。坦白說,應(yīng)該避免選擇這個(gè)選項(xiàng)。下面列舉了一些缺陷,說明開發(fā)人員為什么應(yīng)該關(guān)閉這個(gè)選項(xiàng):

?????? 1 、離開 CString CMap CList 等等),我就無法工作。 MFC 工具類庫只是在 C++ 標(biāo)準(zhǔn)委員會(huì)建立標(biāo)準(zhǔn)程序庫之前的臨時(shí)產(chǎn)品。在標(biāo)準(zhǔn)程序庫建立之后,應(yīng)該停止使用 MFC 版本,因?yàn)闃?biāo)準(zhǔn)程序庫提供的類 string, map, list 等,與等價(jià)的 MFC 版本相比更靈活健壯。而且,現(xiàn)在的 CString 類是 MFC ATL 的共享類,因此在 ATL 最新版中不需要額外包含其他的 MFC 部分,也不需要鏈接 MFC 庫,可直接使用。其他的 MFC 工具類也稱為了共享類,如 CPoint CRect 。對于這些和 MFC 類似的集合類庫,在 ATL 最新版實(shí)現(xiàn)了相應(yīng)的集合類( CAtlMap, CAtlList 等等)。

?????? 2 、沒有向?qū)Ь筒荒芄ぷ鳌1菊碌膬?nèi)容全部是關(guān)于 Visual Studio 提供給程序員的向?qū)Чδ堋?/span> ATL 的向?qū)Чδ芎?/span> MFC 的一樣強(qiáng)大。

?????? 3 、我已經(jīng)掌握 MFC ,不想學(xué)其他的。幸運(yùn)的是,有這樣思想的人現(xiàn)在都不會(huì)讀本書。

?

ATL 工程向?qū)У牡谌齻€(gè)選項(xiàng)是支持 COM+ ,使得工程鏈接 COM+ 組件服務(wù)庫 comsvs.dll ,包含相應(yīng)的頭文件 comsvcs.h 。所有應(yīng)用程序就可以訪問 COM+ 的各種接口。選中 COM+ 支持后,可以選中子項(xiàng)以支持組件注冊,在工程中生成額外的 coclass 類實(shí)現(xiàn) IComponentRegister 接口。

?

1.2.3 ATL 工程向?qū)У慕Y(jié)果

?

不管有沒有選中上面介紹的三個(gè)工程項(xiàng), ATL 向?qū)傻?/span> COM 服務(wù)器都實(shí)現(xiàn)了如下的三個(gè)功能:自注冊、服務(wù)器生命期控制、暴露類對象。作為額外提供的方便,向?qū)г诿看尉幾g成功后發(fā)送一個(gè)事件以注冊 COM 服務(wù)器。根據(jù) DLL/EXE 服務(wù)器的類型,響應(yīng)事件執(zhí)行 regsvr32.exe <project>.dll 或者 <project>.exe /regserver

?

關(guān)于 COM 服務(wù)器支持此三項(xiàng)功能的更詳細(xì)信息,以及如何擴(kuò)展進(jìn)行高級的同步控制和聲明期需要,請參考第五章 COM 服務(wù)器。

?

1.3??? 插入 COM

?

1.3.1 添加 ATL 簡單對象

?

建立了 ATL COM 服務(wù)器后,可能需要插入一個(gè)新的 COM 類。選擇菜單 Project => Add Class Item 實(shí)現(xiàn)。插入 ATL 類時(shí),需要線選中插入類的類型,如果 1-4 所示。

?o_1-4.JPG
1-4 添加 ATL COM

?

如果時(shí)間充足,你也許想花點(diǎn)時(shí)候稍微的瀏覽一下可用的類類型。每個(gè)類型都會(huì)生成一系列特殊的代碼,它們使用 ATL 基礎(chǔ)類提供大部分的功能,然后生成自定義接口的框架。 COM 類實(shí)現(xiàn)不同的 COM 接口時(shí),你可以選擇不同向?qū)ь愋汀2恍业氖牵驅(qū)Р]有提供訪問所有 ATL 功能(甚至大部分功能)。但是,設(shè)計(jì)生成的代碼很容易修改、開始之后很方便增加、刪除函數(shù)。熟悉這些向?qū)深惖念愋秃瓦x項(xiàng)最好的辦法就是實(shí)踐。

?

選擇類類型后(點(diǎn)擊 OK ), Visual Studio 通常會(huì)要求輸入一些其他的信息。一些類型的附加選項(xiàng)比 ATL 簡單類型多很多(圖 1-4 選擇簡單類型)。大部分的 COM 類都至少需要圖 1-5 和圖 1-6 所示的信息。

?

?o_1-5.JPG
1-5 設(shè)置 COM 類名稱

?

?o_1-6.JPG
1-6 設(shè)置 COM 類名稱

?

ATL 簡單對象向?qū)υ捒虻拿Q頁中,只需要輸入短名稱,如 CalcPi 。短名稱被用來組成對話框中的其他部分信息(你也可以選擇不使用這些自動(dòng)生成的信息)。這些信息分為兩類:必要的 C++ 信息,包括 C++ 類名稱、 C++ 類的頭文件、實(shí)現(xiàn)文件名稱;必要的 COM 信息,包括 coclass 名稱(接口定義語言使用 [IDL] )、默認(rèn)接口的名稱(也用于 IDL )、稱為類型的友好名稱(用于 IDL 和注冊設(shè)置)、最后還有版本獨(dú)立的程序標(biāo)識(shí)符(用于注冊設(shè)置)。版本相關(guān)的 ProgID 是版本獨(dú)立的 ProgID 加上“ .1 ”后綴組成。注意其中的 Attributed 選項(xiàng)不能選中。在非屬性工程中,我可以有選擇的添加屬性化的類到非屬性化的工程中,只需要選中圖 1-5 中的 Attributed 項(xiàng)。

?

Options 頁中,可以修改一些低級的 COM 設(shè)定。線程模型描述了你希望新增類的實(shí)例所生存的套間類型:單線程套間( STA ,也稱為套間模型);多線程套間( MTA ,也稱為自由套間模型)。 Single 模型通常用于少數(shù)的類,不管客戶是怎樣的套間模型,類的所有實(shí)例都要求共享在應(yīng)用程序的主 STA 中。 Both 模型使對象和客戶處于相同的套間類型,這樣可以避免過多的代理 / 存根對。中立線程模型只有在 Window 2000 及以后的操作系統(tǒng)才可以用。使對象可以在調(diào)用者的線程中安全執(zhí)行。調(diào)用中立類型的組件通常速度更快,因?yàn)樗恍枰€程交換,而在不同類型的套間之間進(jìn)行跨套間調(diào)用通常是需要線程交換的。你選擇的線程模型設(shè)置會(huì)影響服務(wù)器在注冊中存儲(chǔ)的 ThreadingModel 值,它決定了對象應(yīng)該如何實(shí)現(xiàn)線程安全的 AddRef Release

?

接口類型設(shè)置允許你設(shè)定新增類的默認(rèn)接口類型: Custom (需要自定義的代理 / 存根,不從接口 IDispatch 繼承); Dual (使用類型庫進(jìn)行列集,從 IDispatch 繼承)。此設(shè)置影響生成的 IDL 默認(rèn)接口。選擇 Custom 后可以進(jìn)一步選中自動(dòng)化兼容選項(xiàng)。選中后的 IDL 定義中會(huì)添加 [oleautomation] 修飾屬性,它限制了接口方法的變量類型可以使用 OLE 自動(dòng)化兼容類型。比如, [oleautomation] 接口方法必須使用 SAFEARRAY 代替方便的 C 風(fēng)格數(shù)組。如果你希望 ATL COM 對象適用于不同的客戶環(huán)境,比如 Visual Basic ,你就應(yīng)該選中這個(gè)選項(xiàng)。而且,如果使用了 [oleautomation] 接口,運(yùn)行在微軟 .Net 框架中的代碼訪問 COM 對象更簡單。部署 [oleautomation] 接口的 COM 對象也可能更簡單,因?yàn)榇祟惤涌诳偸鞘褂媒y(tǒng)一的列集在不同的套間傳遞接口。在支持 COM 的計(jì)算機(jī)上總是存在類型庫列集的,因此你不需要與組件一起發(fā)布額外的代理 / 存根 DLL

?

聚集設(shè)置允許你設(shè)定是否希望你的對象被聚集使用,也就是能否作為受控的內(nèi)部對象參與聚合。這個(gè)設(shè)置不會(huì)影響當(dāng)前新增類對象是否可以作為外部控制對象使用聚合。關(guān)于被聚合的更多信息請參考第四章的“ ATL 的對象”。關(guān)于如何聚合其他對象請參考第六章的“接口映射”。

?

支持 ISupportErrorInfo 設(shè)置指示向?qū)梢粋€(gè) ISupportErrorInfo 接口的實(shí)現(xiàn)。如果希望拋出 COM 異常那么就有必要選中這個(gè)選項(xiàng)。在跨語言和套間邊界時(shí),與單獨(dú)的 HRESULT 所提供的錯(cuò)誤相比, COM 異常(也稱為 COM 錯(cuò)誤信息對象)可以傳遞更多的錯(cuò)誤細(xì)節(jié)。關(guān)于拋出、捕捉 COM 異常的更多信息請參考第五章“ COM 服務(wù)器”。

?

支持連接點(diǎn)指示向?qū)?/span> IConnectionPoint 的實(shí)現(xiàn),它允許對象激發(fā)腳本環(huán)境中的事件,比如瀏覽器的寄宿腳本。控件同樣使用連接點(diǎn)激發(fā)控件容器的事件,詳細(xì)參考第九章“連接點(diǎn)”。

?

在幫助信息中, Free-Threaded Marshaler 的解釋是:允許同一接口的客戶獲得一個(gè)原始接口,即使它們的線程模型不匹配。這個(gè)幫助信息并沒有說使用這個(gè)選項(xiàng)的危險(xiǎn)性。不辛的是, Free-Threaded Marshaler FTM )如果代價(jià)高昂的汽車:雖然你想擁有,但你卻負(fù)擔(dān)不起。在選擇此項(xiàng)前請先參考第六章“接口映射”關(guān)于 FTM 的描述。

?

支持 IObjectWithSite 設(shè)置使向?qū)梢粋€(gè) IObjectWithSite 的實(shí)現(xiàn)。寄宿于容器(如 IE 瀏覽器)的對象需要使用這個(gè)接口。容器使用這個(gè)接口傳遞接口指針到它們?nèi)菁{的對象,然后對象就可以直接和容器通信。

?

1.3.2 ATL 簡單對象向?qū)У慕Y(jié)果

?

在設(shè)置好這些選項(xiàng)后,簡單對象向?qū)婺闵梢粋€(gè)單獨(dú)的文件,以開始增加自己的實(shí)現(xiàn)。對于此新增類:生成一個(gè)新的類定義頭文件;一個(gè)新的實(shí)現(xiàn) CPP 文件;一個(gè) .RGS 文件包含注冊信息。而且 IDL 文件也會(huì)更新包含新接口的定義。

?

生成的類定義文件如下:

?

// CalcPi.h : 聲明 CCalcPi

#pragma once

#include "resource.h"?????? // 主要資源符號(hào)

?

#include "PiSvr.h"

#include "_ICalcPiEvents_CP.h"

?

class ATL_NO_VTABLE CCalcPi :

??? public CComObjectRootEx<CComSingleThreadModel>,

??? public CComCoClass<CCalcPi, &CLSID_CalcPi>,

??? public ISupportErrorInfo,

??? public IConnectionPointContainerImpl<CCalcPi>,

??? public CProxy_ICalcPiEvents<CCalcPi>,

??? public IDispatchImpl<ICalcPi, &IID_ICalcPi, &LIBID_PiSvrLib,

??????? /*wMajor =*/ 1, /*wMinor =*/ 0>

{

public:

??? CCalcPi() { }

DECLARE_REGISTRY_RESOURCEID(IDR_CALCPI)

?

BEGIN_COM_MAP(CCalcPi)

??? COM_INTERFACE_ENTRY(ICalcPi)

??? COM_INTERFACE_ENTRY(IDispatch)

??? COM_INTERFACE_ENTRY(ISupportErrorInfo)

??? COM_INTERFACE_ENTRY(IConnectionPointContainer)

END_COM_MAP()

?

BEGIN_CONNECTION_POINT_MAP(CCalcPi)

??? CONNECTION_POINT_ENTRY(__uuidof(_ICalcPiEvents))

END_CONNECTION_POINT_MAP()

// ISupportsErrorInfo

??? STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

?

??? DECLARE_PROTECT_FINAL_CONSTRUCT()

?

??? HRESULT FinalConstruct() {

??????? return S_OK;

??? }

?

??? void FinalRelease() {

??? }

?

public:

?

};

?

OBJECT_ENTRY_AUTO(__uuidof(CalcPi), CCalcPi)

?

第一個(gè)需要注意的就是基類列表。此例中, ATL 同時(shí)使用了模板和多繼承技術(shù)。每個(gè)基類都提供了 COM 對象需要的一些普通實(shí)現(xiàn)代碼。

?????? 1 CComObjectRootEx 提供了 IUnknown 接口實(shí)現(xiàn)

?????? 2 CComcoClass 提供了類廠實(shí)現(xiàn)

?????? 3 ISupportErrorInfo 是一個(gè)接口,實(shí)現(xiàn)了 CPP 文件的一個(gè)方法。

?????? 4 、選中了支持連接點(diǎn)選項(xiàng)后, IConnectionPointContainerImpl 提供了此功能的實(shí)現(xiàn)

?????? 5 CProxy_ICalcPiEvents 也是連接點(diǎn)的部分實(shí)現(xiàn)

?????? 6 IDispatchImpl 提供了對象雙接口 IDispatch 的實(shí)現(xiàn)。

?

另一個(gè)需要注意的是 COM_MAP 宏,它是 ATL 映射的一個(gè)實(shí)例:一系列生成代碼的宏(通常用來生成查找表)。特別的是 COM_MAP 宏用來實(shí)現(xiàn)所有 COM 對象都要求支持的 QueryInterface 方法。

?

關(guān)于 ATL 基類如何實(shí)現(xiàn)基本 COM 功能,如何利用這些實(shí)現(xiàn)建立對象層次以及適當(dāng)?shù)耐蕉嗑€程對象,請參考第四章“ ATL 對象”。如何使用 COM_MAP 宏的詳細(xì)信息請參考第六章“接口映射”。