MFC 程序逆向

上篇囉裏囉嗦地說了一大堆,其實所說的消息都是PostMessage方式的。MFC中還有另外一種很常見的消息發送方式,就是SendMessage函 數。這個消息起始路徑和上篇所講的完全不一樣。這種方式下,前面的7個站點均不執行,而是直接進入第8站點:User32內核,從第8站點出來後,這兩種 消息方式走上了同一條道路,進入第9個站點或第10個站點了,真是殊道同歸。
對於MFC窗口程序,所有窗口都使用同一窗口過程 : AfxWndProcBase(第9個站點)或AfxWndProc(第10個站點)。如果程序是 動態鏈接到MFC DLL(定義了_AFXDLL),則AfxWndProcBase被用作窗口過程,否則AfxWndProc被用作窗口過程。而在 AfxWndProcBase中,最終也是調用AfxWndProc函數。
所以,可以說,第10個站點:AfxWndProc函數是MFC的所有消息必經之點。
可以作如下測試:在Button1事件代碼中加入:   SendMessage(WM_COMMAND,IDC_BUTTON2,0);   這是往 Button2發送點擊消息,當點擊Button1時,跟進Button1的事件代碼流程,再跟進SendMessage函數的內部代碼,可以發現,和上 面所講是完全一樣的。

各位可能有疑問了,消息從User32內核出來之後,應該是由Windows系統自動發往各個窗口的消息處理函數,但這裏怎麼會全部進入了AfxWndProc()函數呢?這涉及到了鉤子函數,有興趣者,請看本文附錄,正文不作多說。現在繼續進入消息之旅:

請看以下源碼:
9. AfxWndProcBase函數
LRESULT CALLBACK AfxWndProcBase(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
   AFX_MANAGE_STATE(_afxBaseModuleState.GetData());
   return AfxWndProc(hWnd, nMsg, wParam, lParam);
}
AfxWndProcBase首先使用宏AFX_MANAGE_STATE設置正確的模塊狀態,然後調用AfxWndProc。
說明:如果程序是動態鏈接到MFC DLL(定義了_AFXDLL),則AfxWndProcBase被用作窗口過程,否則AfxWndProc被用作窗口過程。從源碼可以知道,在AfxWndProcBase中,最終也是調用AfxWndProc函數。

AfxWndProcBase反彙編代碼:
73D31B81 >   MOV EAX,MFC42.73DC2CFE
73D31B86     CALL MFC42.__EH_prolog                                      ; JMP 到 MSVCRT._EH_prolog
73D31B8B     PUSH ECX
73D31B8C     PUSH ECX
73D31B8D     PUSH MFC42.#2188_?CreateObject@?$CProcessLocal@V_AFX_BASE_>
73D31B92     MOV ECX,OFFSET MFC42._afxBaseModuleState
73D31B97     CALL MFC42.#3028_?GetData@CProcessLocalObject@@QAEPAVCNoTr>
73D31B9C     PUSH EAX
73D31B9D     LEA ECX,DWORD PTR SS:[EBP-14]
73D31BA0     CALL MFC42.#6467_??0AFX_MAINTAIN_STATE2@@QAE@PAVAFX_MODULE>
73D31BA5     PUSH DWORD PTR SS:[EBP+14]
73D31BA8     AND DWORD PTR SS:[EBP-4],0
73D31BAC     PUSH DWORD PTR SS:[EBP+10]
73D31BAF     PUSH DWORD PTR SS:[EBP+C]
73D31BB2     PUSH DWORD PTR SS:[EBP+8]
73D31BB5     CALL MFC42.#1578_?AfxWndProc@@YGJPAUHWND__@@IIJ@Z
73D31BBA     MOV ECX,DWORD PTR SS:[EBP-10]
73D31BBD     MOV EDX,DWORD PTR SS:[EBP-14]
73D31BC0     MOV DWORD PTR DS:[ECX+4],EDX
73D31BC3     MOV ECX,DWORD PTR SS:[EBP-C]
73D31BC6     MOV DWORD PTR FS:[0],ECX
73D31BCD     LEAVE
73D31BCE     RETN 10

10. AfxWndProc函數 - 是所有的CWnd類及其派生類的WndProc
LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
   if (nMsg == WM_QUERYAFXWNDPROC)   return 1;
   CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
   return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}
AfxWndProc()要做的第一件事是找到目標窗口的CWnd對象。一旦找到CWnd對象,就會立刻調用AfxCallWndProc()。
這樣,AfxWndProc就成爲CWnd或其派生類的窗口過程。不論隊列消息,還是非隊列消息,都送到AfxWndProc窗口過程來處理(如果使用MFC DLL,則AfxWndProcBase被調用,然後是AfxWndProc)。
Windows消息送給AfxWndProc窗口過程之後,AfxWndProc得到HWND窗口對應的MFC窗口對象,然後,調用AfxCallWndProc函數進行下一步處理。
AfxWndProc函數反彙編代碼:
73D31BD1 >   PUSH EBP
73D31BD2     MOV EBP,ESP
73D31BD4     CMP DWORD PTR SS:[EBP+C],360
73D31BDB     JE MFC42.73D8BF8A
73D31BE1     PUSH DWORD PTR SS:[EBP+8]
73D31BE4     CALL MFC42.#2867_?FromHandlePermanent@CWnd@@SGPAV1@PAUHWND>
73D31BE9     PUSH DWORD PTR SS:[EBP+14]
73D31BEC     PUSH DWORD PTR SS:[EBP+10]
73D31BEF     PUSH DWORD PTR SS:[EBP+C]
73D31BF2     PUSH DWORD PTR SS:[EBP+8]
73D31BF5     PUSH EAX
73D31BF6     CALL MFC42.#1109_?AfxCallWndProc@@YGJPAVCWnd@@PAUHWND__@@I>
73D31BFB     POP EBP
73D31BFC     RETN 10
提示:
     a. OD加載程序後,調出MFC42.dll模塊,定位到AfxWndProc代碼入口處。
b. 在入口PUSH EBP處設置條件斷點[esp+8]==111,即可設置按鈕點擊事件斷點。
c. [esp+4]==002407B4 && [esp+8]==202 可以爲指定按鈕設置點擊斷點(002407B4是按鈕的句柄值)。
說明:此時設置條件斷點就更方便了,[esp]是返回地址,[esp+4]是接收消息的窗口句柄,[esp+8]就是消息代碼值

11. AfxCallWndProc函數
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,WPARAM wParam = 0, LPARAM lParam = 0)
{      _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
      //存儲標誌符和參數,因爲MFC內部需要這些參數和信息,但用戶不需關心
      MSG oldState = pThreadState->m_lastSentMsg;  
      pThreadState->m_lastSentMsg.hwnd = hWnd;
      pThreadState->m_lastSentMsg.message = nMsg;
      pThreadState->m_lastSentMsg.wParam = wParam;
      pThreadState->m_lastSentMsg.lParam = lParam;
      …
      //委派到窗口的WindowProc
      lResult = pWnd->WindowProc(nMsg, wParam, lParam);
      …
      return lResult;
}
AfxCallWndProc函數把消息送給CWnd類或其派生類的對象。該函數主要是把消息和消息參數(nMsg、wParam、lParam)傳遞給 MFC窗口對象的成員函數WindowProc(pWnd->WindowProc)作進一步處理。如果是WM_INITDIALOG消息,則在調 用WindowProc前後要作一些處理。

AfxCallWndProc 函數反彙編代碼:
73D31BFF >   MOV EAX,MFC42.73DC2D82
73D31C04     CALL MFC42.__EH_prolog                                    ; JMP 到 MSVCRT._EH_prolog
73D31C09     SUB ESP,34
73D31C0C     PUSH EBX
73D31C0D     PUSH ESI
73D31C0E     PUSH EDI
73D31C0F     MOV ECX,OFFSET MFC42._afxThreadState
73D31C14     MOV DWORD PTR SS:[EBP-10],ESP
73D31C17     PUSH MFC42.#2202_?CreateObject@?$CThreadLocal@V_AFX_THREAD>
73D31C1C     CALL MFC42.#3030_?GetData@CThreadLocalObject@@QAEPAVCNoTra>
...
73D31C62     PUSH DWORD PTR SS:[EBP+18]
73D31C65     MOV EAX,DWORD PTR DS:[EDI]
73D31C67     MOV ECX,EDI
73D31C69     PUSH DWORD PTR SS:[EBP+14]
73D31C6C     PUSH ESI
73D31C6D     CALL DWORD PTR DS:[EAX+A0]     <D1.?WindowProc@CWnd@@MAEJIIJ   mfc42:MFC42.DLL>
...
73D31C9A     C2 1400          RETN 14

12. CWnd::WindowProc函數
AfxWndProc和 AfxCallWndProc都是AFX的API函數。而WindowProc已經是CWnd的一個方法。所以可以注意到在 WindowProc中已經沒有關於句柄或者是CWnd的參數了。至此,消息已經正式登堂入室,步入MFC的大廳了。真是辛苦啊!
其源碼如下:
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
   LRESULT lResult = 0;
   if (!OnWndMsg(message, wParam, lParam, &lResult))     // OnWndMsg做了大部分工作
     lResult = DefWindowProc(message, wParam, lParam);
   return lResult;
}
CWnd::WindowProc先發送消息到OnWndMsg()函數,它試圖在類中爲該消息尋找一個處理函數;如果未被處理,則調用 DefWindowProc()函數。DefWindowProc()是缺省的窗口過程,所有不能或者沒有被OnWndMsg處理的函數都將交由它處理。
CWnd::WindowProc是一個虛擬函數,程序員可以在CWnd的派生類中覆蓋它,改變MFC分發消息的方式。例如,MFC的 CControlBar就覆蓋了WindowProc,對某些消息作了自己的特別處理,其他消息處理由基類的WindowProc函數完成。

CWnd::WindowProc()函數反彙編代碼:
73D31CC8 >   PUSH EBP
73D31CC9     MOV EBP,ESP
73D31CCB     PUSH ECX
73D31CCC     PUSH ESI
73D31CCD     MOV ESI,ECX
73D31CCF     LEA ECX,DWORD PTR SS:[EBP-4]
73D31CD2     AND DWORD PTR SS:[EBP-4],0
73D31CD6     MOV EAX,DWORD PTR DS:[ESI]
73D31CD8     PUSH ECX
73D31CD9     PUSH DWORD PTR SS:[EBP+10]
73D31CDC     MOV ECX,ESI
73D31CDE     PUSH DWORD PTR SS:[EBP+C]
73D31CE1     PUSH DWORD PTR SS:[EBP+8]
73D31CE4     CALL DWORD PTR DS:[EAX+A4]   ;<D1.?OnWndMsg@CWnd@@    mfc42:MFC42.DLL>
73D31CEA     TEST EAX,EAX
73D31CEC     JNZ SHORT MFC42.73D31D04
73D31CEE     PUSH DWORD PTR SS:[EBP+10]
73D31CF1     MOV EAX,DWORD PTR DS:[ESI]
73D31CF3     MOV ECX,ESI
73D31CF5     PUSH DWORD PTR SS:[EBP+C]
73D31CF8     PUSH DWORD PTR SS:[EBP+8]
73D31CFB     CALL DWORD PTR DS:[EAX+A8]    ; <D1.?DefWindowProcA@CWnd@@    mfc42:MFC42.DLL>
73D31CFB     CALL DWORD PTR DS:[EAX+A8]
73D31D01     MOV DWORD PTR SS:[EBP-4],EAX
73D31D04     MOV EAX,DWORD PTR SS:[EBP-4]
73D31D07     POP ESI
73D31D08     LEAVE
73D31D09     RETN 0C
提示:
     a. OD加載程序後,調出MFC42.dll模塊,定位到WindowProc代碼入口處。
b. 在入口PUSH EBP處設置條件斷點[esp+4]==111,即可設置按鈕點擊事件斷點。
說明:   1. 此時[esp]是返回地址,[esp+4]是消息代碼值。
         2. 由於,此時的接收消息窗口的句柄被CWnd類隱藏起來了,所以此時要設定指定按鈕斷點不太方便。

13. CWnd::OnWndMsg函數 (這個函數很長,此處僅選一部分)
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{   LRESULT lResult = 0;
   if (message == WM_COMMAND)
   {   if (OnCommand(wParam, lParam))     // 命令消息從此處流進
     {   lResult = 1;
       goto LReturnTrue;     }
     return FALSE;   }
   if (message == WM_NOTIFY)
   {   NMHDR* pNMHDR = (NMHDR*)lParam;
     if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))   goto LReturnTrue;
     return FALSE;   }
   if (message == WM_ACTIVATE)   _AfxHandleActivate(this, wParam, CWnd::FromHandle((HWND)lParam));
   ...
}
CWnd::OnWndMsg()函數的功能首先按字節對消息進行排序,對於WM_COMMAND消息,調用OnCommand()消息響應函數,對於 WM_NOTIFY消息調用OnNotify()消息響應函數。任何被遺漏的消息將是一個窗口消息。OnWndMsg()函數搜索類的消息映像,以找到一 個能處理任何窗口消息的處理函數。
如果OnWndMsg()函數不能找到這樣的處理函數的話,則把消息返回到WindowProc()函數,由它將消息發送給DefWindowProc()函數。

CWnd::OnWndMsg 部分反彙編代碼:
73D31D0C >   MOV EAX,MFC42.73DC2E0A
73D31D11     CALL MFC42.__EH_prolog         ; JMP 到 MSVCRT._EH_prolog
...
73D31D1E     MOV EBX,DWORD PTR SS:[EBP+8]   ;取出message參數
...
73D31D23     CMP EBX,111           ;是否爲WM_COMMAND消息
73D31D29     MOV EDI,ECX
73D31D2B     JE MFC42.73D31DBE
...
73D31DBE     PUSH DWORD PTR SS:[EBP+10]
73D31DC1     MOV EAX,DWORD PTR DS:[EDI]
73D31DC3     PUSH DWORD PTR SS:[EBP+C]
73D31DC6     CALL DWORD PTR DS:[EAX+80] ; <D1.?OnCommand@CWnd@@   mfc42:MFC42.DLL>
...
提示:
73D31DC6     CALL DWORD PTR DS:[EAX+80],也是對點擊按鈕之類的WM_COMMAND消息設置斷點較好的切入點,這裏可以直接F2設置斷點,更爲方便,因爲,只有在WM_COMMAND消息下,纔有可能執行這條語句。


14. CWnd::OnCommand函數
BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{
   UINT nID = LOWORD(wParam);
   HWND hWndCtrl = (HWND)lParam;
   int nCode = HIWORD(wParam);
   ...
   return OnCmdMsg(nID, nCode, NULL, NULL);   //通過虛函數調用,直接進入了重載的的CD2Dlg::OnCmdMsg函數
}
該函數查看這是不是一個控件通知(lParam參數不爲NULL,如果lParam參數爲空的話,說明該消息不是控件通知),如果它是, OnCommand()函數會試圖將消息映射到製造通知的控件;如果他不是一個控件通知(或者如果控件拒絕映射的消息)OnCommand()就會調用 OnCmdMsg()函數

CWnd::OnCommand()函數部分反彙編代碼:
73D3291C >   PUSH EBP
73D3291D     MOV EBP,ESP
73D3291F     SUB ESP,2C
73D32922     MOV EAX,DWORD PTR SS:[EBP+8]

73D3296B     PUSH EBX
73D3296C     PUSH EBX
73D3296D     MOV ECX,ESI
73D3296F     PUSH DWORD PTR SS:[EBP+8]
73D32972     PUSH EDI
73D32973     CALL DWORD PTR DS:[EAX+14]     ;<D1.?OnCmdMsg@CDialog@@    mfc42:MFC42.DLL>
73D32976     POP EDI
73D32977     POP ESI
73D32978     POP EBX
73D32979     LEAVE
73D3297A     RETN 8
提示:
在函數入口處,同樣可以F2直接設置斷點,定位WM_COMMAND消息。

15. CD2Dlg::OnCmdMsg函數(如果重載了的話)
BOOL CD2Dlg::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{    ...
   return CDialog::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}

16. CDialog::OnCmdMsg()函數:
BOOL CDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)
{
   if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))   //從這裏直接進入了CCmdTarget::OnCmdMsg()
     return TRUE;
   ... 
}
CDialog::OnCmdMsg()函數部分反彙編代碼:
73D38FAA >   PUSH EBP
73D38FAB     MOV EBP,ESP

73D38FBB     PUSH DWORD PTR SS:[EBP+10]
73D38FBE     PUSH EDI
73D38FBF     PUSH EBX
73D38FC0     CALL MFC42.#4424_?OnCmdMsg@CCmdTarget@@UAEHIHPAXPAUAFX_CMD>     ; CCmdTarget::OnCmdMsg()
73D38FC5     TEST EAX,EAX
73D38FC7     JNZ SHORT MFC42.73D38FE6

73D38FD8     RETN 10
對話框的OnCmdMsg其實也是重載了CCmdTarget::OnCmdMsg()函數。

17. CCmdTarget::OnCmdMsg()函數
BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)
{   const AFX_MSGMAP* pMessageMap;
   const AFX_MSGMAP_ENTRY* lpEntry;
   UINT nMsg = 0;
   nMsg = HIWORD(nCode);
   nCode = LOWORD(nCode);
   if (nMsg == 0)   nMsg = WM_COMMAND;
   //查看消息映射是否自己所需
for (pMessageMap = GetMessageMap(); pMessageMap != NULL;pMessageMap = (*pMessageMap->pfnGetBaseMap)())
   {   lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID); 
     if (lpEntry != NULL)
     {    //有匹配的消息映射時,會進行如下調用:
       return _AfxDispatchCmdMsg(this, nID, nCode,lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);
     }   }
   return FALSE; }
根據接收消息的類,OnCmdMsg()函數將在一個稱爲命令傳遞(Command Routing)的過程中潛在的傳遞命令消息和控件通知。例如:如果擁有該窗口的類是一個框架類,則命令和通知消息也被傳遞到視圖和文檔類,併爲該類尋找一個消息處理函數。
CCmdTarget是MFC消息映射體系結構的基類。正是通過這個體系結構,纔將命令或者消息映射到開發人員所寫的命令處理函數或者消息響應函數。
OnCmdMsg()實際上是CCmdTarget的成員函數,而不是CWnd的成員函數。認實這一點很重要,因爲它允許任何從CCmdTarget派生 的類接收一個命令消息,即使那些沒有一個窗口的類也可以。如,當你跟蹤MFC的SDI或MDI程序消息流程時,會發現沒有窗口的文檔類處理消息時,也會重 載OnCmdMsg()函數,使它能爲文檔模板類提供命令消息。

73D3223C >   PUSH EBP
73D3223D     MOV EBP,ESP

73D32267     CALL DWORD PTR DS:[EAX+30]   ; GetMessageMap 得到消息映射表地址

73D3227A     CALL MFC42.#1145_?AfxFindMessageEntry@@YGPBUAFX_MSGMAP_ENT>    ;尋找消息函數
73D3227F     TEST EAX,EAX
73D32281     JNZ SHORT MFC42.73D32296       ; 找到,則轉

73D32296     PUSH DWORD PTR SS:[EBP+14]
73D32299     PUSH DWORD PTR DS:[EAX+10]
73D3229C     PUSH DWORD PTR SS:[EBP+10]
73D3229F     PUSH DWORD PTR DS:[EAX+14]     ;這個[EAX+14]就是消息函數的地址
73D322A2     PUSH DWORD PTR SS:[EBP+C]
73D322A5     PUSH DWORD PTR SS:[EBP+8]
73D322A8     PUSH EDI
73D322A9     CALL MFC42.73D3233C         ;調用_AfxDispatchCmdMsg這個函數

這裏GetMessageMap() 和 AfxFindMessageEntry() 兩個函數就是搜索查尋消息函數在消息映射表中的位置,從而找出消 息函數的地址。關於這個兩個函數代碼分析,及消息映射表的結構,本文就不分析了(分析起來,又要囉裏囉嗦地說了一大堆)。有興趣者可自行參考相關資料,網 上很多(看雪論壇精華集上也有很多這方面的資料,而且寫得很不錯)。

提示:
個人認爲,對於對話框程序,在這個函數函數入口處設置斷點最好(請記住這個函數:CCmdTarget::OnCmdMsg()),因爲:
一、不用設置條件斷點,只有在發生WM_COMMAND消息後,才運行到此。
二、而且再繼續往下運行到73D3229F     PUSH DWORD PTR DS:[EAX+14],就得到了消息函數的地址。
或者,也可以往下到 73D322A9     CALL MFC42.73D3233C語句時F7跟進,再往下執行幾條語句,就很容易來到WM_COMMAND消息函數代碼處。見下面說明。

跟進73D3233C,執行幾條語句,經過幾次跳轉後,很快就定位到了按鈕事件代碼處:
73D3233C     PUSH EBP
73D3233D     MOV EBP,ESP
...
73D3234E     CMP EAX,28
73D32351     JBE SHORT MFC42.73D32390
73D32353     SUB EAX,29
73D32356     JE MFC42.73D8E55B
73D3235C     SUB EAX,3
73D3235F     JNZ MFC42.73D8E52F

73D3239E     SUB EAX,0A
73D323A1 >   JE SHORT MFC42.73D323D2
73D323A3     DEC EAX
73D323A4     JE MFC42.73D8E4FD
73D323AA     SUB EAX,16
73D323AD     JE SHORT MFC42.73D323C8
73D323AF     SUB EAX,3
73D323B2     JNZ MFC42.73D8E4E7

73D323D2     MOV ECX,DWORD PTR SS:[EBP+8]   ; Case C of Switch XXXXXXXX
73D323D5     CALL DWORD PTR SS:[EBP+14]     ; D1.?OnButton1@CD1Dlg@@IAEXXZ D1Dlg.obj   進入按鈕函數代碼
73D323D8   ^ JMP SHORT MFC42.73D3237B
其實上面一段代碼就是_AfxDispatchCmdMsg函數。

18. _AfxDispatchCmdMsg()函數(反彙編代碼見上), 找到按鈕消息函數處
AFX_STATIC BOOL AFXAPI _AfxDispatchCmdMsg(CCmdTarget* pTarget, UINT nID, int nCode,
   AFX_PMSG pfn, void* pExtra, UINT nSig, AFX_CMDHANDLERINFO* pHandlerInfo)
{   union MessageMapFunctions mmf;
   mmf.pfn = pfn;
   BOOL bResult = TRUE;
   switch (nSig)
   {
   case AfxSig_vv:
     // normal command or control notification
     ASSERT(CN_COMMAND == 0);         // CN_COMMAND same as BN_CLICKED
     ASSERT(pExtra == NULL);
     (pTarget->*mmf.pfn_COMMAND)();   //從這裏執行下面的CD2Dlg::OnButton1()函數
     break;
   case AfxSig_bv:
     ...
   ...
}
這裏,通過調用全局函數_AfxDispatchCmdMsg,來調用具體的消息處理函數。這樣便完成了從產生消息到調用消息響應函數的全過程。其參數分別介紹如下。
pTarget:該參數是指向處理消息的對象。
nID:該參數是命令ID。
nCode:該參數是通知消息等,對於一個命令消息,該變量將賦值爲CN_COMMAND(相當於0)。
pfn:該參數是消息處理函數地址。
pExtra:該參數用於存放一些有用的信息,它取決於當前正被處理的消息類型。如果是控件通知WM_NOTIFY,則是指向NMHDR的AFX_NOTIFY結構的指針;如果是菜單項和工具欄更新,則它是指向CCmdUI派生類的指針;如果是其他類型,則爲空。
nSig:該參數定義消息處理函數的調用變量。在AFXMSG_.H中,爲nSig預定義了60多個值,例如,nSig值爲iww,則在調用消息處理函數前,使OnWndMsg()格式化wParam和lParam爲兩個UINT變量,返回值爲整型。
pHandlerInfo:該參數是一個指針,指向AFX_CMDHANDLERINFO結構。
前6個參數(除了pExtra以外)都是輸入參數,而參數pExtra和pHandlerInfo既可以用作輸出參數,也可以用作輸入參數。
該函數主要完成的任務是:首先,它檢查參數pHandlerInfo是否空,如果不空,則用pTarget和pfn填充它所指向的結構,並且返回 TRUE;其次,如果pHandlerInfo空,則進行消息處理函數的調用。它根據參數nSig的值,把參數pfn的類型轉換爲要調用的消息處理函數的 類型。
如果在視圖中沒有找到相應的消息處理函數,則將會交由文檔類來進行處理。

19. 執行我們的按鈕消息函數
void CD1Dlg::OnButton1() {   AfxMessageBox("OK");   }

注:
     MFC另外兩種常見的框架程序,SDI和MDI程序,其消息流程分析同此類似,其間過程大同小異,我就不再分析了。有興趣者,可自行分析,這裏只是提一下注意之點:
1.   對於SDI/MDI程序,最好在CWnd::OnCommand()函數入口處,設置斷點,然後跟進CCmdTarget::OnCmdMsg()函數,就很快找到消息函數地址了。
2.   若要在CCmdTarget::OnCmdMsg()函數入口處設置斷點。必須設置條件斷點,如[esp+4]==3e8, 這裏3e8是按鈕的 ID值。由於在SDI和MDI程序正常運行時,經常有文檔類,框架類消息從此流過,設置條件斷點後,會引起OD運行特別慢。所以,如果確實要在此處設置斷 點,注意:在需要設置斷點時再設置斷點,OD中斷後,馬上取消斷點!

附錄:MFC對Windows消息的截獲過程

MFC在調用對話框的DoModal函數之時,在PreModal內部調用了AfxHookWindowCreate()函數(對於SDI/MDI程序, 是在調用CWnd::CreateEx()函數裏面),這是一個安裝WH_CBT類型鉤子的函數。安裝WH_CBT類型的鉤子過程後,系統在下列情況下將 會首先調用此函數:激活窗口,創建或銷燬窗口,最大化或最小化窗口,移動或縮放窗口,完成一個系統命令,從系統的消息隊列裏移除一個鼠標或者鍵盤事件,設 置鍵盤,設置輸入焦點或者同步系統消息隊列事件等。安裝鉤子後,窗口的消息走向就要發生變化。
安裝構子函數AfxHookWindowCreate()源碼如下:
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,   _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
     if (pThreadState->m_hHookOldCbtFilter == NULL) AfxThrowMemoryException();
   }
   pThreadState->m_pWndInit = pWnd;
}

繼續觀察其中鉤子過程的地址_AfxCbtFilterHook的源碼,部分源碼如下:
LRESULT CALLBACK _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
{    _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
     if (code != HCBT_CREATEWND)
     {       // 等待HCBT_CREATEWND通過其他的鉤子
            return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code, wParam, lParam);
     }
     …
     if (!afxData.bWin4 && !bContextIsDLL && (pCtl3dState = _afxCtl3dState.GetDataNA()) != NULL &&   pCtl3dState->
m_pfnSubclassDlgEx != NULL &&    (dwFlags = AfxCallWndProc(pWndInit, hWnd, WM_QUERY3DCONTROLS)) != 0)
     {        // 窗口類註冊是用AfxWndProc進行的嗎?
             WNDPROC afxWndProc = AfxGetAfxWndProc();
             BOOL bAfxWndProc = ((WNDPROC)GetWindowLong(hWnd, GWL_WNDPROC) == afxWndProc);
             pCtl3dState->m_pfnSubclassDlgEx(hWnd, dwFlags); 
             if (!bAfxWndProc)     //如果還沒有編排到AfxWndProc則用AfxWndProc子類化窗口
             {                  //用標準的AfxWndProc子類化窗口
                    oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC,
                         (DWORD)afxWndProc);                  
                    *pOldWndProc = oldWndProc;      //保存原窗口過程
             }
     }
     …
}
從鉤子函數中可以看到,如果它檢測到註冊窗口類時,使用的窗口過程不是AfxWndProc函數,則它將用此函數替代,並且通過這個設置,在調用:: CreateWindowEx後,將原窗口過程保存在窗口類的成員變量m_pfnSuper中,這樣就形成了一個窗口過程鏈。在需要的時候,原窗口過程地 址可以通過窗口類成員函數GetSuperWndProcAddr獲得。

這樣,AfxWndProc就成爲CWnd或其派生類的窗口過程。不論隊列消息還是非隊列消息,都將首先送到AfxWndProc窗口過程進行處理。經過消息分發之後仍沒有被處理的消息,將送給原窗口過程處理。

註冊時所使用的窗口過程果真不是AfxWndProc,而是DefWindowProc,因此鉤子函數將完成這一替換任務。而調用函數AfxGetAfxWndProc所返回的結果正是函數AfxWndProc的地址,從其定義便可知這一事實:

WNDPROC AFXAPI   AfxGetAfxWndProc()
{
#ifdef _AFXDLL
       return AfxGetModuleState()->m_pfnAfxWndProc;
#else
       return &AfxWndProc;
#endif
}

至此,相信各位能夠明白DispatchMessage最終將消息發到了AfxWndProc函數裏,而非DefWindowProc裏。AfxWndProc的作用是截獲所有發送給窗口的消息(包括隊列消息和非隊列消息),所以實質上它是一個窗口消息分發器。

發佈了49 篇原創文章 · 獲贊 4 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章