MFC 擁有龐大的類別繼承體系。若採用虛函數表,每個衍生類別都會產生巨大的 v-table,記憶體佔用高、呼叫效率低。Microsoft 設計了訊息映射機制:以靜態表格儲存「訊息 ID → 處理函數」對應,透過運行時查表分發,兼顧效率與彈性。
// AfxWinMain 簡化流程
if (!pThread->InitInstance()) { ... }
nReturnCode = pThread->Run(); // 進入訊息迴圈
CWinApp::Run() → CWinThread::Run()PeekMessage → OnIdle → PumpMessagePumpMessage 核心分發邏輯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 → OnWndMsgLRESULT 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;
線性掃描但極快;訊息映射表通常較小(數十項)。
GetMessage 從佇列取訊息PreTranslateMessage 鏈(視窗→父視窗→主視窗)攔截機會TranslateMessage / DispatchMessageWindowProc → OnWndMsgAFX_MSGMAP 鏈(本類別 → 基類 → ... → CWnd)AfxFindMessageEntry 匹配 nMessage/nCode/nIDpfn 對應處理函數DefWindowProc| 特性 | 實現方式 | 優勢 |
|---|---|---|
| 避免巨大虛函數表 | 靜態 AFX_MSGMAP_ENTRY 陣列 | 記憶體緊湊、無繼承開銷 |
| 支援繼承鏈查找 | pfnGetBaseMap 串聯基類映射表 | 衍生類別自動繼承基類處理函數 |
| 範圍命令 ID | nID~nLastID 區間匹配 | 單一處理函數服務連續命令 ID |
| 動態訊息 (RegisterWindowMessage) | nMessage >= 0xC000 走專用分支 | 執行期註冊訊息同樣支援 |
| 預轉譯攔截 | PreTranslateMessage 虛函數 + 視窗鏈遍歷 | 加速鍵、ToolTip 等優先處理 |
MFC 訊息映射是「靜態表格 + 運行時鏈式查找」的經典設計。理解其資料結構(AFX_MSGMAP/ENTRY)、查找演算法(AfxFindMessageEntry)、分發入口(PumpMessage→WindowProc→OnWndMsg),能幫助開發者在疑難雜症(訊息未響應、重複處理、繼承覆蓋)中快速定位根因。