1.11 ? 容納控件

?

1.11.1????? 容納控件

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

?

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

?

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

?

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

?

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

?

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

?

容納對話框運行顯示時,控件被創(chuàng)建,同時根據(jù)開發(fā)階段設(shè)置的屬性初始化控件。圖 1-20 顯示了容納了一個控件的對話框。

?

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

?

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

?

1.11.2 C++ COM 客戶端

?

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

?

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

?

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

?

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

?

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

?

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

?

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

?

#import “pisvr.dll” <options>

?

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

?

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

?

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

?

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

?

::CoCreateInstance( CLSID_PISvr, … );

?

相反,應(yīng)該使用下面的語句形式:

?

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

?

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

?

選項 raw_interfaces_only 應(yīng)該是最復(fù)雜的。默認情況下,當 #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 編譯器的擴展部分,定義在系統(tǒng)的 comdef.h 頭文件中(封裝類使用的一些其他類型也同屬此文件)。智能指針自動管理引用計數(shù), _bstr_t 類型管理 BSTR 的內(nèi)存(第二章的“字符串和文本”討論)。

?

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

?

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

?

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

?

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

?

1.12 ?ATL Server Web 工程

?

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

?

VS 提供出色的向?qū)碇С纸?/span> WEB 應(yīng)用程序和服務(wù)。實際上,縱覽 ATL Server 工程提供的大量可用選項,能幫助我們非常深刻的理解其結(jié)構(gòu),也是了解其支持提供功能的決好機會。 VS 提供了一個向?qū)椭覀冇?/span> ATL Server 建立 Web 應(yīng)用程序。從新建工程對話框的 Visual C++ 文件夾下選擇 ATL Server 工程可以打開此向?qū)А?/span>

?

1-21 所示的工程設(shè)置頁顯示了生成、部署我們的 WEB 應(yīng)用程序所選項的選項。

?

1-21.JPG
1-21 ATL Server 工程的工程設(shè)置

?

默認情況下, ATL Servre 在解決方案中生成兩個工程:一個 Web 應(yīng)用程序 DLL 和一個 ISAPI 擴展 DLL ISAPI 擴展 DLL 被加載到 IIS 進程中( inerinfo.exe ),邏輯結(jié)構(gòu)上位于 IIS Web 應(yīng)用程序 DLL 之間。盡管 ISAPI 擴展可以自己處理 HTTP 請求,更普通的做法是讓其提供通用的基礎(chǔ)結(jié)構(gòu)服務(wù),比如線程池和緩沖,而讓 Web 應(yīng)用程序 DLL 提供真正的 HTTP 響應(yīng)邏輯。 ATL Server 工程向?qū)梢粋€ ISAPI 擴展實現(xiàn)了 Web 應(yīng)用程序調(diào)用處理函數(shù)中的特殊函數(shù)通信。圖 1-22 描述了這種關(guān)系。

?

1-22.JPG
1-22 基本的 ISAPI 結(jié)構(gòu)

?

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

?

Deployment Support 選擇框可以啟用 VS 網(wǎng)頁部署工具。選中此選項后, Visual Studio  編譯進程會自動執(zhí)行一些步驟,以適當?shù)牟渴鹞覀兊?/span> WEB 應(yīng)用程序使它利用 IIS 提供的服務(wù)。稍后就會看到這種集成部署的功能使多么的方便,

?

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

?

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

?

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

?

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

?

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

?

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

?

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

?

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 的對象創(chuàng)建功能,它也需要一個 ATL 模塊對象。同樣,在其生成的代碼中也實現(xiàn)了三個非常有名的入口點: HttpExtensionProc GetExtensionVersion TerminateExtension IIS 正是用它們與 ISAPI 擴展進行通信,處理 HTTP 請求信息。這些實現(xiàn)都被簡單的委派到 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????????????????????????????????

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

?

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

?

大多數(shù)的編碼都是在 Web 應(yīng)用程序工程中進行的。向?qū)槲覀兩梢粋€ SRF 文件框架并加入到工程中。集成到 VS 中的 HTML 編輯器使我們能非常方便的查看、操縱文件內(nèi)容。


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

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

?

在我們的應(yīng)用程序 DLL 中, ATL Server 通過幾個宏把 SRF 文件的命名映射到處理類。向?qū)傻?/span> <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 提供了一個請求處理類的實現(xiàn)。用 REPLACEMENT_METHOD_MAP SRF 文件中的字符串映射到中適當?shù)奶幚砗瘮?shù)。

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

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

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

讓我們修改向?qū)傻拇a,以根據(jù)
HTTP 請求字符串中指定的小數(shù)位數(shù)顯示 PI 結(jié)果。首先,把 SRF 文件按照如下修改:

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

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


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

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

?

1-26.JPG
1-26 顯示 PI 50 小數(shù)的 Web 應(yīng)用程序

?

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

?

1.13 ? 總結(jié)

?

在本章,我們?nèi)胄L般的瀏覽了 ATL 向?qū)岣叩囊恍┕δ埽ㄒ恍┗镜慕涌趯崿F(xiàn)。即使有豐富的向?qū)Чδ埽?/span> ATL 也不是牢固的 COM 知識的替代品,這一點勿庸置疑。我們?nèi)孕枰莆杖绾卧O(shè)計、實現(xiàn)自定義接口。在本書的剩余部分你將看到,我們?nèi)孕枰煜そ涌谥羔槨⒁糜嫈?shù)、運行時類型發(fā)現(xiàn)、線程、持續(xù)化等等。 ATL 能幫助我們,當我們?nèi)孕枰煜?/span> COM

?

我們必須知道:向?qū)Р⒉荒苁刮覀冇H密接觸 ATL 或者 Web 應(yīng)用程序開發(fā)。在本章看到的 ATL 信息的精選功能,都有不止 10 個突出的細節(jié)、擴展和缺點。盡管向?qū)Э梢怨?jié)省我們很多時間,它并不能完成所有的工作。它不能確保設(shè)計和實現(xiàn)目標滿足我們的要求,那是我們的職責。