MFC原理第五講.消息映射.以及如何添加消息
一丶消息映射是什么
我們知道.Win32程序.都是通過消息去驅動的. 不斷的在處理消息. 只要我們使用固定的宏.就可以讓我們的框架知道一旦消息發生.該往哪一個類傳遞. 每一個類可以擁有一個映射表格.
也可以沒有.
關鍵宏
1. DECLARE_MESSAGE_MAP 聲明宏.放在類中
2. BEGIN_MESSAGE_MAP 實現宏放在類實現外
3. END_MESSAGE_MAP 實現宏放在類外面
如何添加消息.
如果我們添加了 BEGIN 跟 END 兩個宏之后. 我們在中間添加他們的消息就可以.
例如:
BEGIN_MESSAGE_MAP(CMainWnd,CFrameWnd) //兩個參數.第一個是自己的類.第二個是父類.
ON_WM_LBUTTONDOWN() 我們的消息. 需要添加聲明以及實現.
END_MESSAGE_MAP()
我們的消息.MFC都給我們封裝好了.如果實現消息. 則一律 ON_WM開頭. 消息 是WM_XXX.
我們可以在 afxmsg.h 中查看.
我們在類中聲明消息.並且添加消息處理函數即可.
例如以下代碼:
上面是聲明.下面是實現.
應用程序截圖
二丶消息映射原理分析
不管學習任何東西. 先學會用.再去學習原理.這樣是最快的.
現在對我們的宏進行拆分來看.
1.DECLARE_MESSAGE_MAP 宏查看.
#define DECLARE_MESSAGE_MAP() \ protected: \ static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \ virtual const AFX_MSGMAP* GetMessageMap() const; \
這個宏一幕了然. 添加了兩個方法.一個是虛方法.
那么這個是聲明宏.那么另外兩個應該就有實現宏了.看下實現宏的拆解.
2.BEGIN_MESSAGE_MAP(字節類名.父類名)
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \ PTM_WARNING_DISABLE \ const AFX_MSGMAP* theClass::GetMessageMap() const \ { return GetThisMessageMap(); } \ const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \ { \ typedef theClass ThisClass; \ typedef baseClass TheBaseClass; \ static const AFX_MSGMAP_ENTRY _messageEntries[] = \ {
可以看得出.拆解開是一個一半的東西. 這里需要添加我們的消息了.所以下面是END_MESSAGE_MAP
所以兩個一起解析.
const AFX_MSGMAP* CMainWnd::GetMessageMap() const { return GetThisMessageMap(); } const AFX_MSGMAP* PASCAL CMainWnd::GetThisMessageMap() { typedef CMainWnd ThisClass; typedef CFrameWnd TheCFrameWnd; static const AFX_MSGMAP_ENTRY _messageEntries[] = { //這里添加我們的消息 {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } //END_MESSAGE_MAP 宏.下面都是. }; static const AFX_MSGMAP messageMap = { &TheCFrameWnd::GetThisMessageMap, &_messageEntries[0] }; return &messageMap; }
可以看出.這兩個宏就是對聲明的兩個宏的實現. 而上面我們添加的ON_WM_LBUTTONDOWN 就是放在 END_MESSAGE_MAP 上面的. 我們看一下格式.
const AFX_MSGMAP* CMainWnd::GetMessageMap() const { return GetThisMessageMap(); } const AFX_MSGMAP* PASCAL CMainWnd::GetThisMessageMap() { typedef CMainWnd ThisClass; typedef CFrameWnd TheCFrameWnd; static const AFX_MSGMAP_ENTRY _messageEntries[] = { //這里添加我們的消息 ON_WM_LBUTTONDOWN() //我們要解析這個消息 {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } }; static const AFX_MSGMAP messageMap = { &TheCFrameWnd::GetThisMessageMap, &_messageEntries[0] }; return &messageMap; }
#define ON_WM_LBUTTONDOWN() \ { WM_LBUTTONDOWN, 0, 0, 0, AfxSig_vwp, \ (AFX_PMSG)(AFX_PMSGW) \ (static_cast< void (AFX_MSG_CALL CWnd::*)(UINT, CPoint) > ( &ThisClass :: OnLButtonDown)) },
其實上面可以看出.其實是一個 _messageEntries數組.保存着我們的消息. 我們替換這個宏看一下.
{ WM_LBUTTONDOWN, 0, 0, 0, AfxSig_vwp, (AFX_PMSG)(AFX_PMSGW)(static_cast< void (AFX_MSG_CALL CWnd::*)(UINT, CPoint) > ( &ThisClass :: OnLButtonDown)) },
解析完成可以看到.第一個是 WM_LBUTTONDOWN 對應着他的處理函數 OnLButtonDown 也就是最后一個參數.
所以現在明白了我們添加消息的時候.為什么要按照規定的格式. 因為這個宏已經使消息一一對應了.
因為上面是一個結構體數組用來保存.所以我們可以看一下這個數組是什么格式的.
struct AFX_MSGMAP_ENTRY { UINT nMessage; // windows message UINT nCode; // control code or WM_NOTIFY code UINT nID; // control ID (or 0 for windows messages) UINT nLastID; // used for entries specifying a range of control id's UINT_PTR nSig; // signature type (action) or pointer to message # AFX_PMSG pfn; // routine to call (or special value) };
注釋也給我們寫的很清楚了. 消息. 消息控制代碼.什么消息類型 控制ID pfn函數的返回值類型 pfn 消息處理對應函數.
但是我們要具體解釋一下. 第5個跟第6個參數.
第五個參數指明了我們函數的返回值
比如上圖:
AfxSig_vwp 類型. 我們可以點擊F12取頭文件中查看.例如下圖:
所以我們知道了我們的函數定義的返回以及參數類型了.
我是按照VS2015下的MFC講解. 如果是VC6.0下. 那么 需要添加三個成員方法. 不過實現是類似了. 具體可以查看VC60的MFC源碼.
根據VS2015給我們添加了兩個成員方法. 可以看到返回值也是一個結構體.那么看下這個結構體內容吧.
struct AFX_MSGMAP { const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)(); 父類的MAP const AFX_MSGMAP_ENTRY* lpEntries; 存放自己當前的消息結構. };
上面總結一下.
1.首先聲明宏會為我們添加兩個方法. 獲取消息映射表
2.實現宏則實現這兩個方法. 而且還有兩個結構. 一個是存放消息信息的結構. 另一個結構則是存放父類的MessageMap .以及自己當前存放信息的結構
那么知道了消息映射表.我們可以在按鈕點擊的時候. 按一下打印出我們消息的時候對應的地址了. 這個對於逆向很有幫助. 學到最后我們可以寫一個工具. 可以有很好的切入點了.
具體代碼如下:
void CMainWnd::OnMyLButtonDown(UINT flag, CPoint point) //知道了結構我們自己改也可以. { //獲取消息映射表.看看消息傳遞. const AFX_MSGMAP * pMsg = GetMessageMap(); //打印出自己當前按鈕點擊的時候的地址. CString str; str.Format(TEXT("地址 = %p\r\n"), pMsg->lpEntries->pfn); 從消息映射表中獲取我們的函數地址. 其實應該遍歷.並且判斷是否是消息.這里直接就偷懶了.因為只有一個消息. OutputDebugString(str); AfxMessageBox(TEXT("OnLButtonDown")); }
應用程序截圖:
很方便的看到了地址. 我們可以使用逆向工具 到這個地址查看.看看是否是我們點擊的時候的消息.
因為是Dbg版本.所以有Jmp跳轉的一層.我們直接跳轉進去查看.
可以很簡單的看到.就是我們編寫的代碼的位置.
下一講講解消息傳遞.