純 SDK 寫視窗程式需重複:WNDCLASSEX 註冊、CreateWindow、訊息迴圈、龐大 switch(msg) 分派。MFC/OWL/WTL 等框架雖解決此問題,但理解其底層封裝原理有助於深入掌握 Windows 機制。本文示範從零用 C++ 面向對象封裝一個極簡視窗基類 CWindow,支援繼承、虛函式訊息處理、this 指針回調。
HINSTANCE, HWND, WNDCLASSEX, 類別名稱OnCreate, OnPaint, OnDestroy...)CWindow 定義
class CWindow
{
protected:
HWND m_hWnd;
HINSTANCE m_hInst;
TCHAR m_szClassName[64];
// 靜態全域訊息處理函式 (回調入口)
static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
// 虛函式:派生類覆寫處理特定訊息
virtual LRESULT OnCreate(LPCREATESTRUCT lpCS) { return 0; }
virtual void OnPaint(HDC hdc) {}
virtual void OnDestroy() { PostQuitMessage(0); }
// ... 其他訊息虛函式 ...
public:
CWindow(HINSTANCE hInst, LPCTSTR className);
BOOL Create(LPCTSTR title, DWORD style, int x, int y, int w, int h, HWND hParent, HMENU hMenu);
void Show(int nCmdShow);
void RunMessageLoop();
HWND GetHWND() const { return m_hWnd; }
};
this 指針與靜態回調綁定CreateWindowEx 傳遞 thisBOOL CWindow::Create(...)
{
// 註冊類別 (略)
m_hWnd = CreateWindowEx(0, m_szClassName, title, style, x, y, w, h, hParent, hMenu, m_hInst, this);
// ↑ lpCreateParams = this
return m_hWnd != NULL;
}
WM_NCCREATE 取出 this 並 SetWindowLongPtr(GWLP_USERDATA)LRESULT CALLBACK CWindow::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
CWindow* pThis = (CWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
if (msg == WM_NCCREATE)
{
LPCREATESTRUCT pCS = (LPCREATESTRUCT)lParam;
pThis = (CWindow*)pCS->lpCreateParams;
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pThis);
// 呼叫虛函式 OnCreate
return pThis->OnCreate(pCS);
}
if (pThis) return pThis->HandleMessage(msg, wParam, lParam);
return DefWindowProc(hWnd, msg, wParam, lParam);
}
HandleMessageLRESULT CWindow::HandleMessage(UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps; HDC hdc = BeginPaint(m_hWnd, &ps);
OnPaint(hdc); // 虛函式
EndPaint(m_hWnd, &ps);
return 0;
}
case WM_DESTROY: OnDestroy(); return 0;
// ... 其他訊息映射到虛函式 ...
}
return DefWindowProc(m_hWnd, msg, wParam, lParam);
}

class CMyWindow : public CWindow
{
public:
CMyWindow(HINSTANCE hInst) : CWindow(hInst, _T("MyWindowClass")) {}
protected:
virtual LRESULT OnCreate(LPCREATESTRUCT lpCS) override
{
// 載入選單
HMENU hMenu = LoadMenu(m_hInst, MAKEINTRESOURCE(IDR_MAINMENU));
SetMenu(m_hWnd, hMenu);
return 0;
}
virtual void OnPaint(HDC hdc) override
{
TextOut(hdc, 50, 50, _T("Hello, OOP SDK Window!"), 24);
// 繪製圖形...
}
virtual void OnCommand(WPARAM wParam, LPARAM lParam) override
{
switch (LOWORD(wParam))
{
case ID_FILE_EXIT: DestroyWindow(m_hWnd); break;
// ...
}
}
};

int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow)
{
CMyWindow win(hInst);
if (win.Create(_T("OOP SDK Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, NULL, NULL))
{
win.Show(nCmdShow);
win.RunMessageLoop();
}
return 0;
}
| 方案 | 優點 | 缺點 |
|---|---|---|
| 虛函式 (本文) | 語法簡潔、繼承自然、IDE 導航友善 | 每個類別維護 vtable,未使用訊息佔空間;難以動態增減訊息處理 |
| 訊息映射表 (MFC/WTL) | 僅存儲實際處理的訊息、支援動態鏈接、無 vtable 開銷 | 巨集語法複雜、需預處理器/模板支援 |
本文採虛函式純粹為教學演示;生產級框架建議採用訊息映射表。
CWinApp 封裝 WinMain、單例應用程式物件。CDialog、CFrameWnd 等進階視窗類別。CButton, CEdit...)。CDC, CBitmap, CFont) 實現 RAII。「SDK 物件化」不僅減少重複代碼,更能讓開發者透過親手構建框架,深刻理解 HWND/this 綁定、訊息分派、DC 管理等 Windows 核心機制。本文提供的 CWindow 雖簡陋,但包含了框架設計的靈魂:封裝平台差異、暴露領域介面、以繼承實現擴充。
本文配套原始碼可供下載。