概述
我們已經介紹了Windows SDK的Hello程式,它的流程主要分為三個步驟:
- 註冊視窗類別(RegisterClass)。並且我們詳細解釋了為何要有視窗類別,為何要RegisterClass。
- 建立並顯示視窗(CreateWindow and ShowWindow)。
- 訊息迴圈(MessageLoop)。即:取得訊息 -> 指派訊息 -> 處理訊息。
這裡,我們就要結合WINX的Hello程式,把整個流程串一遍。
作為比較,我想溫習一下ATL/WTL的Hello程式。我們在此提供了幾篇剖析ATL/WTL的Hello程式的好文章:
- Win32 vs. ATL Windows Programming
- MFC程式員的WTL開發指南之ATL介面類
WINX的Hello程式
#define WINX_USE_APPMODULE
#include <winx.h>
class CHelloMainFrame : public winx::MainFrame<CHelloMainFrame>
{
WINX_CLASS("CHelloMainFrame");
public:
void OnPaint(HWND hWnd)
{
winx::PaintDC dc(hWnd);
dc.TextOut(1, 1, _T("Hello, WINX!"));
}
};
winx::CAppModule _Module;
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
CAppModuleInit module;
CHelloMainFrame::RegisterClass();
CHelloMainFrame wndMain;
wndMain.Create(NULL, _T("Hello"));
return module.Run();
}
WINX的編程模型1. 註冊視窗類別(RegisterClass)
WINX中RegisterClass是需要主動調用的,這倒省了象ATL/WTL那樣解釋半天:-)
區別於已知的所有C++介面庫(MFC、ATL/WTL、SmartWin、wxWidgets等等,甚至包括我早期寫的SW系統),WINX傾向於把RegisterClass概念告訴使用者。並且,為此我專門寫了一篇“Windows精解:視窗類別釋疑”來解釋相關概念的重要性。這一切與WINX的可視化策略有關,我們在“WINX如何做到可視化介面開發”中詳述這一點。
以下這些宏與WINX的RegisterClass有關:
- WINX_CLASS / WINX_CLASS_EX
- WINX_CLASS_STYLE
- WINX_DEFAULT_BKGND / WINX_DEFAULT_COLOR / WINX_DEFAULT_BRUSH
- WINX_DEFAULT_CURSOR / WINX_DEFAULT_SYSCURSOR
它們分別對應Windows視窗類別(WNDCLASSEX)中的成員:
- lpszClassName
- style
- hbrBackground
- hCursor
大家已經熟悉用WINX_CLASS指定視窗類別的名稱,其他宏的用法完全一致。例如,預設滑鼠游標是箭頭(IDC_ARROW),要改為象Edit控制項一樣使用IDC_IBEAM,很容易:
class CHelloMainFrame : public winx::MainFrame<CHelloMainFrame>
{
WINX_DEFAULT_SYSCURSOR(IDC_IBEAM);
...
};
2. 初始化類(InitClass)
WINX引入了許多小巧的初始化類。大致有:
- CComAppInit - COM初始化類,即CoInitialize/CoUninitialize對。
- COleAppInit - OLE初始化類,即OleInitialize/OleUninitialize對。
- CDebugAppInit - 啟動記憶體流失調試(僅Debug版本,Release版本為空白類)。
- CComModuleInit - CComModule Init/Term。
- CAppModuleInit - CAppModule Init/Term。
- GdiplusAppInit - GdiplusStartup/GdiplusShutdown。
這些初始化類代碼簡單,但是抽象得恰到好處。在WINX之前,我曾經試圖把這些初始化過程封裝起來不讓使用者看到,但是最終不得不放棄。
3. 訊息迴圈(MessageLoop)
目前,WINX並未提供自己的訊息迴圈。我們借用WTL的CMessageLoop::Run。你沒有在WINX的例子中見到CMessageLoop,是因為它被CAppModuleInit 類隱藏起來了。
class CAppModuleInit : public WTL::CMessageLoop
{
public:
CAppModuleInit(
_ATL_OBJMAP_ENTRY* p = NULL,
HINSTANCE hInst = GetThisModule(),
const GUID* plibid = NULL)
{
_Module.Init(p, hInst, plibid);
_Module.AddMessageLoop(this);
}
~CAppModuleInit()
{
_Module.Term();
}
};
4. 視窗過程(WindowProc)
訊息迴圈中,訊息最終被Windows發送到視窗過程(WindowProc)中。那麼WINX的視窗過程在哪?
template <class WindowClass, class HandleClass = DefaultWindowHandle>
class Window
{
public:
static LRESULT CALLBACK WindowProc(
HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
WindowClass* pWnd = (WindowClass*)WindowMap::GetWindow(hWnd);
if (pWnd == NULL)
{
if (message != WM_NCCREATE)
return pWnd->InternalDefault(hWnd, message, wParam, lParam);
LPCREATESTRUCT lpCS = (LPCREATESTRUCT)lParam;
if (lpCS->lpCreateParams) {
pWnd = (WindowClass*)lpCS->lpCreateParams;
lpCS->lpCreateParams = NULL;
}
else {
if (WindowClass::StackWindowObject) {
WINX_ASSERT("WindowClass::StackWindowObject - unexpected!");
return FALSE;
}
else {
pWnd = WINX_NEW(WindowClass);
}
}
WindowMap::SetWindow(hWnd, pWnd);
}
return pWnd->ProcessMessage(hWnd, message, wParam, lParam);
}
};
這裡面有幾個細節需要解釋:
- WindowMap::GetWindow/SetWindow是什嗎?在介紹“SW系統的視窗類別”時,我們提到:
- MFC通過一個全域的HashMap建立視窗控制代碼(hWnd)到視窗對象(pWnd)的映射。
- SW系統通過視窗的UserData建立視窗控制代碼(hWnd)到視窗對象(pWnd)的映射。在WINX中,建立兩者映射的策略是任選的。除了以上兩者中外,還有第三種選擇:
- 使用SetProp/GetProp建立映射。並且這是WINX預設的選擇。
- 你自己實現的其他方式。
我們簡單分析一下,這些方式的利弊。
- 通過HashMap建立映射,問題在於這個HashMap對象如何在其他的DLL中取到?這導致bug或者強耦合的結構。
- 通過視窗的UserData建立映射,問題在於如果UserData已經被佔用怎麼辦?這導致機制上不安全的隱患。
- 使用SetProp/GetProp建立映射,效能比UserData方式慢,但極其安全。
- WindowClass::StackWindowObject是什嗎?我們知道,對象(當然包括C++視窗對象)有兩種建立方式:
- 建立在棧(Stack)上。即以局部自動變數方式建立。
- 建立在堆(Heap)上。即通過new/delete建立。
WINX允許你為視窗類別選擇其中一種。詳細我們在以後由專文敘述。
- 最後,視窗訊息被pWnd->ProcessMessage(hWnd, message, wParam, lParam)處理。ProcessMessage進行了最終的訊息指派。這一塊是WINX訊息機制的核心,前面我們我們已經仔仔細細作了講解:
- WINX的訊息指派機制
- WINX的訊息指派機制(續)
- WINX的訊息指派機制(續2)
- WINX的訊息指派機制(終結篇)