zoukankan      html  css  js  c++  java
  • 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.
    ");
                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.
    ");
            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
    ",
                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中. 这个函数跟上面类似.也是遍历消息映射表去寻找.有兴趣的可以自己看下源码.

  • 相关阅读:
    抽象函数
    函数的奇偶性习题
    高斯函数的那些事
    分段函数
    二次函数习题
    图是学习高中数学的生命线
    恒成立能成立恰成立习题
    http和https的作用与区别
    vue使用v-if v-show页面闪烁,div闪现的解决方法
    理解prototype、proto和constructor的三角关系
  • 原文地址:https://www.cnblogs.com/iBinary/p/9641589.html
Copyright © 2011-2022 走看看