消息映射初見
我們新建一個Draw單文檔工程后,通過在DrawView類中添加一個“鼠標左鍵按下的消息”進行消息映射機制的分析,在分析之前,先
完成“WM_LButtonDown”消息響應函數的添加。
添加步驟:
1.在ClassView中找到DrawView類,點擊鼠標右鍵,點擊“屬性”按鈕,這時會彈出DrawView類的“屬性配置界面”。
2.在“屬性配置界面中”,點擊“消息圖標”->找到“WM_LButtonDown”->下拉選項->點擊"添加OnLButtonDown",這時在工程中會自動增加三處代碼。
(1)消息響應函數原型
在CDraw類的頭文件中,能找到OnLButtonDown的函數聲明,其中afx_msg是限定符,表明是消息響應函數。
class CDrawView : public CView{protected: // 僅從序列化創建CDrawView();DECLARE_DYNCREATE(CDrawView).....protected:// 生成的消息映射函數protected:DECLARE_MESSAGE_MAP()public: afx_msg void OnLButtonDown(UINT nFlags, CPoint point);//新增增加代碼};
(2)ON_WM_LBUTTONDOWN消息映射宏
在DrawView.cpp的文件開頭能找到ON_WM_LBUTTONDOWN這樣的宏,代碼如下:
BEGIN_MESSAGE_MAP(CDrawView, CView)// 標準打印命令 ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview) ON_WM_LBUTTONDOWN()//新增加代碼END_MESSAGE_MAP()
(3)消息響應函數的定義
在CDrawView.cpp源文件中,可以看到OnLButtonDown函數的定義,如下代碼段。
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point){ // TODO: 在此添加消息處理程序代碼和/或調用默認值 CView::OnLButtonDown(nFlags, point);}
經過以上分析,可以知道,一個MFC消息響應函數在程序中有三處相關信息:消息響應函數原型、消息響應函數定義以及用來關系
消息和消息響應函數的宏。
消息路由實現方式
實際上,消息路由可以有多重方式,其中一種可能的方式就是,在基類中針對每種消息定義一個虛函數。當子類需要對某個消息進
行處理,則重寫基類相應的虛函數即可。根據多態性原理,如果子類重寫了該函數,就調用子類的這個函數,否則調用父類的相應
函數,從原理是可行的,但從編程角度看是不可以取的。
為什么不可取呢?因為虛函數必須由一個“虛函數表”來實現。在應用程序中使用的每個派生類,系統都需要為它們分配一個
vtable(相關內容見“c++虛函數表解析”)。不管基類中虛函數釋是否被重寫,每個派生類都要背負一個很大的虛函數表。我們知道
MFC的派生層次很深,并且消息種類也很多,如果采用虛函數的實現方式,這是對內存資源的也很大的損耗,這么多的虛函數表,
查找對應的響應函數,效率也很低。因此MFC采用更加高級的“消息映射機制”。
消息映射機制的實現
在win32 SDK程序中,通過消息循環完成消息獲取、通過窗口過程函數完成消息的響應,在窗口過程函數中通switch-case的方式,
完成對每種消息具體操作。在MFC程序中,只要添加如上三處有關消息的步驟之后,就可以實現消息的響應,這種機制稱之為:
MFC消息映射機制。
MFC消息映射機制的具體實現方法是:在每個能接收和處理消息的類中,定義一個消息和消息函數靜態對照表,即消息映射表,在
消息映射表中,消息和消息處理函數指針是成對出現的。某個類能處理的所有消息及其對應的消息處理函數的地址都列在這個類的
靜態表中。當有消息需要處理時,程序只需要搜索該靜態表,查看表中是否包含該消息,就可以知道該類能否處理該消息了。
BEGIN_MESSAGE_MAP(CDrawView, CView)// 標準打印命令ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview) ON_WM_LBUTTONDOWN()END_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[] = \{
//消息和消息響應函數映射宏,這些宏展開都是AFX_MSGMAP_ENTRY 結構,具體代碼如下:
#define ON_COMMAND(id, memberFxn) \{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, \static_cast (memberFxn) },// ON_COMMAND(id, OnBar) is the same as// ON_CONTROL(0, id, OnBar) or ON_BN_CLICKED(0, id, OnBar)#define ON_COMMAND_RANGE(id, idLast, memberFxn) \{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)idLast, AfxSigCmd_RANGE, \(AFX_PMSG) \(static_cast< void (AFX_MSG_CALL CCmdTarget::*)(UINT) > \(memberFxn)) },#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)) },struct AFX_MSGMAP_ENTRY
//結束標志
#define END_MESSAGE_MAP() \{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ }; \static const AFX_MSGMAP messageMap = \{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \return &messageMap; \}
其中staticconstAFX_MSGMAP_ENTRY_messageEntries[]={}就是這個類的靜態表,{0,0,0,0,AfxSig_end,(AFX_PMSG)0}這個是靜態表的結束標志。ON_COMMAND(id, memberFxn)、ON_WM_LBUTTONDOWN()等展開后都是AFX_MSGMAP_ENTRY類型,都是_messageEntries的成員。因此想要添加其他的消息和消息響應函數只要按照三部曲“消息響應原型聲明、消息響應函數定義、消息和消息響應函數義”操作即可完成MFC的消息映射。
struct AFX_MSGMAP_ENTRY{UINT nMessage; // windows messageUINT nCode; // control code or WM_NOTIFY codeUINT nID; // control ID (or 0 for windows messages)UINT nLastID; // used for entries specifying a range of control id'sUINT_PTR nSig; // signature type (action) or pointer to message #AFX_PMSG pfn; // routine to call (or special value)};typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
消息響應函數會被統一轉換成無參數、無返回值的AFX_PMSG類型儲存起來。原有的返回值、參數信息通過標識符標唯一保存,即
nSig參數。而后通過標識符再被還原回去,可能的標識符由enum AfxSig給出,被定義在afxmsg_.h中。
消息路由過程
在WinCore.cpp文件中定義了一個WindowProc函數,該函數會調用一個OnWndMsg函數,這個函數完成了MFC的消息路由功能,即
消息映射,相關代碼如下:
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;}
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;}// special case for notifiesif (message == WM_NOTIFY){NMHDR* pNMHDR = (NMHDR*)lParam;if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))goto LReturnTrue;return FALSE;}...LDispatch:ASSERT(message < 0xC000);mmf.pfn = lpEntry->pfn;switch (lpEntry->nSig){default:ASSERT(FALSE);break;case AfxSig_l_p://消息響應函數還原{CPoint point(lParam);lResult = (this->*mmf.pfn_l_p)(point);break;}
OnWndMsg函數處理過程是:
1.判斷消息是否有消息響應函數.判斷方法是在相應的窗口類中查找所需要的消息響應函數,首先判斷是否有相應的函數聲明,其次
在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP這個兩個宏之間查找是否有相應的消息映射宏。
2.通過上述步驟,如果找到了這個消息響應函數,那么就調用該響應函數,對消息進行處理。
3.如果在子類中沒有找到消息響應函數,那么就交個基類進行處理。
通過以上步驟,MFC就實現了具體的消息映射,完成對消息的響應。
看文倉www.kanwencang.com網友整理上傳,為您提供最全的知識大全,期待您的分享,轉載請注明出處。
歡迎轉載:http://www.kanwencang.com/bangong/20161027/10942.html
文章列表