MFC原理第六讲.消息传递

---恢复内容开始---

                  MFC原理第六讲.消息传递

一丶简介  

    通过上一讲我们的消息映射表.我们得知. 消息映射表 会保存父类的MessageMap 以及自己当前的消息结构体数组.

消息传递是一层一层的递进的.那么我们现在要看一下怎么递进的.

要学习的知识

    1.窗口创建的流程.以及默认的回调函数

    2.消息处理流程

 

二丶窗口创建的流程.以及默认的回调函数

  我们要看窗口创建.那么就需要跟进 MFC源码去看. 首先就是对我们的Create函数下断点.看一下做了什么事情.

进入Create函数内部.

BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
    LPCTSTR lpszWindowName,
    DWORD dwStyle,
    const RECT& rect,
    CWnd* pParentWnd,
    LPCTSTR lpszMenuName,
    DWORD dwExStyle,
    CCreateContext* pContext)
{
    HMENU hMenu = NULL;
    if (lpszMenuName != NULL)    //首先判断我们有彩蛋吗.如果有加载我们的菜单.
    {
        // load in a menu that will get destroyed when window gets destroyed
        HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, ATL_RT_MENU);
        if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
        {
            TRACE(traceAppMsg, 0, "Warning: failed to load menu for CFrameWnd.\n");
            PostNcDestroy();            // perhaps delete the C++ object
            return FALSE;
        }
    }

    m_strTitle = lpszWindowName;    // save title for later

    if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
        rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,     //内部还是调用的CreateEx函数.所以我们继续跟进去查看.
        pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
    {
        TRACE(traceAppMsg, 0, "Warning: failed to create CFrameWnd.\n");
        if (hMenu != NULL)
            DestroyMenu(hMenu);
        return FALSE;
    }

    return TRUE;
}

CreateEx查看. 窗口过程处理函数.

BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
    LPCTSTR lpszWindowName, DWORD dwStyle,
    int x, int y, int nWidth, int nHeight,
    HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
    ASSERT(lpszClassName == NULL || AfxIsValidString(lpszClassName) || 
        AfxIsValidAtom(lpszClassName));
    ENSURE_ARG(lpszWindowName == NULL || AfxIsValidString(lpszWindowName));
    
    // allow modification of several common create parameters
    CREATESTRUCT cs;                                //熟悉的窗口类创建但是是一个新的结构.
    cs.dwExStyle = dwExStyle;
    cs.lpszClass = lpszClassName;
    cs.lpszName = lpszWindowName;
    cs.style = dwStyle;
    cs.x = x;
    cs.y = y;
    cs.cx = nWidth;                               //其中高版本在这里就会设置默认的窗口回调.
    cs.cy = nHeight;
    cs.hwndParent = hWndParent;
    cs.hMenu = nIDorHMenu;
    cs.hInstance = AfxGetInstanceHandle();
    cs.lpCreateParams = lpParam;

    if (!PreCreateWindow(cs))                      //内部进行风格设置以及注册窗口类.
    {
        PostNcDestroy();
        return FALSE;
    }

    AfxHookWindowCreate(this);                                //Hook 窗口回调函数.设置窗口回调函数. Create消息来到的时候
    HWND hWnd = CreateWindowEx(cs.dwExStyle, cs.lpszClass,
            cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
            cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);

#ifdef _DEBUG
    if (hWnd == NULL)
    {
        TRACE(traceAppMsg, 0, "Warning: Window creation failed: GetLastError returns 0x%8.8X\n",
            GetLastError());
    }
#endif

    if (!AfxUnhookWindowCreate())
        PostNcDestroy();        // cleanup if CreateWindowEx fails too soon

    if (hWnd == NULL)
        return FALSE;
    ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
    return TRUE;
}

新的结构

typedef struct tagCREATESTRUCTA {
    LPVOID      lpCreateParams;
    HINSTANCE   hInstance;
    HMENU       hMenu;
    HWND        hwndParent;
    int         cy;
    int         cx;
    int         y;
    int         x;
    LONG        style;
    LPCSTR      lpszName;
    LPCSTR      lpszClass;
    DWORD       dwExStyle;
} CREATESTRUCTA, *LPCREATESTRUCTA;

新的类跟注册窗口的时候很相似. 我们看一下窗口回调在哪里设置的吧.

窗口回调函数 是通过

AfxHookWindowCreate 函数来进行设置.而这个函数本身就是一个Windows自带的HOOK. 其真正的窗口回调函数.是在内部中.设置回调的时候 新的回调函数进行设置的.
void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
{
    _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
    if (pThreadState->m_pWndInit == pWnd)
        return;

    if (pThreadState->m_hHookOldCbtFilter == NULL)
    {
        pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,           //设置CBT HOOK . _AfxCbtFilterHook里面才是真正的替换窗口过程处理函数.
            _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
        if (pThreadState->m_hHookOldCbtFilter == NULL)
            AfxThrowMemoryException();
    }
    ASSERT(pThreadState->m_hHookOldCbtFilter != NULL);
    ASSERT(pWnd != NULL);
    ASSERT(pWnd->m_hWnd == NULL);   // only do once

    ASSERT(pThreadState->m_pWndInit == NULL);   // hook not already in progress
    pThreadState->m_pWndInit = pWnd;
}

看一下函数内部

 LRESULT CALLBACK
_AfxCbtFilterHook( int  code, WPARAM wParam, LPARAM lParam)   {
     //
     WNDPROC afxWndProc  =  AfxGetAfxWndProc();
    oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,(DWORD_PTR)afxWndProc);  //重要的位置就是这里.使用的SetWindowLong这个函数.将窗口过程函数替换为了 afxWndProc
     //
 } 
 WNDPROC AFXAPI AfxGetAfxWndProc()   {
     //
     return   & AfxWndProc;
}

总结: 通过上面代码我们得知了.窗口在创建的时候以及窗口回调进行的一些列设置

  1.调用Create创建窗口

  2.设置窗口类.

  3.注册窗口类.

  4.通过AfxHookWindowsCreate 将我们的默认窗口回调改成了 afxWndProc

  5.窗口创建完毕.

上面五条则是我们创建窗口的时候进行的一系列操作. 所以我们的消息处理函数变成了 afxWndProc了这个消息处理函数就会在发生消息的时候第一个来到.

 

三丶消息处理流程

  通过上面我们得知了窗口处理回调已经更改了. 现在我们直接对我们的消息下段点.就可以验证一下.是否是我们的函数首次来到.

对我们的按钮点击下段点. 通过栈回朔一层一层往上看.

第一层

 第一层级就是判断我们的消息.进行不同的处理. 所以不重要.跳过.



第二层消息处理层
这一层就是我们要进行的消息处理的一层.如果消息不处理则默认交给默认的处理函数进行处理
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    // OnWndMsg does most of the work, except for DefWindowProc call
    LRESULT lResult = 0;
    if (!OnWndMsg(message, wParam, lParam, &lResult))
        lResult = DefWindowProc(message, wParam, lParam);
    return lResult;
}

第n层.因为不重要了.所以我们栈回朔到最顶层即可.

AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
    // special message which identifies the window as using AfxWndProc
    if (nMsg == WM_QUERYAFXWNDPROC)
        return 1;

    // all other messages route through message map
    CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
    ASSERT(pWnd != NULL);                    
    ASSERT(pWnd==NULL || pWnd->m_hWnd == hWnd);
    if (pWnd == NULL || pWnd->m_hWnd != hWnd)
        return ::DefWindowProc(hWnd, nMsg, wParam, lParam);
    return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}

 我们如果自己去看.可以看到.WindProc函数是被外部调用的. 而且这个函数是一个虚函数.也就是说如果我们重写了消息处理函数.那么我们自己就可以处理消息了.

如果自己不处理.那么默认就调用 CWnd里面的消息处理函数了

而里面的 OnMsg函数同样也是一个虚函数. 如果不该写一样调用父类的

调试可以看一下.

 

 

 只是部分代码截图.如果有兴趣可以深究. 我们知道. Windows 消息分为三大类. 

1.普通消息.  

2.菜单消息. WM_COMMAND

3.WM_NOTIFY  

而我们的鼠标点击消息就是普通消息.  如果来菜单消息了就统一为WM_COMMAND消息. 代表的是通知类消息.

而我们的这个方法就是判断消息是什么类型的. 进行不同消息的处理. 

如果说来的消息都不包括的话.那么下面就开始遍历消息映射表.然后进行消息查找.

完整代码

代码太多删减一下.
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;
    }

    if (message == WM_CREATE && m_pDynamicLayout != NULL)
    {
        ASSERT_VALID(m_pDynamicLayout);

        if (!m_pDynamicLayout->Create(this))
        {
            delete m_pDynamicLayout;
            m_pDynamicLayout = NULL;
        }
        else
        {
            InitDynamicLayout();
        }
    }

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

    // special case for activation
    if (message == WM_ACTIVATE)
        _AfxHandleActivate(this, wParam, CWnd::FromHandle((HWND)lParam));

    // special case for set cursor HTERROR
    if (message == WM_SETCURSOR &&
        _AfxHandleSetCursor(this, (short)LOWORD(lParam), HIWORD(lParam)))
    {
        lResult = 1;
        goto LReturnTrue;
    }

   // special case for windows that contain windowless ActiveX controls
   .......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)
    {
        // cache hit
        lpEntry = pMsgCache->lpEntry;
        winMsgLock.Unlock();
        if (lpEntry == NULL)
            return FALSE;

        // cache hit, and it needs to be handled
        if (message < 0xC000)
            goto LDispatch;
        else
            goto LDispatchRegistered;
    }
    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;
}

LDispatch:                              因为自己的当前MessageMap表i中保存着消息结构体数组. 所以遍历可以得出 消息.以及对应的函数指针
ASSERT(message < 0xC000);

mmf.pfn = lpEntry->pfn;                                     然后其结果保存在 mmf.pfn中.  个mmf是一个结构.联合体结构. 具体下方可以看一下这个结构.其实结构其实就是保存了函数返回值以及类型信息

switch (lpEntry->nSig)                                      我们消息结构体中前边也讲过.有一个sig标识.代表了函数的返回值以及参数类型. 进而通过不同的函数.调用不同的消息处理函数
{
default:
ASSERT(FALSE);
break;
case AfxSig_l_p:

 

结构.只显示部分

union MessageMapFunctions
{
AFX_PMSG pfn; // generic member function pointer

BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_D)(CDC*);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_b)(BOOL);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_u)(UINT);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_h)(HANDLE);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_W_u_u)(CWnd*, UINT, UINT);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_W_COPYDATASTRUCT)(CWnd*, COPYDATASTRUCT*);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_HELPINFO)(LPHELPINFO);
HBRUSH (AFX_MSG_CALL CCmdTarget::*pfn_B_D_W_u)(CDC*, CWnd*, UINT);
HBRUSH (AFX_MSG_CALL CCmdTarget::*pfn_B_D_u)(CDC*, UINT);
int (AFX_MSG_CALL CCmdTarget::*pfn_i_u_W_u)(UINT, CWnd*, UINT);

}

 如果是 WM_COMMAND 或者 WM_NOTIFY 消息.则取对应的 OnCommand中. 这个函数跟上面类似.也是遍历消息映射表去寻找.有兴趣的可以自己看下源码.

posted @ 2018-09-13 16:51  iBinary  阅读(1255)  评论(0编辑  收藏  举报