1.11
?
容納控件
?
1.11.1?????
容納控件
利用
ATL
所支持的控件容納,可實現容納控件。比如,
CAxDialogImpl
中的
Ax
兩字就表示
ActiveX
控件,表示對話框具有容納控件的能力。在對話框中實現容納控件,只需在對話框資源上點擊右鍵,從彈出菜單選擇
Insert ActiveX Control
,然后彈出一個對話框,列舉了系統安裝的所有控件,如圖
1-17
所示。
?
圖
1-17 ?
插入
ActiveX
控件對話框
?
插入控件后,點擊控件可以在控件的屬性窗口設置控件的屬性。如圖
1-18
所示。
?
圖
1-18
控件屬性對話框
?
在屬性對話框的工具欄上點擊控件事件按鈕,還可以選擇處理控件的事件,如圖
1-19
所示。
?
圖
1-19
選擇處理的控件事件
?
容納對話框運行顯示時,控件被創建,同時根據開發階段設置的屬性初始化控件。圖
1-20
顯示了容納了一個控件的對話框。
?
圖
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 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
基本的
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 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? ATL Server
的
Application Options
頁
?
ATL Server
工程中可以設置的其他項都在
Developer Support Options
頁,如圖
1-25
所示。
Generating TODO comments
簡單的提醒開發人員注意附加實現應該提供的區域。如果我們選中
Custom Assert and Trace Handling Support
,調試編譯時會包含一個
CDebugReportHook
類的實例,它能大大的簡化從遠程機器上調試
Web
應用程序的過程。
?
圖
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
顯示
PI
后
50
小數的
Web
應用程序
?
關于用
ATL Server
建立
ISAPI
應用程序,包括
Web Services
的更多信息,請參考第十三章“你好
, ATL Server
”。
?
1.13
?
總結
?
在本章,我們入旋風般的瀏覽了
ATL
向導提高的一些功能,包括一些基本的接口實現。即使有豐富的向導功能,
ATL
也不是牢固的
COM
知識的替代品,這一點勿庸置疑。我們仍需要掌握如何設計、實現自定義接口。在本書的剩余部分你將看到,我們仍需要熟悉接口指針、引用計數、運行時類型發現、線程、持續化等等。
ATL
能幫助我們,當我們仍需要熟悉
COM
。
?
我們必須知道:向導并不能使我們親密接觸
ATL
或者
Web
應用程序開發。在本章看到的
ATL
信息的精選功能,都有不止
10
個突出的細節、擴展和缺點。盡管向導可以節省我們很多時間,它并不能完成所有的工作。它不能確保設計和實現目標滿足我們的要求,那是我們的職責。