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 destroyedHINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, ATL_RT_MENU);if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL){TRACE(traceAppMsg, 0, "Warning: failed to load menu for CFrameWnd.\n");PostNcDestroy(); // perhaps delete the C++ objectreturn FALSE;}}m_strTitle = lpszWindowName; // save title for laterif (!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.\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 parametersCREATESTRUCT 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 _DEBUGif (hWnd == NULL){TRACE(traceAppMsg, 0, "Warning: Window creation failed: GetLastError returns 0x%8.8X\n",GetLastError());} #endifif (!AfxUnhookWindowCreate())PostNcDestroy(); // cleanup if CreateWindowEx fails too soonif (hWnd == NULL)return FALSE;ASSERT(hWnd == m_hWnd); // should have been set in send msg hookreturn 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 onceASSERT(pThreadState->m_pWndInit == NULL); // hook not already in progresspThreadState->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 callLRESULT 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 AfxWndProcif (nMsg == WM_QUERYAFXWNDPROC)return 1;// all other messages route through message mapCWnd* 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 commandsif (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 notifiesif (message == WM_NOTIFY){NMHDR* pNMHDR = (NMHDR*)lParam;if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))goto LReturnTrue;return FALSE;}// special case for activationif (message == WM_ACTIVATE)_AfxHandleActivate(this, wParam, CWnd::FromHandle((HWND)lParam));// special case for set cursor HTERRORif (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 hitlpEntry = pMsgCache->lpEntry;winMsgLock.Unlock();if (lpEntry == NULL)return FALSE;// cache hit, and it needs to be handledif (message < 0xC000)goto LDispatch;elsegoto LDispatchRegistered;}else{// not in cache, look for itpMsgCache->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 messageif ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,message, 0, 0)) != NULL){pMsgCache->lpEntry = lpEntry;winMsgLock.Unlock();goto LDispatch;}}else{// registered windows messagelpEntry = pMessageMap->lpEntries;while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) != NULL){UINT* pnID = (UINT*)(lpEntry->nSig);ASSERT(*pnID >= 0xC000 || *pnID == 0);// must be successfully registeredif (*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 pointerBOOL (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中. 這個函數跟上面類似.也是遍歷消息映射表去尋找.有興趣的可以自己看下源碼.
堅持兩字,簡單,輕便,但是真正的執行起來確實需要很長很長時間.當你把堅持兩字當做你要走的路,那么你總會成功. 想學習,有問題請加群.群號:725864912群名稱: 逆向學習小分隊 群里有大量學習資源. 以及定期直播答疑.有一個良好的學習氛圍. 涉及到外掛反外掛病毒 司法取證加解密 驅動過保護 VT 等技術,期待你的進入。
總結
以上是生活随笔為你收集整理的MFC原理 消息传递的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分布式开放 消息系统 (RocketMQ
- 下一篇: springCloud - 第4篇 -