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, , "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, , "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, , "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 = ;
- 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 ;
- // 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 = ;
- union MessageMapFunctions mmf;
- mmf.pfn = ;
- CInternalGlobalLock winMsgLock;
- // special case for commands
- if (message == WM_COMMAND)
- {
- if (OnCommand(wParam, lParam))
- {
- lResult = ;
- 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 = ;
- 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-);
- 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, , )) != NULL)
- {
- pMsgCache->lpEntry = lpEntry;
- winMsgLock.Unlock();
- goto LDispatch;
- }
- }
- else
- {
- // registered windows message
- lpEntry = pMessageMap->lpEntries;
- while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, , )) != NULL)
- {
- UINT* pnID = (UINT*)(lpEntry->nSig);
- ASSERT(*pnID >= 0xC000 || *pnID == );
- // 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中. 这个函数跟上面类似.也是遍历消息映射表去寻找.有兴趣的可以自己看下源码.
MFC原理第六讲.消息传递的更多相关文章
- MFC原理第四讲.动态创建机制
MFC原理第四讲.动态创建机制 一丶要学习的知识点以及简介 动态创建是什么意思? 动态创建其实就是跟C++的new一样.都是创建对象.但是规避了C++语法的缺陷. 例如: char * ClassNa ...
- MFC原理第二讲.MFC的初始化过程
MFC原理第二讲MFC的初始化过程 一丶简介 通过上一讲.我们手工模拟了一个MFC程序. 但是上一讲留下了疑问. 就是WinMain在哪.以及消息处理在哪里. 这一节主要就是讲解Winmain在哪里. ...
- MFC原理第一讲.MFC的本质.以及手工编写MFC的程序
MFC原理第一讲.MFC的本质.以及手工编写MFC的程序 PS: 这个博客属于复习知识.从头开始讲解. 在写这篇博客之前.已经写了3篇MFC的本质了.不过掌握知识点太多.所以从简重新开始. 一丶MFC ...
- MFC原理第五讲.消息映射.以及如何添加消息
MFC原理第五讲.消息映射.以及如何添加消息 一丶消息映射是什么 我们知道.Win32程序.都是通过消息去驱动的. 不断的在处理消息. 只要我们使用固定的宏.就可以让我们的框架知道一旦消息发生.该往哪 ...
- MFC原理第三讲.RTTI运行时类型识别
MFC原理第三讲.RTTI运行时类型识别 一丶什么是RTTI RTTI. 运行时的时候类型的识别. 运行时类型信息程序.能够使用基类(父类)指针 或者引用 来检查这些指针或者引用所指的对象. 实际派生 ...
- C++反汇编第六讲,认识C++中的Try catch语法,以及在反汇编中还原
C++反汇编第六讲,认识C++中的Try catch语法,以及在反汇编中还原 我们以前讲SEH异常处理的时候已经说过了,C++中的Try catch语法只不过是对SEH做了一个封装. 如果不懂SEH异 ...
- Stanford机器学习---第六讲. 怎样选择机器学习方法、系统
原文:http://blog.csdn.net/abcjennifer/article/details/7797502 本栏目(Machine learning)包括单参数的线性回归.多参数的线性回归 ...
- 机器学习基石的泛化理论及VC维部分整理(第六讲)
第六讲 第五讲主要讲了机器学习可能性,两个问题,(1)\(E_{in} 要和 E_{out}\) 有很接近,(2)\(E_{in}\)要足够小. 对于第一个假设,根据Hoefding's Inequa ...
- 《ArcGIS Engine+C#实例开发教程》第六讲 右键菜单添加与实现
原文:<ArcGIS Engine+C#实例开发教程>第六讲 右键菜单添加与实现 摘要:在这一讲中,大家将实现TOCControl控件和主地图控件的右键菜单.在AE开发中,右键菜单有两种实 ...
随机推荐
- 小白的CTF学习之路4——内存
明天要进行二模考试了,沉住气,加油,能过 内存是学C路上必不可少的一环,一定要非常认真的去学 内存的物理结构: ROM:只读内存——早期的手机 RAM:读写(数据断点既消) DRAM:经常更新 SRA ...
- linux 安装mysql5.7.25
这两天一直在弄mysql.一直安装.终于可以安装一个成一个了.哈哈哈 自己又写了个脚本希望对大家有所帮助 脚本非常简单 不错操作起来也很容易 重要提示 我的linux 是centos7.不是6. 7和 ...
- MAC帧和PPP帧区别
- 哈夫曼(Huffman)树和哈夫曼编码
一.哈夫曼(Huffman)树和哈夫曼编码 1.哈夫曼树(Huffman)又称最优二叉树,是一类带权路径长度最短的树, 常用于信息检测. 定义: 结点间的路径长度:树中一个结点到另一个结点之间分支数目 ...
- linux pxe 安装Centos7
服务端 需要3种服务 dhcp + tftp + vsftp tftp 提供引导 为什么不使用其他协议来进行pxe引导 是因为网卡只会集成tftp这种服务 写明到镜像的方式 dhcp 下发tf ...
- Sql- Group By ,Where,having用法
Group by,where,having 是数据库查询中最常用的几个关键字.在工作中,时常用到,前面遇到一个问题,一个查询中使用了where ,group by ,having及聚集函数时 ,执行顺 ...
- WebPackBrows
一个http工具,通过java编写 调用方法 s.y.webpackbrows.fac.WebPackFactor.getConnection 还会继续完善 下载位置 https://pan.baid ...
- “Xavier”安卓木马分析:可静默收集数据并远程代码执行
趋势科技研究发现了一款Android恶意木马——Xavier.在谷歌Play应用市场中,超过800款Android应用感染了该恶意木马,影响数百万Android用户.感染的应用范围覆盖图片编辑器,墙纸 ...
- 删除坏掉的 Active Directory Domain
最近公司的某个 Domain Controller 报告可能由于长时间没在线,同步失败,然后用 Repldiag 工具清理 lingering objects 的过程中,该工具报告存在 serious ...
- Vue实现懒加载的基本思路
懒加载是前端开发者的基本功之一.实现懒加载肯定是要直接操作DOM的,这个没得跑,但我们可以想办法让流程尽可能优雅些. 基本结构 父组件是列表容器,子组件是列表中的项,如卡片.帖子等,承载图片的DOM对 ...