翻譯:賴儀靈

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

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

?

1.1??? 什么是 ATL

?

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

?

ATL 的主要功能:

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

?????? 2 、提供一些基本 COM 接口的實現類,比如 IUnknown, IClassFactory, IDispatch, IPersistXxx, IConnectionPointContainer, IEnumXxx

?????? 3 、提供 COM 服務器的管理類,比如暴露類對象、實現自我注冊以及管理服務器生命期。

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

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

?????? 6 、快速建立應用程序的向導功能。

?

ATL 的實現啟發于當今 C++ 領域的類庫標準——標準模板庫( STL )。 ATL 由一些短小、高效、靈活的類組成。權利與職責同在,與 STL 一樣,只有具有一定經驗的 C++ 程序員才能很好的使用 ATL

?

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

?

事實上, ATL 也實現了一些向導幫助我們生成初始的代碼。在本章的剩余部分,展示了在 Visual Studio 2005 中如何使用 ATL 向導。一起來體驗吧!

?

1.2??? 創建 COM 服務器

?

1.2.1 創建 ATL 工程

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

?

?o_1-1.JPG

1 1 創建新的 Visual Studio 工程

?

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

?

ATL 工程模板的工作就是為你的 COM 服務器建立一個工程。 COM 服務器既可以是 DLL 也可以是 EXE EXE 又可以進一步分為標準應用程序惡化 NT 服務。 ATL 工程模板支持所有的三種服務器類型。默認情況下,初始選擇 DLL 服務器類型,如圖 1-2 所示。

?

?o_1-2.JPG

1-2 ?? ATL 工程設置

?

1.2.2 ATL 工程向導選項

?????? 1-2 的工程向導中列出的部分選擇我們需要做進一步的解釋。第一個是選擇工程建立使用 ATL 屬性。正如在前言中所述,在 ATL 8 中首選使用非屬性工程,所以本書也集中討論非屬性工程。如果你需要使用屬性,請參考附錄 D 的“ ATL 屬性”,介紹了屬性工程的知識。

?

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

?

?o_1-3.JPG

1-3 新建 ATL COM 服務器的應用程序設置

?

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

?

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

?

ATL 工程向導的第二個選項可以給工程添加微軟基礎類庫( MFC )支持。坦白說,應該避免選擇這個選項。下面列舉了一些缺陷,說明開發人員為什么應該關閉這個選項:

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

?????? 2 、沒有向導就不能工作。本章的內容全部是關于 Visual Studio 提供給程序員的向導功能。 ATL 的向導功能和 MFC 的一樣強大。

?????? 3 、我已經掌握 MFC ,不想學其他的。幸運的是,有這樣思想的人現在都不會讀本書。

?

ATL 工程向導的第三個選項是支持 COM+ ,使得工程鏈接 COM+ 組件服務庫 comsvs.dll ,包含相應的頭文件 comsvcs.h 。所有應用程序就可以訪問 COM+ 的各種接口。選中 COM+ 支持后,可以選中子項以支持組件注冊,在工程中生成額外的 coclass 類實現 IComponentRegister 接口。

?

1.2.3 ATL 工程向導的結果

?

不管有沒有選中上面介紹的三個工程項, ATL 向導生成的 COM 服務器都實現了如下的三個功能:自注冊、服務器生命期控制、暴露類對象。作為額外提供的方便,向導在每次編譯成功后發送一個事件以注冊 COM 服務器。根據 DLL/EXE 服務器的類型,響應事件執行 regsvr32.exe <project>.dll 或者 <project>.exe /regserver

?

關于 COM 服務器支持此三項功能的更詳細信息,以及如何擴展進行高級的同步控制和聲明期需要,請參考第五章 COM 服務器。

?

1.3??? 插入 COM

?

1.3.1 添加 ATL 簡單對象

?

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

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

?

如果時間充足,你也許想花點時候稍微的瀏覽一下可用的類類型。每個類型都會生成一系列特殊的代碼,它們使用 ATL 基礎類提供大部分的功能,然后生成自定義接口的框架。 COM 類實現不同的 COM 接口時,你可以選擇不同向導類型。不幸的是,向導并沒有提供訪問所有 ATL 功能(甚至大部分功能)。但是,設計生成的代碼很容易修改、開始之后很方便增加、刪除函數。熟悉這些向導生成類的類型和選項最好的辦法就是實踐。

?

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

?

?o_1-5.JPG
1-5 設置 COM 類名稱

?

?o_1-6.JPG
1-6 設置 COM 類名稱

?

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

?

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

?

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

?

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

?

支持 ISupportErrorInfo 設置指示向導生成一個 ISupportErrorInfo 接口的實現。如果希望拋出 COM 異常那么就有必要選中這個選項。在跨語言和套間邊界時,與單獨的 HRESULT 所提供的錯誤相比, COM 異常(也稱為 COM 錯誤信息對象)可以傳遞更多的錯誤細節。關于拋出、捕捉 COM 異常的更多信息請參考第五章“ COM 服務器”。

?

支持連接點指示向導生成 IConnectionPoint 的實現,它允許對象激發腳本環境中的事件,比如瀏覽器的寄宿腳本。控件同樣使用連接點激發控件容器的事件,詳細參考第九章“連接點”。

?

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

?

支持 IObjectWithSite 設置使向導生成一個 IObjectWithSite 的實現。寄宿于容器(如 IE 瀏覽器)的對象需要使用這個接口。容器使用這個接口傳遞接口指針到它們容納的對象,然后對象就可以直接和容器通信。

?

1.3.2 ATL 簡單對象向導的結果

?

在設置好這些選項后,簡單對象向導替你生成一個單獨的文件,以開始增加自己的實現。對于此新增類:生成一個新的類定義頭文件;一個新的實現 CPP 文件;一個 .RGS 文件包含注冊信息。而且 IDL 文件也會更新包含新接口的定義。

?

生成的類定義文件如下:

?

// CalcPi.h : 聲明 CCalcPi

#pragma once

#include "resource.h"?????? // 主要資源符號

?

#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)

?

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

?????? 1 CComObjectRootEx 提供了 IUnknown 接口實現

?????? 2 CComcoClass 提供了類廠實現

?????? 3 ISupportErrorInfo 是一個接口,實現了 CPP 文件的一個方法。

?????? 4 、選中了支持連接點選項后, IConnectionPointContainerImpl 提供了此功能的實現

?????? 5 CProxy_ICalcPiEvents 也是連接點的部分實現

?????? 6 IDispatchImpl 提供了對象雙接口 IDispatch 的實現。

?

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

?

關于 ATL 基類如何實現基本 COM 功能,如何利用這些實現建立對象層次以及適當的同步多線程對象,請參考第四章“ ATL 對象”。如何使用 COM_MAP 宏的詳細信息請參考第六章“接口映射”。