MFC 大量使用 BEGIN_XXX_MAP 巨集建立映射表:訊息映射、OLE 命令映射、介面映射等。每個映射表包含基類映射指標,形成鏈結串列。查找時從衍生類向基類遞迴,直到找到或抵達根類別。
CWnd::OnWndMsg 源碼解析
const AFX_MSGMAP* pMessageMap = GetMessageMap();
UINT iHash = (LOWORD((DWORD_PTR)pMessageMap) ^ message) & (iHashMax-1);
AfxLockGlobals(CRIT_WINMSGCACHE);
AFX_MSG_CACHE* pMsgCache = &_afxMsgCache[iHash];
// 快取命中檢查
if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap) {
lpEntry = pMsgCache->lpEntry;
AfxUnlockGlobals(CRIT_WINMSGCACHE);
if (!lpEntry) return FALSE;
goto Dispatch;
}
// 快取未命中:遍歷映射鏈
pMsgCache->nMsg = message;
pMsgCache->pMessageMap = pMessageMap;
#ifdef _AFXDLL
for (; pMessageMap->pfnGetBaseMap; pMessageMap = (*pMessageMap->pfnGetBaseMap)())
#else
for (; pMessageMap; pMessageMap = pMessageMap->pBaseMap)
#endif
{
// 檢查循環引用
ASSERT(pMessageMap != ...);
if (message < 0xC000) { // 標準訊息
if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0))) {
pMsgCache->lpEntry = lpEntry;
AfxUnlockGlobals(CRIT_WINMSGCACHE);
goto Dispatch;
}
} else { // 已註冊訊息
for (lpEntry = pMessageMap->lpEntries;
(lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0));
lpEntry++) {
UINT* pnID = (UINT*)(lpEntry->nSig);
if (*pnID == message) {
pMsgCache->lpEntry = lpEntry;
AfxUnlockGlobals(CRIT_WINMSGCACHE);
goto DispatchRegistered;
}
}
}
}
pMsgCache->lpEntry = NULL;
AfxUnlockGlobals(CRIT_WINMSGCACHE);
return FALSE;
_afxMsgCache 以雜湊索引加速熱門訊息查找。_AFXDLL 定義下透過函數指標取得基類映射,否則直接存取 pBaseMap。MFC 將 HWND、HDC、HMENU 等封裝為 C++ 物件,透過執行緒區域儲存 (TLS) 維護對應表:
CWnd::FromHandlePermanent,CWnd::Attach/Detach 管理。GetDlgItem, SelectObject) 自動建立臨時物件,閒置時回收。CWnd::GetParentFrame
CFrameWnd* CWnd::GetParentFrame() const {
if (!GetSafeHwnd()) return NULL;
CWnd* pParent = GetParent(); // API GetParent + 句柄映射查物件
while (pParent) {
if (pParent->IsFrameWnd()) return (CFrameWnd*)pParent;
pParent = pParent->GetParent();
}
return NULL;
}
AfxWndProc
LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) {
if (nMsg == WM_QUERYAFXWNDPROC) return 1;
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd); // 每則訊息必查永久映射
ASSERT(pWnd && pWnd->m_hWnd == hWnd);
return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}
效能代價:每則訊息皆需查 TLS 映射表,是 MFC 效能損耗來源之一。
句柄映射儲存於 TLS,同一 HWND 在不同執行緒可能映射到不同物件。跨執行緒操作 MFC 視窗物件需極度謹慎,參考 KB Q147578。
MFC 映射機制以「查找表 + 快取 + 繼承鏈」實現靈活的訊息分派與物件封裝,犧牲部分效能換取開發效率與擴充性。理解其運作原理,有助於寫出高效能 MFC 程式並避開多執行緒陷阱。