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

深入解析 MFC 映射機制:訊息映射與控制代碼映射

     編輯:VC 教學

前言

MFC 大量使用映射表實現訊息分派、命令路由、OLE 介面查詢等核心功能。理解其內部運作有助於撰寫高效能程式碼並進行深度除錯。本文分析兩大核心映射:條件查找映射 (訊息/命令/介面) 與 控制代碼映射 (HWND/HGDI 物件對應)。

一、條件查找映射

架構特點

  • 每個類別擁有映射表指標,指向基類映射表,形成鏈結串列。
  • 查找時從衍生類向基類遞迴搜尋,直到找到目標或抵達根類別。
  • 應用於:視窗訊息、命令、事件、OLE 命令分派、介面查詢 (QueryInterface) 等。

訊息映射查找流程 (以 CWnd::OnWndMsg 為例)

BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
    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)
    {
        const AFX_MSGMAP_ENTRY* lpEntry = pMsgCache->lpEntry;
        AfxUnlockGlobals(CRIT_WINMSGCACHE);
        if (lpEntry == NULL) return FALSE;
        // 依訊息類型跳轉分派
        if (message < 0xC000) goto LDispatch;
        else goto LDispatchRegistered;
    }
    else
    {
        // 快取未命中,遍歷映射鏈
        pMsgCache->nMsg = message;
        pMsgCache->pMessageMap = pMessageMap;

        #ifdef _AFXDLL
        for (; pMessageMap->pfnGetBaseMap != NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)())
        #else
        for (; pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap)
        #endif
        {
            // 防止循環參考
            #ifdef _AFXDLL
            ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
            #else
            ASSERT(pMessageMap != pMessageMap->pBaseMap);
            #endif

            if (message < 0xC000) // 標準視窗訊息
            {
                if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0)) != NULL)
                {
                    pMsgCache->lpEntry = lpEntry;
                    AfxUnlockGlobals(CRIT_WINMSGCACHE);
                    goto LDispatch;
                }
            }
            else // 已註冊訊息
            {
                lpEntry = pMessageMap->lpEntries;
                while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) != NULL)
                {
                    UINT* pnID = (UINT*)(lpEntry->nSig);
                    if (*pnID == message)
                    {
                        pMsgCache->lpEntry = lpEntry;
                        AfxUnlockGlobals(CRIT_WINMSGCACHE);
                        goto LDispatchRegistered;
                    }
                    lpEntry++;
                }
            }
        }
        pMsgCache->lpEntry = NULL;
        AfxUnlockGlobals(CRIT_WINMSGCACHE);
        return FALSE;
    }

LDispatch:
    // 實際分派處理...
}

效能優化重點

  1. 快取機制:最近查找結果儲存在全域快取陣列 _afxMsgCache,命中時直接取得處理函式入口,避免重複遍歷。
  2. 順序搜尋非雜湊:儘管名為 iHash,實際上仍是線性搜尋映射表項目。因此:
  • 將常用訊息處理項目置於映射表前端可提升速度。
  • 快取最近查找結果對熱點訊息效果顯著。

優缺點權衡

優點缺點
衍生類輕鬆擴充/覆寫映射,無需重寫查找邏輯理解難度較高,除錯需深入原始碼
避免龐大虛函數表開銷查找效能不如直接虛函數呼叫
支援動態介面查詢 (如 IDispatch)多執行緒環境下需注意鎖定開銷

二、控制代碼映射

目的

將 Windows 控制代碼 (HWND, HDC, HMENU 等) 一對一對應至 MFC 物件指標 (CWnd*, CDC*, CMenu*),確保同一執行緒內同一控制代碼對應唯一物件。

核心實現

  • 永久映射:CWnd::FromHandlePermanent 查找永久關聯物件。
  • 暫時映射:針對 GetDlgItem, SelectObject 等 API 回傳的暫時控制代碼,建立臨時物件 (CTempWnd 等),閒置時自動清理。
  • 儲存於執行緒區域儲存 (TLS),各執行緒獨立維護映射表。

範例:CWnd::GetParentFrame

CFrameWnd* CWnd::GetParentFrame() const
{
    if (GetSafeHwnd() == NULL) return NULL;
    ASSERT_VALID(this);

    CWnd* pParentWnd = GetParent(); // 呼叫 API GetParent
    while (pParentWnd != NULL)
    {
        if (pParentWnd->IsFrameWnd())
            return (CFrameWnd*)pParentWnd;
        pParentWnd = pParentWnd->GetParent();
    }
    return NULL;
}

_AFXWIN_INLINE CWnd* CWnd::GetParent() const
{
    ASSERT(::IsWindow(m_hWnd));
    return CWnd::FromHandle(::GetParent(m_hWnd)); // 映射查找
}

流程:API 取得父視窗控制代碼 → FromHandle 在執行緒映射表查找對應 CWnd* → 呼叫虛函數 IsFrameWnd 判斷類型。

效能影響

每個訊息到達時,AfxWndProc 皆需呼叫 CWnd::FromHandlePermanent 遍歷永久映射表:

LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
    if (nMsg == WM_QUERYAFXWNDPROC) return 1;
    CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
    ASSERT(pWnd != NULL && pWnd->m_hWnd == hWnd);
    if (!pWnd || pWnd->m_hWnd != hWnd) return ::DefWindowProc(hWnd, nMsg, wParam, lParam);
    return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}

此映射查找是 MFC 應用程式效能損耗來源之一。

多執行緒考量

因映射表儲於 TLS,同一 HWND 在不同執行緒可能對應不同物件指標。跨執行緒操作視窗物件需謹慎,參考微軟知識庫 Q147578。

結語

MFC 的雙重映射機制在開發便利性與執行效能間取得平衡。開發者應理解其運作原理,於效能敏感路徑考慮直接使用 API 或快取查找結果,並在多執行緒場景下妥善處理物件關聯。

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