mfc 消息隱射機(jī)制

一.引言
VC++的MFC類庫實(shí)際上是Windows下C++編程的一套最為流行的類庫。它合理的封裝了WIN32 API函數(shù),并設(shè)計(jì)了一套方便的消息映射機(jī)制。
二.SDK下的消息機(jī)制實(shí)現(xiàn)
Windows的消息都是和線程相對(duì)應(yīng)的。即Windows會(huì)把消息發(fā)送給和該消息相對(duì)應(yīng)的線程。在SDK的模式下,程序是通過GetMessage函數(shù)從和某個(gè)線程相對(duì)應(yīng)的消息隊(duì)列里面把消息取出來并放到一個(gè)特殊的結(jié)構(gòu)里面,一個(gè)消息的結(jié)構(gòu)是一個(gè)如下的STRUCTURE。

typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;

  • hwnd:和窗口過程相關(guān)的窗口的句柄
  • message:消息的ID號(hào)
  • wParam、lParam:表示和消息相關(guān)的參數(shù)
  • time:消息發(fā)送的時(shí)間
  • pt:消息發(fā)送時(shí)的鼠標(biāo)的位置
    TranslateMessage:把虛鍵消息翻譯成字符消息并放到響應(yīng)的消息隊(duì)列里面
    DispatchMessage:把消息分發(fā)到相關(guān)的窗口過程,然后窗口過程根據(jù)消息的類型對(duì)不同的消息進(jìn)行相關(guān)的處理;

三.MFC的消息實(shí)現(xiàn)機(jī)制
在MFC的框架結(jié)構(gòu)下,可以進(jìn)行消息處理的類的頭文件里面都會(huì)含有DECLARE_MESSAGE_MAP()宏,這里主要進(jìn)行消息映射和消息處理函數(shù)的聲明??梢赃M(jìn)行消息處理的類的實(shí)現(xiàn)文件里一般都含有如下的結(jié)構(gòu)。
下面主要進(jìn)行消息映射的實(shí)現(xiàn)和消息處理函數(shù)的實(shí)現(xiàn)。能夠進(jìn)行消息處理的類都是基于CCmdTarget類的,也就是說CCmdTarget類是所有可以進(jìn)行消息處理類的父類。CCmdTarget類是MFC處理命令消息的基礎(chǔ)和核心。
BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass)
//{{AFX_MSG_MAP(CInheritClass)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

MFC定義了下面的兩個(gè)主要結(jié)構(gòu):

  1. AFX_MSGMAP_ENTRY
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 nSig;       
// signature type (action) or pointer to message #
AFX_PMSG pfn;    // routine to call (or special value)
};
  1. AFX_MSGMAP_ENTRY結(jié)構(gòu)包含了一個(gè)消息的所有相關(guān)信息,其中:
  • nMessage為Windows消息的ID號(hào)
  • nCode為控制消息的通知碼
  • nID為Windows控制消息的ID
  • nLastID表示如果是一個(gè)指定范圍的消息被映射的話,nLastID用來表示它的范圍。
  • nSig表示消息的動(dòng)作標(biāo)識(shí)
  • AFX_PMSG pfn 它實(shí)際上是一個(gè)指向和該消息相應(yīng)的執(zhí)行函數(shù)的指針。
  struct AFX_MSGMAP
  {
    #ifdef _AFXDLL
    const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
    #else
    const AFX_MSGMAP* pBaseMap;
    #endif
    const AFX_MSGMAP_ENTRY* lpEntries;
  };

AFX_MSGMAP主要作用是兩個(gè):

  • 用來得到基類的消息映射入口地址。
  • 得到本身的消息映射入口地址。

MFC把所有的消息一條條填入到AFX_MSGMAP_ENTRY結(jié)構(gòu)中去,形成一個(gè)數(shù)組,該數(shù)組存放了所有的消息和與它們相關(guān)的參數(shù)。同時(shí)通過AFX_MSGMAP能得到該數(shù)組的首地址,同時(shí)得到基類的消息映射入口地址,這是為了當(dāng)本身對(duì)該消息不響應(yīng)的時(shí)候,就調(diào)用其基類的消息響應(yīng)。

MFC是如何讓窗口過程來處理消息的

  1. 所有MFC的窗口類都通過鉤子函數(shù)_AfxCbtFilterHook截獲消息,并且在鉤子函數(shù)_AfxCbtFilterHook中把窗口過程設(shè)定為AfxWndProc。原來的窗口過程保存在成員變量m_pfnSuper中
  2. 所以在MFC框架下,一般一個(gè)消息的處理過程
  • 函數(shù)AfxWndProc接收Windows操作系統(tǒng)發(fā)送的消息;
  • 函數(shù)AfxWndProc調(diào)用函數(shù)AfxCallWndProc進(jìn)行消息處理,這里一個(gè)進(jìn)步是把對(duì)句柄的操作轉(zhuǎn)換成對(duì)CWnd對(duì)象的操作。
  • 函數(shù)AfxCallWndProc調(diào)用CWnd類的方法WindowProc進(jìn)行消息處理。注意AfxWndProc和AfxCallWndProc都是AFX的API函數(shù)。而WindowProc已經(jīng)是CWnd的一個(gè)方法。所以可以注意到在WindowProc中已經(jīng)沒有關(guān)于句柄或者是CWnd的參數(shù)了。
  • 方法WindowProc調(diào)用方法OnWndMsg進(jìn)行正式的消息處理,即把消息派送到相關(guān)的方法中去處理。消息是如何派送的呢?實(shí)際上在CWnd類中都保存了一個(gè)AFX_MSGMAP的結(jié)構(gòu),而在AFX_MSGMAP結(jié)構(gòu)中保存有所有我們用ClassWizard生成的消息的數(shù)組的入口,我們把傳給OnWndMsg的message和數(shù)組中的所有的message進(jìn)行比較,找到匹配的那一個(gè)消息。實(shí)際上系統(tǒng)是通過函數(shù)AfxFindMessageEntry來實(shí)現(xiàn)的。找到了那個(gè)message,實(shí)際上我們就得到一個(gè)AFX_MSGMAP_ENTRY結(jié)構(gòu),而我們?cè)谏厦嬉呀?jīng)提到AFX_MSGMAP_ENTRY保存了和該消息相關(guān)的所有信息,其中主要的是消息的動(dòng)作標(biāo)識(shí)和跟消息相關(guān)的執(zhí)行函數(shù)。然后我們就可以根據(jù)消息的動(dòng)作標(biāo)識(shí)調(diào)用相關(guān)的執(zhí)行函數(shù),而這個(gè)執(zhí)行函數(shù)實(shí)際上就是通過ClassWizard在類實(shí)現(xiàn)中定義的一個(gè)方法。這樣就把消息的處理轉(zhuǎn)化到類中的一個(gè)方法的實(shí)現(xiàn)上。
  • 如果OnWndMsg方法沒有對(duì)消息進(jìn)行處理的話,就調(diào)用DefWindowProc對(duì)消息進(jìn)行處理。這是實(shí)際上是調(diào)用原來的窗口過程進(jìn)行缺省的消息處理。

所以如果正常的消息處理的話,MFC窗口類是完全脫離了原來的窗口過程,用自己的一套體系結(jié)構(gòu)實(shí)現(xiàn)消息的映射和處理。即先調(diào)用MFC窗口類掛上去的窗口過程,再調(diào)用原先的窗口過程。并且用戶面對(duì)和消息相關(guān)的參數(shù)不再是死板的wParam和lParam,而是和消息類型具體相關(guān)的參數(shù)。比如和消息WM_LbuttonDown相對(duì)應(yīng)的方法OnLButtonDown的兩個(gè)參數(shù)是nFlags和point。nFlags表示在按下鼠標(biāo)左鍵的時(shí)候是否有其他虛鍵按下,point更簡單,就是表示鼠標(biāo)的位置。

MFC窗口類消息傳遞中還提供了兩個(gè)函數(shù): WalkPreTranslateTree:、 PreTranslateMessage;他們是這樣工作的:

  1. 利用MFC框架生成的程序,都是從CWinApp開始執(zhí)行的,而CWinapp實(shí)際繼承了CWinThread類。在CWinThread的運(yùn)行過程中會(huì)調(diào)用窗口類中的WalkPreTranslateTree方法。而WalkPreTranslateTree方法實(shí)際上就是從當(dāng)前窗口開始查找愿意進(jìn)行消息翻譯的類,直到找到窗口沒有父類為止。
  2. 在WalkPreTranslateTree方法中調(diào)用了PreTranslateMessage方法。實(shí)際上PreTranslateMessage最大的好處是我們?cè)谙⑻幚砬翱梢栽谶@個(gè)方法里面先做一些事情。舉一個(gè)簡單的例子,比如我們希望在一個(gè)CEdit對(duì)象里,把所有的輸入的字母都以大寫的形式出現(xiàn)。我們只需要在PreTranslateMessage方法中判斷message是否為WM_CHAR,如果是的話,把wParam(表示鍵值)由小寫字母的值該為大寫字母的值就實(shí)現(xiàn)了這個(gè)功能。

三.MFC的消息響應(yīng)順序

  1. 幾個(gè)消息函數(shù)解釋
  • AfxWndProc:該函數(shù)負(fù)責(zé)接收消息,找到消息所屬的CWnd對(duì)象,然后調(diào)用AfxCallWndProc
  • AfxCallWndProc:該函數(shù)負(fù)責(zé)保存消息(保存的內(nèi)容主要是消息標(biāo)識(shí)符和消息參數(shù))供應(yīng)用程序以后使用,然后調(diào)用WindowProc()函數(shù)
  • WindowProc:該函數(shù)負(fù)責(zé)發(fā)送消息到OnWndMsg()函數(shù),如果未被處理,則調(diào)用DefWindowProc()函數(shù)
  • OnWndMsg:該函數(shù)的功能首先按字節(jié)對(duì)消息進(jìn)行排序,對(duì)于WM_COMMAND消息,調(diào)用OnCommand()消息 響應(yīng)函數(shù),對(duì)于WM_NOTIFY消息調(diào)用OnNotify()消息響應(yīng)函數(shù)。任何被遺漏的消息將是一個(gè)窗口消息。OnWndMsg()函數(shù)搜索類的消息映像,以找到一個(gè)能處理任何窗口消息的處理函數(shù)。如果OnWndMsg()函數(shù)不能找到這樣的處理函數(shù)的話,則把消息返回到WindowProc()函數(shù),由它將消息發(fā)送給DefWindowProc()函數(shù);
  • OnCommand:該函數(shù)查看這是不是一個(gè)控件通知(lParam參數(shù)不為NULL,如果lParam參數(shù)為空的話,說明該消息不是控件通知),如果它是,OnCommand()函數(shù)會(huì)試圖將消息映射到制造通知的控件;如果他不是一個(gè)控件通知(或者如果控件拒絕映射的消息)OnCommand()就會(huì)調(diào)用OnCmdMsg()函數(shù);
  • OnCmdMsg:根據(jù)接收消息的類,OnCmdMsg()函數(shù)將在一個(gè)稱為命令傳遞(Command Routing)的過程中潛在的傳遞命令消息和控件通知。
  1. 創(chuàng)建窗口的順序
  • PreCreateWindow() 該函數(shù)是一個(gè)重載函數(shù),在窗口被創(chuàng)建前,可以在該重載函數(shù)中改變創(chuàng)建參數(shù)
  • PreSubclassWindow() 這也是一個(gè)重載函數(shù),允許首先子分類一個(gè)窗口
  • OnGetMinMaxInfo() 該函數(shù)為消息響應(yīng)函數(shù),響應(yīng)的是WM_GETMINMAXINFO消息,允許設(shè)置窗口的最大或者最小尺寸
  • OnNcCreate() 該函數(shù)也是一個(gè)消息響應(yīng)函數(shù),響應(yīng)WM_NCCREATE消息,發(fā)送消息以告訴窗口的客戶區(qū)即將被創(chuàng)建
  • OnNcCalcSize() 該函數(shù)也是消息響應(yīng)函數(shù)響應(yīng)WM_NCCALCSIZE消息,作用是允許改變窗口客戶區(qū)大小
  • OnCreate() 該函數(shù)也是一個(gè)消息響應(yīng)函數(shù),響應(yīng)WM_CREATE消息,發(fā)送消息告訴一個(gè)窗口已經(jīng)被創(chuàng)建
  • OnSize() 該函數(shù)也是一個(gè)消息響應(yīng)函數(shù),響應(yīng)WM_SIZE消息,發(fā)送該消息以告訴該窗口大小已經(jīng)發(fā)生變化
  • OnMove() 消息響應(yīng)函數(shù),響應(yīng)WM_MOVE消息,發(fā)送此消息說明窗口在移動(dòng)
  • OnChildNotify() 該函數(shù)為重載函數(shù),作為部分消息映射被調(diào)用,告訴父窗口即將被告知一個(gè)窗口剛剛被創(chuàng)建
  1. 關(guān)閉窗口的順序(非模態(tài)窗口)
  • OnClose() 消息響應(yīng)函數(shù),響應(yīng)窗口的WM_CLOSE消息,當(dāng)關(guān)閉按鈕被單擊的時(shí)候發(fā)送此消息
  • OnDestroy() 消息響應(yīng)函數(shù),響應(yīng)窗口的WM_DESTROY消息,當(dāng)一個(gè)窗口將被銷毀時(shí),發(fā)送此消息
  • OnNcDestroy() 消息響應(yīng)函數(shù),響應(yīng)窗口的WM_NCDESTROY消息,當(dāng)一個(gè)窗口被銷毀后發(fā)送此消息
  • PostNcDestroy() 重載函數(shù),作為處理OnNcDestroy()函數(shù)的最后動(dòng)作,被CWnd調(diào)用;
  1. 打開模態(tài)對(duì)話框的順序
  • DoModal()是重載函數(shù),重載DoModal()成員函數(shù)。
  • PreSubclassWindow()也是重載函數(shù),允許首先子分類一個(gè)窗口。
  • OnCreate()是消息響應(yīng)函數(shù),響應(yīng)WM_CREATE消息,發(fā)送此消息以告訴一個(gè)窗口已經(jīng)被創(chuàng)建。
  • OnSize()也是消息響應(yīng)函數(shù),響應(yīng)WM_SIZE消息,發(fā)送此消息以告訴窗口大小發(fā)生變化。
  • OnMove()也是消息響應(yīng)函數(shù),響應(yīng)WM_MOVE消息,發(fā)送此消息以告訴窗口正在移動(dòng)。
  • OnSetFont()也是消息響應(yīng)函數(shù),響應(yīng)WM_SETFONT消息,發(fā)送此消息以允許改變對(duì)話框中控件的字體。
  • OnInitDialog()也是消息響應(yīng)函數(shù),響應(yīng)WM_INITDIALOG消息,發(fā)送此消息以允許初始化對(duì)話框中的控件或者創(chuàng)建新控件。
  • OnShowWindow()也是消息響應(yīng)函數(shù),響應(yīng)WM_SHOWWINDOW消息,該函數(shù)被ShowWindow()函數(shù)調(diào)用。
  • OnCtlColor()也是消息響應(yīng)函數(shù),響應(yīng)WM_CTLCOLOR消息,被父窗口發(fā)送已改變對(duì)話框或?qū)υ捒蛏厦婵丶念伾?/li>
  • OnChildNotify()是重載函數(shù),作為WM_CTLCOLOR消息的結(jié)果發(fā)送。
  1. 關(guān)閉模態(tài)對(duì)話框的順序
  • OnClose()是消息響應(yīng)函數(shù),響應(yīng)WM_CLOSE消息,當(dāng)"關(guān)閉"按鈕被單擊的時(shí)候,該函數(shù)被調(diào)用。
  • OnKillFocus()也是消息響應(yīng)函數(shù),響應(yīng)WM_KILLFOCUS消息,當(dāng)一個(gè)窗口即將失去鍵盤輸入焦點(diǎn)以前被發(fā)送。
  • OnDestroy()也是消息響應(yīng)函數(shù),響應(yīng)WM_DESTROY消息,當(dāng)一個(gè)窗口即將被銷毀時(shí),被發(fā)送。
  • OnNcDestroy()也是消息響應(yīng)函數(shù),響應(yīng)WM_NCDESTROY消息,當(dāng)一個(gè)窗口被銷毀以后被發(fā)送。
  • PostNcDestroy()也是重載函數(shù),作為處理OnNcDestroy()函數(shù)的最后動(dòng)作被CWnd調(diào)用。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容