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

Windows 訊息機制與 MFC 訊息映射實作原理完整指南

     編輯:關於C語言

Windows 訊息:系統與應用程式的溝通語言

Windows 程式採用事件驅動模型。所有輸入(滑鼠、鍵盤、計時器、繪圖、系統事件)均封裝為訊息,投遞至目標視窗的視窗程序處理。MFC 在此基礎上建立訊息映射機制,以極高效率實現 C++ 多型般的動態分派。

訊息分類學

分類維度類別範例特徵
投遞路徑佇列訊息WM_MOUSEMOVE, WM_KEYDOWN, WM_PAINT, WM_TIMER, WM_QUIT經系統佇列 → 執行緒佇列 → GetMessage/DispatchMessage
非佇列訊息WM_ACTIVATE, WM_SETFOCUS, WM_WINDOWPOSCHANGED 等大多數直接呼叫視窗程序,繞過佇列
來源系統訊息 (0 ~ WM_USER-1, 0x8000~0xBFFF)WM_CREATE, WM_DESTROY, WM_SIZEWindows 保留,語意固定
應用程式訊息 (WM_USER~0x7FFF)WM_USER+100單一應用程式內部自定義
全域唯一訊息 (0xC000~0xFFFF)RegisterWindowMessage("MyMsg")跨進程唯一,需註冊

佇列訊息流程詳解

  1. 硬體中斷 → 驅動程式 → 系統訊息佇列 (唯一,系統級)。
  2. 系統判斷目標視窗及其建立執行緒 → 投入 執行緒訊息佇列 (每 GUI 執行緒一個)。
  3. 執行緒訊息迴圈:GetMessage → TranslateMessage → DispatchMessage。
  4. DispatchMessage 呼叫視窗程序 (WndProc) 處理。

關鍵點:WM_PAINT、WM_TIMER、WM_QUIT 是低優先級佇列訊息,佇列空時才處理(合併重繪、合併計時器)。

MFC 訊息映射:為什麼不直接用虛擬函數?

虛擬函數方案的缺陷

  • CWnd 需處理 100+ 訊息 → 虛擬函數表極大。
  • 每個衍生類繼承整張表,記憶體浪費嚴重。
  • 大多視窗只處理 5~10 個訊息,其餘虛擬函數永不被覆寫。

訊息映射設計

// 靜態映射表項目
struct AFX_MSGMAP_ENTRY
{
    UINT nMessage;        // 訊息 ID (WM_XXX)
    UINT nCode;           // 控制項通知代碼 (BN_CLICKED 等) 或 0
    UINT nID;             // 控制項 ID 或 0
    UINT nLastID;         // 範圍結束 ID
    AFX_PMSG pfn;         // 處理函數指標 (成員函數)
};

// 類別靜態映射表
struct AFX_MSGMAP
{
    const AFX_MSGMAP* pBaseMap;     // 基類映射表
    const AFX_MSGMAP_ENTRY* lpEntries; // 本類別項目陣列
};

宏展開範例

BEGIN_MESSAGE_MAP(CMyWnd, CWnd)
    ON_WM_PAINT()
    ON_COMMAND(ID_BUTTON_OK, OnBtnOk)
    ON_BN_CLICKED(IDC_CHECK, OnCheckClicked)
END_MESSAGE_MAP()

// 展開後產生靜態陣列:
const AFX_MSGMAP_ENTRY CMyWnd::_messageEntries[] = {
    { WM_PAINT, 0, 0, 0, (AFX_PMSG)&CMyWnd::OnPaint },
    { WM_COMMAND, 0, ID_BUTTON_OK, 0, (AFX_PMSG)&CMyWnd::OnBtnOk },
    { WM_COMMAND, BN_CLICKED, IDC_CHECK, 0, (AFX_PMSG)&CMyWnd::OnCheckClicked },
    { 0, 0, 0, 0, 0 } // 結束標記
};
const AFX_MSGMAP CMyWnd::messageMap = { &CWnd::messageMap, _messageEntries };

訊息分派流程:從 WndProc 到 OnXxx

  1. AfxWndProc (MFC 全域視窗程序) 接收訊息。
  2. 透過 CHandleMap 找到 CWnd* (或建立臨時 CTempWnd)。
  3. 呼叫 CWnd::WindowProc → OnWndMsg。
  4. OnWndMsg 遍歷 messageMap 及基類鏈表,比對 nMessage/nCode/nID。
  5. 找到匹配項 → 轉型呼叫對應成員函數 (AFX_PMSG 強制轉型)。
  6. 未找到 → DefWindowProc 預設處理。

效率:O(映射項目數),通常 < 20 次比較,極快。無虛擬函數表開銷。

命令訊息路由:超越視窗邊界

命令 (WM_COMMAND, WM_SYSCOMMAND) 採用路由機制,依序嘗試:

  1. 當前作用中視圖 (CView)
  2. 文檔 (CDocument)
  3. 框架視窗 (CFrameWnd)
  4. 應用程式物件 (CWinApp)
  5. 文檔範本 (CDocTemplate)

實作於 CCmdTarget::OnCmdMsg。這使「另存新檔」等命令可在視圖、文檔、應用程式任一層級處理。

進階主題

主題關鍵點
訊息映射範圍宏ON_COMMAND_RANGE(id1, id2, fn)、ON_NOTIFY_RANGE 處理連續 ID。
動態訊息映射罕用。可執行期修改映射表 (需鎖定),支援外掛架構。
反射訊息ON_WM_CTLCOLOR_REFLECT() 等,控制項自行處理通知,父視窗不介入。
訊息攔截覆寫 PreTranslateMessage (對話框/視圖) 或 HookWindowCreate。
使用者訊息#define WM_MYMSG (WM_USER+100),配合 ON_MESSAGE(WM_MYMSG, OnMyMsg)。
註冊訊息UINT WM_FIND = RegisterWindowMessage(_T("MyFindMsg"));,跨進程通訊。

除錯技巧

  • Spy++ 監控訊息流向。
  • TRACE 宏在 OnWndMsg 印出未處理訊息。
  • 中斷點設在 AfxWndProc 或 CWnd::WindowProc 觀察分派。
  • 檢查 messageMap 靜態陣列是否正確生成 (ClassWizard 同步問題)。

總結

MFC 訊息映射以靜態表格 + 基類鏈表遍歷取代虛擬函數,在記憶體效率、執行速度、維護便利性三方面達到平衡。理解其宏展開本質、分派流程、命令路由、反射機制,能讓您精準控制訊息流向,解決「訊息不觸發、重複處理、跨模組通訊」等疑難雜症,並在需要時突破框架限制實現自定義分派邏輯。

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