Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

MFC 訊息映射機制深度剖析:從原始碼看訊息路由全流程

編輯:Linux 教學

為何需要訊息映射?

MFC 擁有龐大的類別繼承體系。若採用虛函數表,每個衍生類別都會產生巨大的 v-table,記憶體佔用高、呼叫效率低。Microsoft 設計了訊息映射機制:以靜態表格儲存「訊息 ID → 處理函數」對應,透過運行時查表分發,兼顧效率與彈性。

訊息迴圈啟動流程

// AfxWinMain 簡化流程
if (!pThread->InitInstance()) { ... }
nReturnCode = pThread->Run();  // 進入訊息迴圈
  • CWinApp::Run() → CWinThread::Run()
  • 核心迴圈:PeekMessage → OnIdle → PumpMessage

PumpMessage 核心分發邏輯

BOOL CWinThread::PumpMessage() {
    return AfxInternalPumpMessage();
}

BOOL AFXAPI AfxInternalPumpMessage() {
    MSG& msg = pState->m_msgCur;
    if (!::GetMessage(&msg, NULL, NULL, NULL)) return FALSE; // WM_QUIT

    // 關鍵預處理階段
    if (msg.message != WM_KICKIDLE && !AfxPreTranslateMessage(&msg)) {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);  // 進入 WindowProc
    }
    return TRUE;
}

預轉譯階段

BOOL AfxPreTranslateMessage(MSG* pMsg) {
    CWinThread* pThread = AfxGetThread();
    if (pThread) return pThread->PreTranslateMessage(pMsg);
    return AfxInternalPreTranslateMessage(pMsg);
}

// CWinThread::PreTranslateMessage 內部:
// 1. 執行緒訊息分發
// 2. CWnd::WalkPreTranslateTree 從目標視窗向上遍歷父視窗
//    若任一視窗的 PreTranslateMessage 返回 TRUE,訊息被攔截
// 3. 無模對話框加速鍵最後機會

關鍵點: PreTranslateMessage 是虛函數,衍生類別可覆寫攔截特定訊息(如快捷鍵)。返回 TRUE 表示已處理,不再進入標準分發。

標準分發:WindowProc → OnWndMsg

LRESULT CWnd::WindowProc(UINT msg, WPARAM wp, LPARAM lp) {
    LRESULT lr = 0;
    if (!OnWndMsg(msg, wp, lp, &lr))
        lr = DefWindowProc(msg, wp, lp);  // 系統預設處理
    return lr;
}

OnWndMsg 核心查表邏輯

BOOL CWnd::OnWndMsg(UINT msg, WPARAM wp, LPARAM lp, LRESULT* plr) {
    const AFX_MSGMAP* pMap = GetMessageMap();
    for (; pMap; pMap = (*pMap->pfnGetBaseMap)()) {
        // 遍歷當前類別及所有基類的訊息映射表
        const AFX_MSGMAP_ENTRY* pEntry = AfxFindMessageEntry(
            pMap->lpEntries, msg, 0, 0);
        if (pEntry) {
            // 找到對應項,呼叫處理函數
            return TRUE;
        }
    }
    return FALSE;  // 未找到,交給 DefWindowProc
}

訊息映射表資料結構

宣告巨集

#define DECLARE_MESSAGE_MAP() \
protected: \
    static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
    virtual const AFX_MSGMAP* GetMessageMap() const;

實作巨集

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
    const AFX_MSGMAP* theClass::GetMessageMap() const \
    { return GetThisMessageMap(); } \
    const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
    { \
        typedef theClass ThisClass; \
        typedef baseClass TheBaseClass; \
        static const AFX_MSGMAP_ENTRY _messageEntries[] = \
        {

#define END_MESSAGE_MAP() \
        {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0} \
        }; \
        static const AFX_MSGMAP messageMap = \
        { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
        return &messageMap; \
    }

核心結構體

struct AFX_MSGMAP {
    const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();  // 指向基類映射表獲取函數
    const AFX_MSGMAP_ENTRY* lpEntries;            // 本類別映射項陣列
};

struct AFX_MSGMAP_ENTRY {
    UINT nMessage;   // 訊息 ID (WM_xxx)
    UINT nCode;      // 通知碼 (HIWORD(wParam))
    UINT nID;        // 控制項 ID (LOWORD(wParam))
    UINT nLastID;    // ID 範圍結束
    UINT_PTR nSig;   // 簽名類型 (參數/返回值編碼)
    AFX_PMSG pfn;    // 處理函數指標
};

AfxFindMessageEntry 高速查找演算法

原始碼採用針對 x86 優化的組合語言迴圈,邏輯等同:

while (entry->nSig != AfxSig_end) {
    if (entry->nMessage == msg &&
        entry->nCode == code &&
        id >= entry->nID && id <= entry->nLastID)
        return entry;
    ++entry;
}
return NULL;

線性掃描但極快;訊息映射表通常較小(數十項)。

訊息處理完整流程圖

  1. GetMessage 從佇列取訊息
  2. PreTranslateMessage 鏈(視窗→父視窗→主視窗)攔截機會
  3. 未攔截 → TranslateMessage / DispatchMessage
  4. WindowProc → OnWndMsg
  5. 遍歷 AFX_MSGMAP 鏈(本類別 → 基類 → ... → CWnd)
  6. AfxFindMessageEntry 匹配 nMessage/nCode/nID
  7. 匹配成功 → 呼叫 pfn 對應處理函數
  8. 匹配失敗 → DefWindowProc

關鍵設計亮點總結

特性實現方式優勢
避免巨大虛函數表靜態 AFX_MSGMAP_ENTRY 陣列記憶體緊湊、無繼承開銷
支援繼承鏈查找pfnGetBaseMap 串聯基類映射表衍生類別自動繼承基類處理函數
範圍命令 IDnID~nLastID 區間匹配單一處理函數服務連續命令 ID
動態訊息 (RegisterWindowMessage)nMessage >= 0xC000 走專用分支執行期註冊訊息同樣支援
預轉譯攔截PreTranslateMessage 虛函數 + 視窗鏈遍歷加速鍵、ToolTip 等優先處理

結語

MFC 訊息映射是「靜態表格 + 運行時鏈式查找」的經典設計。理解其資料結構(AFX_MSGMAP/ENTRY)、查找演算法(AfxFindMessageEntry)、分發入口(PumpMessage→WindowProc→OnWndMsg),能幫助開發者在疑難雜症(訊息未響應、重複處理、繼承覆蓋)中快速定位根因。

Copyright © Windows教程網 All Rights Reserved