上一章的「Hello, World!」ATL 版可能讓許多朋友不屑一顧——它只多了一個 CComModule,連視窗類註冊、訊息迴路都沒有。但我堅持:它幾乎已擁有 ATL GUI 程式的所有組成:入口、初始化、程式主體、卸載。
「等一下!」你可能打斷:「註冊視窗類、訊息迴路呢?」沒錯,對完整 GUI 程式來說這也是必要的。
為照顧熟悉 MFC 卻不諳 Win32 基礎的讀者,特別準備此節(SDK 愛好者可跳過)。
//////////////////////////////////////////////////////////////////////////
// ATL GUI 程式設計配套原始碼
// 第二章 一個最簡單視窗程式的轉型
// 專案名稱:HelloSDK
// 作者:李馬
// https://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:
{ PostQuitMessage( 0 ); }
break;
case WM_PAINT:
{
HDC hdc; PAINTSTRUCT ps;
hdc = BeginPaint( hWnd, &ps );
DrawText( hdc, _T("Hello, SDK!"), -1, &ps.rcPaint, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
EndPaint( hWnd, &ps );
}
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 ); // 註冊視窗類
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 );
MSG msg;
while ( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return msg.wParam;
}
這段代碼雖顯冗長,實則條理分明,體現 Win32 GUI 完整流程:
InitApplication 完成。視窗類似 OOP 中的「類」,但包含的是「屬性與響應」而非「屬性與方法」。InitInstance,負責建立並顯示視窗。HelloWndProc 為回呼函數,處理 WM_DESTROY(發送 WM_QUIT 結束迴路)、WM_PAINT(繪製文字)、其他訊息交給 DefWindowProc。先展示 ATL 版 WinMain:
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd )
{
_Module.Init( NULL, hInstance );
CHelloATLWnd wnd;
wnd.Create( NULL, CHelloATLWnd::rcDefault, _T("Hello ATL") );
wnd.ShowWindow( nShowCmd );
wnd.UpdateWindow();
MSG msg;
while ( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
_Module.Term();
return msg.wParam;
}
_Module 依舊只有 Init/Term,訊息迴路也未變。關鍵差異在 CHelloATLWnd——你可能猜測:
CreateWindow、ShowWindow 等 API揭開 CHelloATLWnd 面紗:
class CHelloATLWnd : public CWindowImpl< CHelloATLWnd, CWindow, CWinTraits< WS_OVERLAPPEDWINDOW > >
{
public:
CHelloATLWnd()
{
CWndClassInfo& wci = GetWndClassInfo();
wci.m_bSystemCursor = TRUE;
wci.m_lpszCursorID = IDC_ARROW;
wci.m_wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
wci.m_wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
}
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;
}
};
本章不解釋細節,繼續猜想:
DECLARE_WND_CLASS 宏完成。BEGIN_MSG_MAP/END_MSG_MAP 為 ATL 訊息映射,MESSAGE_HANDLER 分發訊息。CWinTraits 有關。疑問:CWindowImpl、CWindow、CWinTraits 是什麼?視窗類何時註冊?訊息分發如何實現?請期待下章,或直接閱讀 atlwin.h 原始碼。
本書針對 ATL 3.0/VC6.0,疏忽了 CComModule 在 ATL 7.0 的演進。感謝網友指點,ATL 7.0 中 CComModule 功能拆分為多個類:
CAtlBaseModule:模組與資源實例 HINSTANCECAtlComModule:COM 類資訊CAtlWinModule:視窗類資訊CAtlDebugInterfacesModule:介面除錯支援CAtlModule 派生類:CAtlDllModuleT (DLL)、CAtlExeModuleT (EXE)、CAtlServiceModuleT (服務)拆分原因:功能顆粒化、不同功能分層、自動宣告全域實例、消除手動呼叫 Init/Term(移至建構/解構函數)。但為相容性與 WTL 考量,本系列仍沿用 ATL 3.0 CComModule。