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

VC++ 核心剖析:MFC 映射機制深度解讀——訊息映射、句柄映射與效能權衡

     編輯:VC 教學

映射無處不在:MFC 的核心資料結構

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 以雜湊索引加速熱門訊息查找。
  • 順序查找非雜湊:映射表內部為線性陣列,常用訊息應置於映射表前段 提升效能。
  • 避免虛函數表開銷:映射表取代虛函數實現多型分派,節省記憶體與間接呼叫成本。
  • DLL 與靜態連結差異:_AFXDLL 定義下透過函數指標取得基類映射,否則直接存取 pBaseMap。

句柄映射:物件與 HWND/GDI 物件的雙向綁定

MFC 將 HWND、HDC、HMENU 等封裝為 C++ 物件,透過執行緒區域儲存 (TLS) 維護對應表:

  • 永久映射:CWnd::FromHandlePermanent,CWnd::Attach/Detach 管理。
  • 暫時映射:API 回傳句柄時 (如 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 程式並避開多執行緒陷阱。

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