1.11 ? 容納控件

?

1.11.1????? 容納控件

利用 ATL 所支持的控件容納,可實現容納控件。比如, CAxDialogImpl 中的 Ax 兩字就表示 ActiveX 控件,表示對話框具有容納控件的能力。在對話框中實現容納控件,只需在對話框資源上點擊右鍵,從彈出菜單選擇 Insert ActiveX Control ,然后彈出一個對話框,列舉了系統安裝的所有控件,如圖 1-17 所示。

?

1-17.JPG
1-17 ? 插入 ActiveX 控件對話框

?

插入控件后,點擊控件可以在控件的屬性窗口設置控件的屬性。如圖 1-18 所示。

?

1-18.JPG
1-18 控件屬性對話框

?

在屬性對話框的工具欄上點擊控件事件按鈕,還可以選擇處理控件的事件,如圖 1-19 所示。

?

1-19.JPG
1-19 選擇處理的控件事件

?

容納對話框運行顯示時,控件被創建,同時根據開發階段設置的屬性初始化控件。圖 1-20 顯示了容納了一個控件的對話框。

?

1-20.JPG
1-20 容納 COM 控件的對話框

?

ATL 不但提供了對話框的容納控件功能,其他窗口也同樣支持:聲明為對話框資源的 UI 控件(稱為復合控件);聲明為 HTML 資源的 UI 控件(稱為 HTML 控件)。關于控件容器的更多信息請參考第十二章“控件容器”。

?

1.11.2 C++ COM 客戶端

?

至少在理論上, COM C++ 是一致的。一個 COM 接口直接映射為一個 C++ 的抽象類。使用 COM 對象,僅僅需要使用 MIDL 編譯器運行 IDL 文件,就可以生成一個頭文件,里面包含有所有需要的信息。

?

所有的這一切都運行正常,直到 VB 團隊詢問他們也是否可以使用 COM 技術。

?

VB 開發人員通常不知道,也不想知道 C++ 語言。 IDL 也是一個與 C++ 傳統語言相似的語言,其中也支持許多 C/C++ 的特性(比如數組和指針)。 VB 需要一種方法來存儲這些 COM 對象的類型信息,以方便 VB 開發人員使用和理解它們。

?

因此類型庫誕生了(也稱為 typelib )。類型庫存儲 COM 對象的信息:對象支持的接口 classid ;接口的方法; IDL 文件中看到的所有信息,等等 ( 除了一些不合宜的、大部分必須等同 C 數組處理內容 ) COM 系統包含一系列可以根據 typelib 內容編程訪問的 COM 對象。最好的就是類型庫可以直接嵌入到 DLL 或者 EXE ,因此不必擔心類型庫信息的丟失。

?

現在,當一些 COM 組件沒有打包 IDL 文件時,類型庫對 VB 開發人員具有非常的意義;類型庫包含有使用組件需要的所有信息。現在只缺少一樣:如何在 C++ 語言中使用類型庫?

?

C++ 語言并不能理解類型庫,它需要頭文件。這就引發了一系列的問題。從 Visual Studio 6 開始,微軟擴展了編譯器,使你可以像使用頭文件一樣使用類型庫。這種擴展使通過語句 #import 實現的。

?

#import 可以像 #include 一樣使用,一般使用形式如下:

?

#import “pisvr.dll” <options>

?

#import 語句根據選項的不同,生成一個或者兩個 C++ 頭文件。這些頭文件的擴展名是 .tlh (用于類型庫頭文件)和 .tli (用于類型庫內聯)。都生成在工程的輸出目錄( debug 版默認在 Debug 目錄, release 版默認在 Release 目錄)。

?

#import 語句提供了很多的選項來控制生成的文件內容。可以在 Visual Studio 文檔中查看所有的選項列表。此處只介紹一些比較常用的控制項。

?

選項 no_namespace 告訴編譯器我們不希望生成的文件內容放入一個 C++ 名字空間內。默認情況下,生成文件的內容被放入按類型庫命名的 C++ 名字空間內。

?

選項 name_guids 告訴編譯器我們希望類型庫中的 GUID 都有一個命名符號。默認情況下,因為名字 CLSID_PISvr 沒有定義,下面的語句不能被編譯:

?

::CoCreateInstance( CLSID_PISvr, … );

?

相反,應該使用下面的語句形式:

?

::CoCreateInstance( __uuidof ( PISvr), … );

?

我們同樣需要使用 __uuidof() 來獲取接口的 IID

?

選項 raw_interfaces_only 應該是最復雜的。默認情況下,當 #import 生成頭文件時,它不僅僅是生成接口類定義。實際上,生成包裝類使得 COM 接口盡可能便于使用。比如,考慮下面的接口定義:

?
interface ICalcPi : IDispatch {
? [propget, id(1), helpstring("property Digits")]
? HRESULT Digits([out, retval] LONG* pVal);
? [propput, id(1), helpstring("property Digits")]
? HRESULT Digits([in] LONG newVal);
? [id(2), helpstring("method CalcPi")]
? HRESULT CalcPi([out,retval] BSTR* pbstrPi);
};

通常情況下,可以如下使用這個接口:

?

HRESULT DoStuff( long nDigits, ICalcPi *pCalc ) {

??? HRESULT hr = pCalc->put_Digits( nDigits );

??? if( FAILED( hr ) ) return hr;

?

??? BSTR bstrResult;

??? hr = pCalc->CalcPi( &bstrResult );

??? if( FAILED( hr ) ) return hr;

?

??? std::cout << "PI to " << nDigits << " digits is "

??????? << CW2A( bstrResult );

?

??? ::SysFreeString( bstrResult );

??? return S_OK;

}

?

另外一種方法是使用 #import 語句,可以如下使用接口:

void DoStuff( long nDigits, ICalcPiPtr spCalc ) {
  spCalc->Digits = nDigits;
  _bstr_t bstrResults = spCalc->CalcPi();
  std::cout << "PI to " << spCalc->Digits << " digits is "
    << ( char * )bstrResults;
}

?

ICalcPiPtr 類型是一種智能指針,它由 _com_ptr_t 類用 typedef 定義得到。這個類本身并不屬于 ATL ,而是直接屬于 COM 編譯器的擴展部分,定義在系統的 comdef.h 頭文件中(封裝類使用的一些其他類型也同屬此文件)。智能指針自動管理引用計數, _bstr_t 類型管理 BSTR 的內存(第二章的“字符串和文本”討論)。

?

包裝類中最值得注意的就是后面的 HRESULT 試驗。作為替換,包裝類把所有的 HRESULT 錯誤都翻譯為 C++ 異常(更精確的 _com_error 類)。這樣就允許生成的代碼用方法的 [retval] 變量作為實際的返回值,排除了很多的臨時變量和輸出參數。

?

包裝類可以大大的簡化編寫 COM 客戶端,當然他們也有缺點( downside )。最大的缺點是需要使用 C++ 異常。在一些工程中我們不愿意為使用異常處理而帶來的效率代價,拋出異常就意味著要求開發人員在安全處理異常時必須非常小心。

?

ATL 開發人員來說,包裝類的另一個缺點是 ATL COM 接口(參考第三章“ ATL 智能類型”)和 BSTR (參考第二章)。 ATL 包裝類比 comdef.h 文件所定義的功能更好已是無可爭辯。比如,我們可以偶然地調用 ICalcPiPtr Release 方法,但是如果使用 ATL 包裝類,調用將產生編譯錯誤。

?

默認情況下,使用 #import 可以生成這些包裝類。如果決定不使用它們,或者因為某些原因不能編譯(我們已經知道,在處理一些復雜、生疏的類型庫,至少有一個謙虛的程序員偶然遇到這種編譯問題,),我們可以關閉這些包裝類,而使用 raw_interfaces_only 選項僅僅得到接口的直接定義。

?

1.12 ?ATL Server Web 工程

?

毫無疑問, ATL 庫最近所添加的最激動人心的就是:一組稱為術語 ATL Server 的類和工具集合。 ATL8.0 ATL3.0 大小增加近四倍,其中 ATL Server 占據了幾乎所有的增長空間。這些擴展類庫對建立 WEB 應用程序、 XML Web Servers 提供了非常全面的支持。雖然傳統的 ASP ASP.NET 平臺提供的基于 WEB 開發的易用框架具有很強的吸引力,但是仍然有很多應用程序開發人員在編寫應用程序需要利用原始的 ISAPI 編程,以獲得最底層的控制和最大的效率。 ATL Server 設計用來提供和 ISAPI 相近的性能和控制力,以及和 ASP 一樣的生產力。最后, ATL Server 也采用之前的設計模式,它使得 ATL 開發更方便,如過去一樣的高效:名字短小、快速、彈性的代碼。

?

VS 提供出色的向導來支持建立 WEB 應用程序和服務。實際上,縱覽 ATL Server 工程提供的大量可用選項,能幫助我們非常深刻的理解其結構,也是了解其支持提供功能的決好機會。 VS 提供了一個向導幫助我們用 ATL Server 建立 Web 應用程序。從新建工程對話框的 Visual C++ 文件夾下選擇 ATL Server 工程可以打開此向導。

?

1-21 所示的工程設置頁顯示了生成、部署我們的 WEB 應用程序所選項的選項。

?

1-21.JPG
1-21 ATL Server 工程的工程設置

?

默認情況下, ATL Servre 在解決方案中生成兩個工程:一個 Web 應用程序 DLL 和一個 ISAPI 擴展 DLL ISAPI 擴展 DLL 被加載到 IIS 進程中( inerinfo.exe ),邏輯結構上位于 IIS Web 應用程序 DLL 之間。盡管 ISAPI 擴展可以自己處理 HTTP 請求,更普通的做法是讓其提供通用的基礎結構服務,比如線程池和緩沖,而讓 Web 應用程序 DLL 提供真正的 HTTP 響應邏輯。 ATL Server 工程向導生成一個 ISAPI 擴展實現了 Web 應用程序調用處理函數中的特殊函數通信。圖 1-22 描述了這種關系。

?

1-22.JPG
1-22 基本的 ISAPI 結構

?

在圖 1-21 的工程設置對話框中, Generate Combined DLL 選擇框允許我們把所有的內容都合成到一個 DLL 當中。當 ISAPI 擴展不打算在其他的 Web 應用程序使用時,選擇它比較合適。相反如果不選擇它,開發人員就可以創建特殊的 ISAPI 擴展,利用自定義線程池、高速的緩存調整機制,提高 ATL Server 的擴展性特征。這些 ISAPI 擴展很可能會在多個 Web 應用程序之間交互運用。而且,讓 ISAPI 擴展保存在單獨的 DLL 當中,在我們向 Web 應用程序添加處理函數的時候有更大的彈性,不需要重新啟動 Web 服務(稍后討論處理類)。在我們的第一個 Web 應用程序中沒有選中此項,讓 VS 生成一個單獨的 DLL

?

Deployment Support 選擇框可以啟用 VS 網頁部署工具。選中此選項后, Visual Studio  編譯進程會自動執行一些步驟,以適當的部署我們的 WEB 應用程序使它利用 IIS 提供的服務。稍后就會看到這種集成部署的功能使多么的方便,

?

1-23 所示的 Server Options 選項中,可以選擇各種 Web 應用程序效率相關的選項。支持多種緩存類型,包括支持任意的二進制數據( Blob 緩存),文件緩存,數據庫連接緩存(數據源緩存)。此外,高效性站點是依賴于健壯的 Session 狀態管理。 ATL Server 提供了兩種機制持續化 Session 狀態。 OLE DB-backed session-state services 按鈕支持把 Session 狀態持續到數據庫(或者其他的 OLE DB 數據源),此選項對于運行在 Web Farms 上的應用程序非常有用。

?

1-23.JPG
1-23 ATL Server 工程的 Server Options

?

1-24 顯示在 Application Options 頁可用的選擇項。 Validation Support 項生成一些代碼對客戶的 HTTP 請求的項目進行驗證,比如請求參數和表單變量。 Stencil Processing Support 生成框架代碼以使用稱為服務響應文件( Server response files SRF )的 HTML 代碼模板。這些文本文件(也稱為模板)以 .srf 為擴展名,含有帶特殊替代標簽的靜態 HTML 內容,這些內容經過我們的代碼處理后可以在運行時生成動態內容。啟用 Stencil Processin 后,向導允許我們選擇響應的適當地點和代碼頁。 Create as Web Service 會在后續章節做進一步的討論。因為我們現在開發的是 Web 應用程序,現在我們不選擇此項。

?

1-24.JPG
1-24? ATL Server Application Options

?

ATL Server 工程中可以設置的其他項都在 Developer Support Options 頁,如圖 1-25 所示。 Generating TODO comments 簡單的提醒開發人員注意附加實現應該提供的區域。如果我們選中 Custom Assert and Trace Handling Support ,調試編譯時會包含一個 CDebugReportHook 類的實例,它能大大的簡化從遠程機器上調試 Web 應用程序的過程。

?

1-25.JPG
1-25? ATL Server Developer Support Options

?

點擊圖 1 25 Finish 按鈕,向導會生成一個解決方案,其中包含兩個工程:一個是 Web 應用程序 DLL (名稱與我們在 New Project 對話框中輸入的工程名一樣);一個是 ISAPI 擴展(名稱是工程名加上 Isapi )。我們先看看在 ISAPI 擴展工程中生成的代碼。生成的 ISAPI 擴展 .cpp 文件內容如下:

?

class CPiSvrWebAppModule :

public CAtlDllModuleT<CPiSvrWebAppModule> {

public:

};

?

CPiSvrWebAppModule _AtlModule;

?

typedef CIsapiExtension<> ExtensionType;

?

// The ATL Server ISAPI extension

ExtensionType theExtension;

?

// Delegate ISAPI exports to theExtension

extern "C"

DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB) {

??? return theExtension.HttpExtensionProc(lpECB);

}

?

extern "C"

BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer) {

??? return theExtension.GetExtensionVersion(pVer);

}

?

extern "C" BOOL WINAPI TerminateExtension(DWORD dwFlags) {

??? return theExtension.TerminateExtension(dwFlags);

}

?

// DLL Entry Point

extern "C"

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason,

??? LPVOID lpReserved) {

??? hInstance;

??? return _AtlModule.DllMain(dwReason, lpReserved);

}

?

因為 ISAPI 擴展使用了 ATL 的對象創建功能,它也需要一個 ATL 模塊對象。同樣,在其生成的代碼中也實現了三個非常有名的入口點: HttpExtensionProc GetExtensionVersion TerminateExtension IIS 正是用它們與 ISAPI 擴展進行通信,處理 HTTP 請求信息。這些實現都被簡單的委派到 CIsapiExtension 的全局實例,其定義如下:

?

template <????????????????????????????????????????? ??

? class ThreadPoolClass=CThreadPool<CIsapiWorker>,???? ??

? class CRequestStatClass=CNoRequestStats,???????????? ??

? class HttpUserErrorTextProvider=CDefaultErrorProvider,??????

? class WorkerThreadTraits=DefaultThreadTraits,???????? ???

? class CPageCacheStats=CNoStatClass,??????????????????

? class CStencilCacheStats=CNoStatClass?????????? ??????

>?????????????????????????????????????????????????? ???????????????

class CIsapiExtension :????????????????????????????????

? public IServiceProvider,????????????????????????????? ?

? public IIsapiExtension,????????????????????????????? ??

? public IRequestStats????????????????????????????????

{... }>?????????????????????? ????????????????????????

?

此類提供了實現 ISAPI 擴展的樣板函數。類中的模板參數提供了某些功能的插件實現,比如線程池管理、錯誤報告和靜態緩沖。在類的 .CPP 文件中,替換為我們自己從 CIsapiExtension 派生的類作為模板參數,這樣就可以高度自定義 ISAPI 擴展的行為。具體的實現技術在第十三章“你好, ATL Server ”講述。 ISAPI 擴展的默認實現對現在的演示目的已經比較適用。

?

大多數的編碼都是在 Web 應用程序工程中進行的。向導為我們生成一個 SRF 文件框架并加入到工程中。集成到 VS 中的 HTML 編輯器使我們能非常方便的查看、操縱文件內容。


<html>
{{ handler PiSvrWebApp.dll/Default }}
??? <head>
??? </head>
??? <body>
??????? This is a test: {{Hello}}<br>
??? </body>
</html>
?

在雙大括號之間的命令項將被傳遞給模板處理器。 {{handler}} 命令指定了響應類的宿主 DLL 名稱,而類用來處理出現在 SRF 文件標簽替換。其中的 /Default 參數能確保在處理標簽替換時使用默認的請求處理類。一般來說,應用程序 DLL 可以包含多個處理 SRF 命令的處理類,甚至這些類可以存在于多個 DLL 中。我們在單個的 DLL 中僅僅使用一個處理類,因此流向處理類的所有命令都將被路由到同一處理類。在早期向導生成的框架中, {{Hello}} 標簽將傳遞到一個處理類,被類實現方法中生成的 HTML 所替換。

?

在我們的應用程序 DLL 中, ATL Server 通過幾個宏把 SRF 文件的命名映射到處理類。向導生成的 <projectname>.h 定義中,說明了這些宏是怎樣使用的:

?

class CPiSvrWebAppHandler
??? : public CRequestHandlerT<CPiSvrWebAppHandler>
{
public:
??? BEGIN_REPLACEMENT_METHOD_MAP(CPiSvrWebAppHandler)
??????? REPLACEMENT_METHOD_ENTRY("Hello", OnHello)
??? END_REPLACEMENT_METHOD_MAP()

??? HTTP_CODE ValidateAndExchange() {
??????? // Set the content-type
??????? m_HttpResponse.SetContentType("text/html");
??????? return HTTP_SUCCESS;
??? }

protected:
??? HTTP_CODE OnHello(void) {
??????? m_HttpResponse << "Hello World!";
??????? return HTTP_SUCCESS;
??? }
};

基類
CRequestHandlerT 提供了一個請求處理類的實現。用 REPLACEMENT_METHOD_MAP SRF 文件中的字符串映射到中適當的處理函數。

在處理器
DLL .CPP 文件,除了請求處理類本身,還有一些其他分全局宏:

BEGIN_HANDLER_MAP()
??? HANDLER_ENTRY("Default", CPiSvrWebAppHandler)
END_HANDLER_MAP()

HANDLER_MAP
宏被用來判斷使用哪個類處理帶特殊名稱的替換。在這種情況下, ”Default” 字符串,同 SRF 文件中的處理標簽一樣,被映射到 CPiSvrWebAppHandler 類。當在 SRF 文件中遇到 {{Hello}} 標簽時, OnHello 方法被調用(通過 REPLACEMENT_METHOD_MAP )。它用聲明在 CRequestHandlerT 中的一個 CHttpReponse 成員變量實例去生成標簽的替換代碼。

讓我們修改向導生成的代碼,以根據
HTTP 請求字符串中指定的小數位數顯示 PI 結果。首先,把 SRF 文件按照如下修改:

<html>
{{ handler PiSvrWebApp.dll/Default }}
??? <head>
??? </head>
??? <body>
??????? PI = {{Pi}}<br>
??? </body>
</html>

然后,我們添加一個稱為
OnPi 的替換方法到處理類,再用 [tag_name] 屬性把此方法與 {{Pi}} 替換標簽關聯起來。在 OnPi 方法的實現中,我們從查詢字符串取得請求的小數位數。存儲在 m_HttpRequest 成員變量中的 CHttpRequest 類暴露一個 CHttpRequestParams 實例。此類提供一個簡單的 Lookup 方法從查詢字符串取得單獨的查詢參數,作為名稱值對。因此處理類似下面的請求就非常簡單:


http://localhost/PiSvrWebApp/PiSvrWebApp.srf?digits=6

當我們編譯解決方案時,
VS 根據我們的行為執行一些方便的任務。因此它是 Web 應用程序,不能簡單的把代碼編譯入 DLL 就算結束。應用程序必須被適當的部署到 Web 服務器上,并在 IIS 注冊。我們需要創建一個虛擬目錄,指定一個合適的隔離處理級別,把 .srf 后綴的文件映射到我們的 ISAPI 擴展 DLL 。回想一下,創建工程的時候,我們在 ATL Server 工程向導的 Project Settings 頁選擇了 deployment support 項,參考前圖 1-21 。因此, VS 會自動調用工具 VCDeploy.exe 為我們執行所有的部署步驟。簡單的用普通方式編譯解決方案,把我們的應用程序 DLL ISAPI 擴展 DLL SRF 文件都放到默認 Web 站點下的一個目錄。通常是位于目錄 <drive>:\inetpub\wwwroot\<projectName> VS 使用我們的 Web 應用程序工程名稱作為虛擬目錄名稱,因此瀏覽 http://localhost/PiSvrWebApp/PiSvrWebApp.srf?digits=50將產生圖1-26 所示的結果:

?

1-26.JPG
1-26 顯示 PI 50 小數的 Web 應用程序

?

關于用 ATL Server 建立 ISAPI 應用程序,包括 Web Services 的更多信息,請參考第十三章“你好 , ATL Server ”。

?

1.13 ? 總結

?

在本章,我們入旋風般的瀏覽了 ATL 向導提高的一些功能,包括一些基本的接口實現。即使有豐富的向導功能, ATL 也不是牢固的 COM 知識的替代品,這一點勿庸置疑。我們仍需要掌握如何設計、實現自定義接口。在本書的剩余部分你將看到,我們仍需要熟悉接口指針、引用計數、運行時類型發現、線程、持續化等等。 ATL 能幫助我們,當我們仍需要熟悉 COM

?

我們必須知道:向導并不能使我們親密接觸 ATL 或者 Web 應用程序開發。在本章看到的 ATL 信息的精選功能,都有不止 10 個突出的細節、擴展和缺點。盡管向導可以節省我們很多時間,它并不能完成所有的工作。它不能確保設計和實現目標滿足我們的要求,那是我們的職責。