MFC(Microsoft Foundation Classes)功能強大且成熟,但在學習 Windows 核心程式設計時,親手實現一個輕量級的視窗類別庫,能讓我們深入理解「視窗類封裝」的本質——如何將全域視窗訊息處理映射到具體的 C++ 物件實例。這正是本文要探討的核心議題。
在 Win32 API 中,註冊視窗類別(WNDCLASS)需指定 lpfnWndProc,類型為 WNDPROC(全域函數指標)。C++ 非靜態成員函數擁有隱含的 this 指標,無法直接轉型為 WNDPROC;而靜態成員函數或全域函數又無法直接存取物件的成員資料。這是一個經典的「靜態回呼與物件實例綁定」問題。
維護一個全域陣列,儲存 HWND 與 this 的對應關係。全域視窗程序遍歷陣列,依 HWND 找到物件指標,再轉呼叫成員函數。缺點顯而易見:視窗數量增長時查找時間線性增長,效率低落。
cbWndExtra 儲存 this 指標WNDCLASS 的 cbWndExtra 成員可要求系統在建立視窗時額外配置記憶體,將 this 存入。靜態視窗程序透過 GetWindowLong(hWnd, GWL_USERDATA) 取回指標。但這佔用了 GWL_USERDATA,若外部程式碼呼叫 SetWindowLong(hWnd, GWL_USERDATA, ...) 會導致程式崩潰,安全性不足。
Thunk 是一小段動態生成的機器碼,內嵌物件的 this 指標,且符合 WNDPROC 的呼叫慣例。系統以視窗程序身分呼叫這段程式碼,Thunk 將堆疊上的 HWND 參數替換為 this,再跳轉到靜態處理函數,實現零成本、高安全性的物件綁定。
定義 1-byte 對齊的結構,對應 x86 指令序列:
#pragma pack(push, 1)
struct Thunk {
BYTE Call; // 0xE8: CALL rel32
int Offset; // 跳過 Proc 欄位到 Code[0] 的偏移量
WNDPROC Proc; // 目標靜態視窗程序 (stdProc)
BYTE Code[5]; // POP ECX; MOV DWORD PTR [ESP+4], this
CMyWnd* Window; // 物件 this 指標
BYTE Jmp; // 0xFF: JMP [r/m32]
BYTE ECX; // 0x21: [ECX]
};
#pragma pack(pop)
指令流程說明:
CALL Offset:呼叫下一條指令(Code[0]),同時將返回位址(即 Proc 欄位位址)壓入堆疊。POP ECX:彈出堆疊頂端的 Proc 位址到 ECX。MOV DWORD PTR [ESP+4], this:此時堆疊頂為返回位址,ESP+4 即為第一個參數 HWND,直接覆寫為物件指標。JMP [ECX]:跳轉到 stdProc 繼續執行。stdProc 與初始化流程LRESULT WINAPI CMyWnd::stdProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
CMyWnd* pThis = reinterpret_cast(hWnd); // hWnd 已被 Thunk 替換為 this
return pThis->WindowProc(uMsg, wParam, lParam);
}
視窗建立初期(WM_NCCREATE)尚未完成 Thunk 綁定,需先用 InitProc 攔截:
LRESULT WINAPI CMyWnd::InitProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (uMsg == WM_NCCREATE) {
CREATESTRUCT* pcs = reinterpret_cast(lParam);
CMyWnd* pThis = static_cast(pcs->lpCreateParams);
if (pThis) {
pThis->m_hWnd = hWnd; // 記錄視窗控制代碼
SetWindowLongPtr(hWnd, GWLP_WNDPROC, // 替換為 Thunk
reinterpret_cast(pThis->CreateThunk()));
return pThis->GetThunk()(hWnd, uMsg, wParam, lParam); // 立即處理當前訊息
}
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

Thunk 技術展現了系統程式設計中「以空間換時間、以程式碼生成替代查找」的經典智慧。雖然現代開發鮮少手寫 Thunk,但理解其原理有助於掌握呼叫慣例、堆疊框架、動態程式碼生成等核心概念,也是 MFC、ATL、WTL 等框架內部實現視窗物件映射的基石。