在上篇中,介绍了消息网络的总体布局,这篇要介绍的是,消息进来以后,如何顺着整个消息网络,找到本身对应的处理函数。编程
1 命令消息(WM_COMMAND)网络
好比菜单项的选择,工具栏按钮点击等触发产生的消息。全部派生自CCmdTarget 的类都有能力接收WM_COMMAND 消息。ide
2 标准消息(WM_XXX)函数
好比窗口建立,窗口销毁等。全部派生自CWnd 的类才有资格接收标准消息。工具
3 通告消息(WM_NOTIFY)布局
这是有控件向父窗口发送的消息,标示控件自己状态的变化。好比下拉列表框选项的改变CBN_SELCHANGE 和树形控件的TVN_SELCHANGED 消息都是通告消息。this
Window 9x 版及之后的新控件通告消息再也不经过WM_COMMAND 传送,而是经过WM_NOTIFY 传送, 可是老控件的通告消息, 好比CBN_SELCHANGE 仍是经过WM_COMMAND 消息发送。spa
4 自定义消息.net
在MFC 编程中,可使用自定义消息。使用自定义消息须要遵循必定的规范,并编写消息响应函数,该例子在本系列文章《WINDOW消息机制(一):向窗体发送消息》中已有示例,此处再也不赘述。指针
函数AfxWindProc时MFC中消息推进引擎的入口点,全部的消息,在Dispatch后,由该函数进行推进,并找到匹配的处理函数。参数很简单:消息所属窗口的句柄、消息类型及其相关参数。该函数应该是运行在CWinApp的run函数中的,有证据: 在调用堆栈的最下面,就是Run函数。
mfc80d.dll!CWinThread::Run()
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); //若窗口指针为NULL或者句柄不匹配,则采用系统默认处理函数进行处理。 if (pWnd == NULL || pWnd->m_hWnd != hWnd) return ::DefWindowProc(hWnd, nMsg, wParam, lParam); //信息匹配,调用AfxCallWndProc return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam); }
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0) { ...... //删除了一些特殊消息处理,只保留关键代码 // delegate to object's WindowProc lResult = pWnd->WindowProc(nMsg, wParam, lParam); ...... }
这边就涉及到一个多态问题,咱们调用pWnd->WindowProc,根据pWnd的具体类型,CDialog,CFrameWnd,CView等具体类型,调用具体的WindowProc.
CFrameWnd中没有定义WindowProc函数,因此调用CWnd::WindowProc
CView中没有定义WindowProc函数,因此调用CWnd::WindowProc
CDoc及其父类CCmdTarget确定没有WindowProc函数,由于他们不是窗口哈
CDialog中没有定义WindowProc函数,因此调用CWnd::WindowProc
在CWnd中有以下声明:
// for processing Windows messages virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
基本逻辑:
定义以下:
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult) { //针对COMMAND类型的消息进行处理 if (message == WM_COMMAND) { if (OnCommand(wParam, lParam)) { lResult = 1; goto LReturnTrue; } return FALSE; } //针对NOTIFY类型的消息进行处理 if (message == WM_NOTIFY) { NMHDR* pNMHDR = (NMHDR*)lParam; if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult)) goto LReturnTrue; return FALSE; } //各类SEPCIAL CASE,不一一列举,被我删除,以避免占用太多的篇幅 ...... //获取消息MAP 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) { //在MSGCACHE中命中了消息 lpEntry = pMsgCache->lpEntry; winMsgLock.Unlock(); if (lpEntry == NULL) //若没有对应的处理函数,返回false,由DefWindowProc处理 return FALSE; // 根据消息类型:标准WINDOWS消息以及用户自定义消息,分别进行处理 if (message < 0xC000) goto LDispatch; else goto LDispatchRegistered; }else { //在当前MsgCache中未找到,则到BaseMessageMap中寻找 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) //根据消息大小,判断消息为WINDOWS标准消息 { // constant window message if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0)) != NULL) { pMsgCache->lpEntry = lpEntry; winMsgLock.Unlock(); goto LDispatch; } } else { //根据消息大小判断消息为用户自定义消息 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: //标准的WINDOWS消息 ASSERT(message < 0xC000); mmf.pfn = lpEntry->pfn; ...... goto LReturnTrue; LDispatchRegistered: // 处理用户自定义的消息 ...... go to LReturnTrue; LReturnTrue: if (pResult != NULL) *pResult = lResult; return TRUE; }根据上文所述,若是是WINDOWS标准消息以及用户自定义消息,那么消息的流向是从子类流向父类(即当前类没法处理,则交由父类的消息MAP搞定,若还不行,继续上传,若是都不行,则由WINDOWS默认的处理函数进行处理) 注:本文说明了消息推送的一个总体过程,可是在此当中是有所区分的,标准的WINDOWS消息以及用户的自动以消息才去的是自底向上推送,可是命令消息涉及到一个横向的消息推送,就在下文说明吧。