首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC++

MFC 視窗類別封裝核心技術:Thunk 機制深度解析

     編輯:VC++

問題背景

Win32 API 註冊視窗類別需提供 WNDPROC(全域函式指標),但 C++ 非靜態成員函式帶有 this 指標,無法直接轉型為 WNDPROC。MFC 及自製視窗庫需解決「全域視窗程序 → 物件成員函式」的映射問題。

三種解決方案比較

方案原理優點缺點
視窗列表查找陣列儲存 HWND ↔ this,全域程序遍歷比對簡單直觀O(N) 查找,視窗多時效能差
cbWndExtra 儲存 this註冊時設 cbWndExtra=sizeof(void*),SetWindowLong(GWL_USERDATA) 存取O(1) 存取若外部呼叫 SetWindowLong(GWL_USERDATA) 會覆蓋導致崩潰
Thunk 技術動態生成機器碼片段,攔截呼叫並替換堆疊參數O(1)、無副作用、完全封裝實作複雜,需組合語言知識

Thunk 原理詳解

系統呼叫視窗程序時堆疊佈局(右至左壓入):

堆疊頂 → 返回位址 | HWND | MSG | WPARAM | LPARAM ← 堆疊底

Thunk 代碼做三件事:

  1. call 跳至靜態 stdProc,同時將 stdProc 位址壓入堆疊。
  2. pop ecx 取出 stdProc 位址。
  3. mov [esp+4], this 將堆疊中 HWND 位置覆寫為物件 this 指標。
  4. jmp ecx 跳轉執行 stdProc。

核心實作代碼

Thunk 結構定義(1 字節對齊)

#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 [esp+4], this
    CMyWnd* Window;    // this 指標(被 mov 寫入堆疊)
    BYTE  Jmp;         // 0xFF  JMP [r/m32]
    BYTE  ECX;         // 0x21  [ecx]
};
#pragma pack(pop)

動態生成 Thunk

WNDPROC CMyWnd::CreateThunk()
{
    Thunk* thunk = new Thunk;

    // call Offset
    thunk->Call = 0xE8;
    thunk->Offset = (size_t)&(((Thunk*)0)->Code) - (size_t)&(((Thunk*)0)->Proc);
    thunk->Proc = &CMyWnd::stdProc;

    // pop ecx
    thunk->Code[0] = 0x59;

    // mov dword ptr [esp+4], this
    thunk->Code[1] = 0xC7;
    thunk->Code[2] = 0x44;
    thunk->Code[3] = 0x24;
    thunk->Code[4] = 0x04;
    thunk->Window = this;

    // jmp [ecx]
    thunk->Jmp = 0xFF;
    thunk->ECX = 0x21;

    m_thunk = (WNDPROC)thunk;
    return m_thunk;
}

靜態進入點 stdProc

LRESULT WINAPI CMyWnd::stdProc(HWND hWnd, UINT uMsg,
    WPARAM wParam, LPARAM lParam)
{
    CMyWnd* pThis = (CMyWnd*)hWnd; // hWnd 實為 this
    return pThis->WindowProc(uMsg, wParam, lParam);
}

初始化階段:InitProc 捕獲 WM_NCCREATE

LRESULT WINAPI CMyWnd::InitProc(HWND hWnd, UINT uMsg,
    WPARAM wParam, LPARAM lParam)
{
    if (uMsg == WM_NCCREATE)
    {
        CMyWnd* pThis = (CMyWnd*)((LPCREATESTRUCT)lParam)->lpCreateParams;
        if (pThis)
        {
            pThis->m_hWnd = hWnd;
            SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pThis->CreateThunk());
            return ((WNDPROC)pThis->GetThunk())(hWnd, uMsg, wParam, lParam);
        }
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

建立視窗時傳遞 this

CreateWindowEx(..., this); // lpParam = this

流程總結

  1. 物件建立 → Create() 呼叫 CreateWindowEx(..., InitProc, ..., this)。
  2. 系統送 WM_NCCREATE,InitProc 取出 this,記錄 m_hWnd,生成 Thunk 並 SetWindowLong(GWL_WNDPROC) 替換程序。
  3. 後續訊息直接進入 Thunk,Thunk 將堆疊 HWND 替換為 this 並跳入 stdProc。
  4. stdProc 轉呼叫成員函式 WindowProc。

結語

Thunk 技術是 MFC、ATL、WTL 等框架實現高效視窗物件封裝的核心。雖實作門檻較高,但提供零開銷、零副作用的完美映射,值得深入理解。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved