深入浅出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中的消息处理路径如图中箭头所示:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值