在上篇中,介紹了消息網絡的總體佈局,這篇要介紹的是,消息進來以後,如何順着整個消息網絡,找到本身對應的處理函數。編程
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消息以及用戶的自動以消息纔去的是自底向上推送,可是命令消息涉及到一個橫向的消息推送,就在下文說明吧。