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

                  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原理第六讲.消息传递的更多相关文章

  1. MFC原理第四讲.动态创建机制

    MFC原理第四讲.动态创建机制 一丶要学习的知识点以及简介 动态创建是什么意思? 动态创建其实就是跟C++的new一样.都是创建对象.但是规避了C++语法的缺陷. 例如: char * ClassNa ...

  2. MFC原理第二讲.MFC的初始化过程

    MFC原理第二讲MFC的初始化过程 一丶简介 通过上一讲.我们手工模拟了一个MFC程序. 但是上一讲留下了疑问. 就是WinMain在哪.以及消息处理在哪里. 这一节主要就是讲解Winmain在哪里. ...

  3. MFC原理第一讲.MFC的本质.以及手工编写MFC的程序

    MFC原理第一讲.MFC的本质.以及手工编写MFC的程序 PS: 这个博客属于复习知识.从头开始讲解. 在写这篇博客之前.已经写了3篇MFC的本质了.不过掌握知识点太多.所以从简重新开始. 一丶MFC ...

  4. MFC原理第五讲.消息映射.以及如何添加消息

    MFC原理第五讲.消息映射.以及如何添加消息 一丶消息映射是什么 我们知道.Win32程序.都是通过消息去驱动的. 不断的在处理消息. 只要我们使用固定的宏.就可以让我们的框架知道一旦消息发生.该往哪 ...

  5. MFC原理第三讲.RTTI运行时类型识别

    MFC原理第三讲.RTTI运行时类型识别 一丶什么是RTTI RTTI. 运行时的时候类型的识别. 运行时类型信息程序.能够使用基类(父类)指针 或者引用 来检查这些指针或者引用所指的对象. 实际派生 ...

  6. C++反汇编第六讲,认识C++中的Try catch语法,以及在反汇编中还原

    C++反汇编第六讲,认识C++中的Try catch语法,以及在反汇编中还原 我们以前讲SEH异常处理的时候已经说过了,C++中的Try catch语法只不过是对SEH做了一个封装. 如果不懂SEH异 ...

  7. Stanford机器学习---第六讲. 怎样选择机器学习方法、系统

    原文:http://blog.csdn.net/abcjennifer/article/details/7797502 本栏目(Machine learning)包括单参数的线性回归.多参数的线性回归 ...

  8. 机器学习基石的泛化理论及VC维部分整理(第六讲)

    第六讲 第五讲主要讲了机器学习可能性,两个问题,(1)\(E_{in} 要和 E_{out}\) 有很接近,(2)\(E_{in}\)要足够小. 对于第一个假设,根据Hoefding's Inequa ...

  9. 《ArcGIS Engine+C#实例开发教程》第六讲 右键菜单添加与实现

    原文:<ArcGIS Engine+C#实例开发教程>第六讲 右键菜单添加与实现 摘要:在这一讲中,大家将实现TOCControl控件和主地图控件的右键菜单.在AE开发中,右键菜单有两种实 ...

随机推荐

  1. excel日期拾取插件(支持Excel 2007 - 2016)

    插件安装完毕后示意图如下: 插件安装说明请查看附件里面的安装说明. 插件下载

  2. LOJ-10095(缩点的特殊使用)

    题目链接:传送门 思路: 缩点求最值,但是有一点行不通,如果被选中的点才能缩点,否则缩点没有意义: 所以就先缩选中的点,然后从小到大统计没有缩点的点,就是NO: 如果找最小值,就是一个环里的最小值,然 ...

  3. docker之tomcat简单部署

    将apache-tomcat-8.0.36.tar.gz及jdk-7u79-linux-x64.gz拷贝到创建的tomcat8目录下 在tomcat8目录下创建Dockerfile文件 在Docker ...

  4. python线程的同步事件Event

    Event对象: 用于线程间的通信,某个线程需要根据其他线程的状态来判断自己的下一步操作. Event内部定义了一个全局变量:_flag,默认为False. 当_flag = False时,会阻塞当前 ...

  5. pycharm clion phpstorn全家桶激活码(可以用到2019年4月)

    SXXI7H41YN-eyJsaWNlbnNlSWQiOiJTWFhJN0g0MVlOIiwibGljZW5zZWVOYW1lIjoicGF5bmUgd2FuZyIsImFzc2lnbmVlTmFtZ ...

  6. UE4行为树

    这是 UE4中行为树编辑器 中可用的默认节点.取决于开发项目的不同(如射击游戏),可能会有更多节点.这里介绍五种行为树节点类型:   节点类型 描述 Composite(流程控制节点) 这种节点定义一 ...

  7. HTTP lab01 做一个简单的测试用 web页面

      做一个简单的测试用 web页面     1.安装httpd服务   yum install httpd   安装完httpd服务后,系统就自动生成了/var/www/html目录     创建一个 ...

  8. Android Studio将引用第三方jar包的library打包成jar包

    在该module的build.gradle中添加 task makeJar(type: Jar) { archiveName 'mysdk.jar' from('build/intermediates ...

  9. java面试一、1.5JVM

    免责声明:     本文内容多来自网络文章,转载为个人收藏,分享知识,如有侵权,请联系博主进行删除. 1.5.JVM JVM运行时内存区域划分

  10. C++输出格式

    C++输出格式 C++中默认输出有效位数是6位,即 则输出: 221.111.11011199967 //6位有效数字,自动截取保存六位1.99967e+006 //六位以上且无法省略显示将会变为指数 ...