MFC之訊息對映機制實現方法
訊息對映初見
我們新建一個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<AFX_PMSG> (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; \
}
其中staticconst AFX_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 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)
};
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 call
LRESULT 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 commands
if (message == WM_COMMAND)
{
if (OnCommand(wParam, lParam))
{
lResult = 1;
goto LReturnTrue;
}
return FALSE;
}
// special case for notifies
if (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就實現了具體的訊息對映,完成對訊息的響應。