Posted on 2007-10-17 10:37
天之驕子 閱讀(553)
評論(0) 編輯 收藏 引用
第二章 一個最簡單窗口程序的轉型
我知道,可能會有很多朋友對上一章的“Hello, World!”ATL版不以為然,因為它并不能算是什么ATL程序——畢竟它只不過是有了個CComModule而已。不過不管怎樣我還是要說,它幾乎仍然擁有了一個ATL GUI程序的所有組成部分:入口、初始化、程序體、卸載……
“等等!”也許你會突然打斷我,“——還有注冊窗口類、消息循環(huán)呢?”
當然,對于一個完整的GUI程序來講,這也是必要的。
貌似廢話
不清楚你是否已經(jīng)為本章的內(nèi)容做好了準備,因為下面我們就要動真格的了。不過考慮到本書的讀者群中可能會存在著相當一部分了解MFC卻對Win32 GUI的基本原理和流程不甚熟悉的朋友,所以李馬特別為你們準備了這一節(jié)的內(nèi)容。SDK的粉絲們可以跳過這一節(jié),如果你們覺得李馬講的有些拖沓冗長的話。
那么,我還是先以一個標準的Win32 SDK程序開始:
////////////////////////////////////////////////////////////////////////// // ATL的GUI程序設計配套源代碼 // 第二章 一個最簡單窗口程序的轉型 // 工程名稱:HelloSDK // 作者:李馬 // http://www.titilima.cn //////////////////////////////////////////////////////////////////////////
#include <windows.h> #include <tchar.h>
LRESULT CALLBACK HelloWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch ( uMsg ) { case WM_DESTROY: break; case WM_PAINT: break; default: return DefWindowProc( hWnd, uMsg, wParam, lParam ); } return 0; }
BOOL InitApplication( HINSTANCE hInstance ) { WNDCLASS wc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH ); wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hIcon = LoadIcon( NULL, IDI_APPLICATION ); wc.hInstance = hInstance; wc.lpfnWndProc = HelloWndProc; wc.lpszClassName = _T("HelloSDK"); wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW;
return RegisterClass( &wc ); }
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd ) { // 注冊窗口類 InitApplication( hInstance );
// 創(chuàng)建窗口 HWND hWnd = CreateWindow( _T("HelloSDK"), _T("Hello SDK"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); ShowWindow( hWnd, nShowCmd ); UpdateWindow( hWnd );
// 消息循環(huán) MSG msg; while ( GetMessage( &msg, NULL, 0, 0 ) )
return msg.wParam; } |
不知道你是否會覺得這段代碼有些冗長?事實上,這個程序已經(jīng)體現(xiàn)了Win32 GUI程序運行的所有流程(請注意,我并不會對這些代碼進行詳細的解釋,因為我已經(jīng)假設你已經(jīng)了解了這些代碼具體行為的必要細節(jié)。如果不是這樣的話,請參考相關的書籍或者MSDN):
- 注冊窗口類的部分。在這個程序中,InitApplication函數(shù)完成了這一工作。窗口類的概念類似于OO(面向?qū)ο螅┲械念悾心阍赪indows中能看到的窗口都是某個特定窗口類的一份實例。但是,窗口類并非任何一種OOP語言中的類——它所包括的并不是通稱的屬性和方法(在C++中稱作成員變量和成員函數(shù)),而是屬性和響應。這個區(qū)別可能會使你感到費解,我會在下一章中為你詳細介紹——因為ATL中對窗口的封裝類將這一點體現(xiàn)得十分淋漓盡致。
- 創(chuàng)建窗口的部分。在通常的SDK代碼里,這些代碼被封裝在一個名為InitInstance的函數(shù)中。這段代碼所做的工作一般是創(chuàng)建窗口并將其顯示出來。
- 消息循環(huán)。Windows是一個基于消息機制的操作系統(tǒng),各個窗口之間的通信也主要是靠Windows消息來完成的。而程序中的消息循環(huán)也就是將本程序UI線程中的消息隊列中提取各種消息,進行處理(如果有必要的話)之后分發(fā)給各個消息的屬主窗口(或者說是目標窗口)。
在這里需要指出的是,HelloWndProc是我們自己定義的一個函數(shù),我們需要用它來控制我們對特定窗口消息的特定響應。我們只需要在注冊窗口類之前,將這個函數(shù)的地址(也就是函數(shù)名)賦值給WNDCLASS::lpfnWndProc成員就可以了。這個函數(shù)我們自己不需要進行調(diào)用,它的調(diào)用是當我們的窗口收到窗口消息后,由Windows完成的。在這個回調(diào)函數(shù)中,我們的處理是這樣的:
- WM_DESTROY。在窗口被銷毀的時候,窗口會收到此消息。在這里,我們會調(diào)用PostQuitMessage,用以向當前UI線程的消息隊列之中發(fā)送一條WM_QUIT消息,GetMessage在收到這條消息后,會返回FALSE,也就結束了消息循環(huán),WinMain也就結束了。
- WM_PAINT。在窗口需要繪制的時候,窗口會收到此消息。在這里我們只是簡單的在窗口的中間繪制了一行文字“Hello, SDK!”。
- 其它消息。這些消息都是我們不關心的,所以我們將其交由系統(tǒng)默認的窗口過程DefWindowProc來處理。
這段代碼貌似冗長,但實際上還是很有條理的,你可以根據(jù)它以及我以上的解說來對照這個程序的ATL版本。
ATL等同品
在寫作這本書的時候,我總是希望我每次都能夠能使用讓你不太陌生的代碼來循序漸進地引導你。考慮再三,對于“Hello, ATL!”的這個程序,我決定先把它的WinMain展現(xiàn)給你:
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd ) { _Module.Init( NULL, hInstance );
// 創(chuàng)建窗口 CHelloATLWnd wnd; wnd.Create( NULL, CHelloATLWnd::rcDefault, _T("Hello ATL") ); wnd.ShowWindow( nShowCmd ); wnd.UpdateWindow();
// 消息循環(huán) MSG msg; while ( GetMessage( &msg, NULL, 0, 0 ) )
_Module.Term(); return msg.wParam; } |
OK,上一章介紹過的_Module又出現(xiàn)在你的眼前了——不過還是沒有什么特別的變化,仍然是那熟悉的Init和Term。而且,正如“山喲還是那座山”一樣,消息循環(huán)喲也仍然是那個消息循環(huán)。當然,你肯定也發(fā)現(xiàn)了那寥寥的變化:CHelloATLWnd是什么?在我將它的代碼展現(xiàn)給你之前,你可能會做出這樣的猜想:
- 這是一個C++類,它對Win32窗口類進行了封裝。
- 這個類封裝了大多數(shù)窗口操作的API函數(shù),諸如CreateWindow、ShowWindow、UpdateWindow。
- 窗口類的注冊可能也是在這個C++類中完成的。
好,打住,這就夠了。讓我們來撩開CHelloATLWnd那貌似神秘的面紗吧,趕緊著。
class CHelloATLWnd : public CWindowImpl< CHelloATLWnd, CWindow, CWinTraits< WS_OVERLAPPEDWINDOW > > { public: CHelloATLWnd() public: DECLARE_WND_CLASS( _T("HelloATL") ) public: BEGIN_MSG_MAP( CHelloATLWnd ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) MESSAGE_HANDLER( WM_PAINT, OnPaint ) END_MSG_MAP() public: LRESULT OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& hHandled ) { ::PostQuitMessage( 0 ); return 0; } LRESULT OnPaint( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& hHandled ) { HDC hdc; PAINTSTRUCT ps;
hdc = BeginPaint( &ps ); DrawText( hdc, _T("Hello, ATL!"), -1, &ps.rcPaint, DT_CENTER | DT_VCENTER | DT_SINGLELINE ); EndPaint( &ps ); return 0; } }; |
猜想,還是猜想!
請允許我在本章中不為你解釋這個類的任何具體細節(jié),取而代之的是繼續(xù)的猜想。因為,這個類中需要解釋的東西太多了,以至于我必須為它單獨開辟一章。
- 窗口類的注冊是由這個C++類的構造函數(shù)與DECLARE_WND_CLASS宏一起完成的。
- 對于BEGIN_MSG_MAP與END_MSG_MAP這一部分,想必使用過MFC的朋友們應該更容易理解。是的,這一對宏可以算作ATL的消息映射,在其中由MESSAGE_HANDLER作為消息分流器,將各種窗口消息分配給各個處理函數(shù)。
- 創(chuàng)建窗口時指定的樣式貌似和模板參數(shù)CWinTraits有關。
當然,除了這些猜想之外,你可能還會同時存在以下疑問:
- CWindowImpl、CWindow、CWinTraits究竟是什么?
- 窗口類是在何時注冊的?
- 消息分流器是如何實現(xiàn)的?
也許你還會有更多的疑問,那么就讓我一并將它們留到下一章再解決吧。如果你實在等不及的話,atlwin.h的代碼也會告訴你一切的。
補敘CComModule
由于這本書主要針對的是ATL 3.0/Visual C++ 6.0,所以我疏忽了對CComModule的研究。在此感謝老李老刀兄提出的一點,就是CComModule在ATL 7.0中已經(jīng)不建議使用了。于是我將MSDN中的相關章節(jié)摘抄下來,權作借花獻佛之用。
CComModule 替換類
ATL 的早期版本使用 CComModule。在 ATL 7.0 中,CComModule 功能被若干個類所取代:
- CAtlBaseModule 包含大多數(shù)使用 ATL 的應用程序所需的信息。包含模塊和資源實例的 HINSTANCE。
- CAtlComModule 包含 ATL 中的 COM 類所需的信息。
- CAtlWinModule 包含 ATL 中的窗口化類所需的信息。
- CAtlDebugInterfacesModule 包含接口調(diào)試支持。
- CAtlModule 下列 CAtlModule 派生的類被自定義為包含特定應用程序類型中所需的信息。這些類中的大部分成員都可以被重寫:
CAtlDllModuleT 在 DLL 應用程序中使用。為標準導出提供代碼。
CAtlExeModuleT 在 EXE 應用程序中使用。提供 EXE 中所需的代碼。
CAtlServiceModuleT 為創(chuàng)建 Windows NT 和 Windows 2000 服務提供支持。
CComModule 仍然可用以便向后兼容。
分布 CComModule 功能的原因
由于以下原因,CComModule 的功能分布到了幾個新類中:
- 使 CComModule 中的功能呈粒狀分割。
對 COM、窗口化、接口調(diào)試和應用程序特定的(DLL 或 EXE)功能的支持現(xiàn)在在不同的類中。
- 自動為這些模塊的每一個聲明全局實例。
所需模塊類的全局實例鏈接到項目中。
- 消除了調(diào)用 Init 和 Term 方法的必要性。
Init 和 Term 方法已移動到模塊類的構造函數(shù)和析構函數(shù)中;不再需要調(diào)用 Init 和 Term。
不過,出于代碼的兼容性以及WTL的內(nèi)容考慮,本系列后續(xù)文章仍然將使用ATL 3.0中的CComModule。