1. 程式人生 > >深入淺出MFC筆記3-MFC程式的訊息流轉

深入淺出MFC筆記3-MFC程式的訊息流轉

對於上面的過程我們可能會對設定的訊息處理函式有疑問,如果建立的視窗的訊息處理函式是Windows預設的訊息處理,那麼MFC如何對各種訊息進行響應呢?對於這個問題我們回到CWnd::CreateEx函式中,我們可以看到在CFrameWnd::PreCreateWindow函式呼叫之後有會呼叫AfxHookWindowCreate這個函式,進入此函式可以看到呼叫SetWindowsHookEx,此函式在訊息處理鏈中塞入了函式_AfxCbtFilterHook,再來看看_AfxCbtFilterHook函式做了些什麼!
先貼出原始碼:

LRESULT CALLBACK
_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
{
    _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
    if (code != HCBT_CREATEWND)
    {
        // wait for HCBT_CREATEWND just pass others on...
        return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,
            wParam, lParam);
    }

    ASSERT(lParam != NULL);
    LPCREATESTRUCT lpcs = ((LPCBT_CREATEWND)lParam)->lpcs;
    ASSERT(lpcs != NULL);

    CWnd* pWndInit = pThreadState->m_pWndInit;
    BOOL bContextIsDLL = afxContextIsDLL;
    if (pWndInit != NULL || (!(lpcs->style & WS_CHILD) && !bContextIsDLL))
    {
        ...
        ASSERT(wParam != NULL); // should be non-NULL HWND
        HWND hWnd = (HWND)wParam;
        WNDPROC oldWndProc;
        if (pWndInit != NULL)
        {
            AFX_MANAGE_STATE(pWndInit->m_pModuleState);

            // the window should not be in the permanent map at this time
            ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);

            // connect the HWND to pWndInit...
            pWndInit->Attach(hWnd);
            // allow other subclassing to occur first
            pWndInit->PreSubclassWindow();

            WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();
            ASSERT(pOldWndProc != NULL);

            // subclass the window with standard AfxWndProc
            WNDPROC afxWndProc = AfxGetAfxWndProc();
            oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,
                (DWORD_PTR)afxWndProc);
            ASSERT(oldWndProc != NULL);
            if (oldWndProc != afxWndProc)
                *pOldWndProc = oldWndProc;

            pThreadState->m_pWndInit = NULL;
        }
        else
        {
            ASSERT(!bContextIsDLL);   // should never get here
            ...
        }
    }

lCallNextHook:
    LRESULT lResult = CallNextHookEx(pThreadState->m_hHookOldCbtF, code,
        wParam, lParam);
    ...
    return lResult;
}

在上面一段程式碼中我們看到此建構函式只攔截了視窗建立訊息的處理,其他訊息並沒有攔截,並在處理時呼叫

SetWindowLongPtr(hWnd, GWLP_WNDPROC,(DWORD_PTR)afxWndProc);

如果你學過Win32程式設計,一定知道這個呼叫就是來修改視窗的處理函式的,這個函式把主視窗的處理函式修改成了afxWndProc,這個是MFC內部定義函式,其有又呼叫了AfxCallWndProc,此函式呼叫CWnd::WindowProc,在CWnd::WindowProc中又呼叫CWnd::OnWndMsg,在CWnd::OnWndMsg中就可以看到OnCommand,OnNotify函式呼叫,以及在訊息對映表中查詢訊息的過程,精簡之後原始碼如下:
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
    LRESULT lResult = 0;
    union MessageMapFunctions mmf;
    mmf.pfn = 0;
    CInternalGlobalLock winMsgLock;
    // special case for commands
    if (message == WM_COMMAND)
    {
        if (OnCommand(wParam, lParam))
        {
            lResult = 1;
            goto LReturnTrue;
        }
        return FALSE;
    }

    // special case for notifies
    if (message == WM_NOTIFY)
    {
        NMHDR* pNMHDR = (NMHDR*)lParam;
        if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
            goto LReturnTrue;
        return FALSE;
    }
    ...

    const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();
    UINT iHash; iHash = (LOWORD((DWORD_PTR)pMessageMap) ^ message) & (iHashMax-1);
    winMsgLock.Lock(CRIT_WINMSGCACHE);
    AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash];
    const AFX_MSGMAP_ENTRY* lpEntry;
    if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap)
    {
        ...
    }
    else
    {
        // not in cache, look for it
        pMsgCache->nMsg = message;
        pMsgCache->pMessageMap = pMessageMap;

        for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;
            pMessageMap = (*pMessageMap->pfnGetBaseMap)())
        {
            // Note: catch not so common but fatal mistake!!
            //      BEGIN_MESSAGE_MAP(CMyWnd, CMyWnd)
            ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
            if (message < 0xC000)
            {
                // constant window message
                if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,
                    message, 0, 0)) != NULL)
                {
                    pMsgCache->lpEntry = lpEntry;
                    winMsgLock.Unlock();
                    goto LDispatch;
                }
            }
            else
            {
                // registered windows message
                lpEntry = pMessageMap->lpEntries;
                while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) != NULL)
                {
                    UINT* pnID = (UINT*)(lpEntry->nSig);
                    ASSERT(*pnID >= 0xC000 || *pnID == 0);
                        // must be successfully registered
                    if (*pnID == message)
                    {
                        pMsgCache->lpEntry = lpEntry;
                        winMsgLock.Unlock();
                        goto LDispatchRegistered;
                    }
                    lpEntry++;      // keep looking past this one
                }
            }
        }

        pMsgCache->lpEntry = NULL;
        winMsgLock.Unlock();
        return FALSE;
    }
LDispatchRegistered:    // for registered windows messages
    ASSERT(message >= 0xC000);
    ASSERT(sizeof(mmf) == sizeof(mmf.pfn));
    mmf.pfn = lpEntry->pfn;
    lResult = (this->*mmf.pfn_l_w_l)(wParam, lParam);

LReturnTrue:
    if (pResult != NULL)
        *pResult = lResult;
    return TRUE;
}


訊息分為命令訊息、控制元件通行以及視窗訊息,上面的函式中OnCommand處理命令訊息,OnNotify處理控制元件通知,視窗訊息的處理是查詢MessageMap對應條目然後呼叫處理函式,查詢MessageMap時向基類的MessageMap回溯,我們的例子程式中最先查詢的MyFrame的MessageMap,之後依次呼叫CFrameWnd,CWnd,CCmdTarget,CObject的
MessageMap,從上面的說明中可以得到視窗訊息的傳導路徑是直接從派生類傳遞到基類的。
下面來看看處理命令訊息的OnCommand函式和處理控制元件通知訊息的OnNotify函式。
呼叫OnCommand函式的視窗指標是MyFrame型別,所以實際要呼叫的是MyFrame::OnCommand,MyFrame中沒有重寫此函式,那麼呼叫的就是CFrameWnd::OnCommand,進入後接著呼叫CWnd::OnCommand,在此函式中又呼叫了CFrameWnd::OnCmdMsg,此函式程式碼如下:
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
    AFX_CMDHANDLERINFO* pHandlerInfo)
{
    CPushRoutingFrame push(this);

    // pump through current view FIRST
    CView* pView = GetActiveView();
    if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
        return TRUE;

    // then pump through frame
    if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
        return TRUE;

    // last but not least, pump through app
    CWinApp* pApp = AfxGetApp();
    if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
        return TRUE;

    return FALSE;
}


重上面的程式碼可以看出,命令訊息先由CView處理,再交由CFrameWnd處理,最後給CWinApp處理,而且被其中一個型別處理之後就不會再被後面的型別處理。這裡OnCmdMsg函式呼叫的都是CCmdTarget::OnCmdMsg函式,此函式原始碼如下:
BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,
    AFX_CMDHANDLERINFO* pHandlerInfo)
{
    // determine the message number and code (packed into nCode)
    const AFX_MSGMAP* pMessageMap;
    const AFX_MSGMAP_ENTRY* lpEntry;
    UINT nMsg = 0;

    if (nCode != CN_UPDATE_COMMAND_UI)
    {
        nMsg = HIWORD(nCode);
        nCode = LOWORD(nCode);
    }

    // for backward compatibility HIWORD(nCode)==0 is WM_COMMAND
    if (nMsg == 0)
        nMsg = WM_COMMAND;

    // look through message map to see if it applies to us

    for (pMessageMap = GetMessageMap(); pMessageMap->pfnGetBaseMap != NULL;
      pMessageMap = (*pMessageMap->pfnGetBaseMap)())
    {
        // Note: catches BEGIN_MESSAGE_MAP(CMyClass, CMyClass)!
        ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
        lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID);
        if (lpEntry != NULL)
        {
            return _AfxDispatchCmdMsg(this, nID, nCode,
                lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);
        }
    }
    return FALSE;   // not handled
}


此函式也是依次從派生類到基類查詢訊息處理表,呼叫處理函式,從上面的說明可以知道,一條命令訊息的處理路徑是先傳遞給CView的派生類,之後傳遞給CFrameWnd的派生類及基類,最後傳遞給CWinApp的派生類。
CWnd::OnNotify也是呼叫了CFrameWnd::OnCmdMsg,所以控制元件通知和命令訊息的處理路徑一樣。

下面借用《深入淺出MFC》中的一張圖來作個總結,MFC中的訊息處理路徑如圖中箭頭所示: