——基于《深入浅出MFC》第九章&孙鑫20讲第六讲
当我在第一次看《深入浅出MFC》的时候,很多东西都是似懂非懂,这种感觉一直持续到我有机会结合孙鑫老师的VC20讲一起看,孙鑫老师的20讲,简直就是《深入浅出MFC》的视频教程,这二者的结合真是太完美了!而且我一直一来看书的习惯就是博采众家之长而为我所用。这一次的学习过程从某种意义上讲,的确是一种享受。
既然MFC在本质上是以消息为基础的,在大致了解了MFC的消息机制之后,我觉得有必要深入理解MFC的消息映射与命令传递机制。
一、消息分类:
1、标准消息:
除WM_COMMAMD之外,所有以WM_开头的消息,凡派生自CWnd的类,可接收该类消息。
对于标准消息,可以在某个窗口类
2、命令消息:
WM_COMMAND称为命令消息,由菜单、加速键(快捷键)、工具栏按钮产生,由消息的wParam标识命令消息来自哪儿,凡派生自CCmdTarget的类,可接收该类消息。
3、通告消息:
由控件产生,向父窗口(对话框)通知发生的事件,该类消息也是以WM_COMMAND形式呈现的,凡派生自CCmdTarget的类,可接收该类消息。
本来,消息对于我们来说是一个非常简单的,只是我们对其分类与处理没有一个明确的界定罢了,而到了现在,或许唯一的障碍来自我们尚不清楚消息是怎样被映射到函数的,难道是上帝之手吗?
二、消息映射:
1、DECLARE_MESSAGE_MAP():
既然消息在诸多类中被接收并处理,我们是否应该在类中事先“声明”呢?是的,我们当然必须在类的声明文件(.H)中声明,只不过我们声明的不是什么消息和函数,而是消息映射表格。刚开始的时候,我曾想过用virtual函数吧!但是MFC的消息有上百种之多,哪些消息需要,哪些消息不需要,这种问题,因时而易,MS也说不清楚,况且,经过若干次的继承,这样一个虚函数表的大小想必也是非常可观了。提到精简,为什么不想到静态函数呢?于是,MS发明了消息映射表格,下面是一个消息映射表格的例子:
//{{AFX_MSG(CWin_DrawView)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
//{{和//}}中间就是所谓的注释宏,每一项由消息对应宏名和处理函数构成。通过其后的DECLARE_MESSAGE_MAP()把类中需要处理的多种消息和类联系在一起。
DECLARE_MESSAGE_MAP()的定义:
<AFXWIN.H>
#define DECLARE_MESSAGE_MAP() \
private: \
static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
static AFX_DATA const AFX_MSGMAP messageMap; \
static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); \
virtual const AFX_MSGMAP* GetMessageMap() const; \
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)
};
AFX_MSGMAP的定义:
struct AFX_MSGMAP
{
const AFX_MSGMAP* pBaseMap;
const AFX_MSGMAP_ENTRY* lpEntries;
};
2、BEGIN_MESSAGE_MAP与END_MESSAGE_MAP()
声明过后,现在就要在类的应用程序文件(.CPP)中加以实现了。下面几个宏比较easy:
<AFXWIN.H>
BEGIN_MESSAGE_MAP(theClass, baseClass)的定义:
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return &theClass::messageMap; } \
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
{ &baseClass::messageMap, &theClass::_messageEntries[0] }; \
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
{ \
END_MESSAGE_MAP()的定义:
#define END_MESSAGE_MAP() \
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
}; \
eg.
BEGIN_MESSAGE_MAP(CWin_DrawView, CView)
//{{AFX_MSG_MAP(CWin_DrawView)
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
在这儿,还有一点需要说明的应该是,并不是所有派生自CCmdTarget的类都有DECLARE/BEGIN/END宏组,CWinThread没有,到现在为止,我还不清楚这个CWinThread到底有多么重要,只是记得它在AfxWinMain中用到过(线程嘛!肯定要用了!)。但我知道,有一个很重要的类CWinApp是派生自CWinThread的,所以,对于CWinApp,其BEGIN_MESSAGE_MAP的baseClass变成了CCmdTarget,有点乱伦,但尚可忍受。
三、消息捕获:
同样是在AftWinMain中,在调用pApp->InitApplication()和pThread->InitInstance()之前调用了AfxWinInit(hInstance,hPrevInstance, lpCmdLine,nCmdShow),在这个函数中,注册了四个窗口类,它们具有相同的处理函数AfxWndProc。MFX利用hook(传说中的钩子吧)抓取消息,送往滤网函数(filer),这个过程对于目前的我的确有点高深,而且并不真正影响我对消息捕获的理解。所以,我可以暂时认为消息被钩子钩出来了,就这么简单……
四、消息传递:
既然各窗口的消息均由AfxWndProc处理,我想要想知道消息是怎样被传递的,只须继续跟踪这个函数就是了。
1、<WINCORE.CPP>:LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
......
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
......
if (pWnd == NULL || pWnd->m_hWnd != hWnd) // 当窗口指针无法创建或不是当前消息捕获窗口时,调用默认窗口过程函数
return ::DefWindowProc(hWnd, nMsg, wParam, lParam);
return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}
2、<WINCORE.CPP>:LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
WPARAM wParam = 0, LPARAM lParam = 0)
{
......
LRESULT lResult;
......
lResult = pWnd->WindowProc(nMsg, wParam, lParam); // 看来这才是真正的窗口过程函数
......
return lResult;
}
3、<WINCORE.CPP>:LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = 0;
if (!OnWndMsg(message, wParam, lParam, &lResult)) // 分辨并处理消息
lResult = DefWindowProc(message, wParam, lParam);
return lResult;
}
4、<WINCORE.CPP>:BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
if (message == WM_COMMAND) // 处理命令消息
{
if (OnCommand(wParam, lParam))
......
}
// special case for notifies
if (message == WM_NOTIFY) // 处理通告消息
{
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
......
}
// special case for activation
if (message == WM_ACTIVATE) // 处理各种标准消息
_AfxHandleActivate(this, wParam, CWnd::FromHandle((HWND)lParam));
......
AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash];
......
if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap)
}
这个函数的工作就是反复比较消息映射表和Cache,是的,的确是Cache,这是我第一次在教科书之外如此真实的接触它,但是MS还是为我们隐藏了实现的细节。所有标准消息在OnWndMsg中被传递给对应处理函数,而命令消息和通告消息则被分别交给了OnCommand和OnNotify,这样两个函数都是虚函数,在CWnd的派生类中将其改写。
5、我现在急于知道的另一问题是:当多个类都建立了对同一个消息的响应函数时,其响应的优先顺序是怎么样的呢?假设消息被CFrameWnd获得,来看一下CFrameWnd改写的OnCmdMsg:
<WINFRM.CPP>:BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{
......
// route as normal command
return CWnd::OnCommand(wParam, lParam);
}
<WINCORE.CPP>:BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{
......
return OnCmdMsg(nID, nCode, NULL, NULL); // CWnd::OnCmdMsg继承自CCmdTarget而未改写它
}
<CMDTARG.CPP>:BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult)
{
......
return OnCmdMsg(nID, MAKELONG(nCode, WM_NOTIFY), ¬ify, NULL);
}
<CMDTARG.CPP>:BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
......
for (pMessageMap = GetMessageMap(); pMessageMap != NULL;
pMessageMap = pMessageMap->pBaseMap)
{
lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID);
if (lpEntry != NULL)
{
// found it
return _AfxDispatchCmdMsg(this, nID, nCode,
lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);
}
}
return FALSE; // not handled
}
<WINFRM.CPP>:BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
CPushRoutingFrame push(this);
// pump through current view FIRST
CView* pView = GetActiveView();
if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
// then pump through frame
if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
// last but not least, pump through app
CWinApp* pApp = AfxGetApp();
if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
return FALSE;
}
<VIEWCORE.CPP>:BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
// first pump through pane
if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
// then pump through document
if (m_pDocument != NULL)
{
// special state for saving view before routing to document
CPushRoutingView push(this);
return m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
return FALSE;
}
由此可见,同一个消息虽可被不同类捕获,但各个类响应优先次序应该是:CView、CDocument、CMainFrame、CWinApp,当一个消息产生后,遵循这一传递路线,直到消息被处理或最终传给CWinApp,由DefWindowProc处理。
五、盖棺论定:
当我们继续跟踪,我们又会发现在OnWndMsg对标准消息的处理、OnCmdMsg对WM_COMMAND和WM_NOTIFY的处理中,所有消息又回到了一起,它们有一个共同的前缀AfxSig_,侯俊杰称之为“罗塞达碑石”,换言之,揭开消息映射最后谜底的关键吧!对于我这样的初学者,我只是发现到了这是,我再也不想去跟踪接下来的union MessageMapFunction和enum AfxSig了!The fuckingMESSAGE MAP. Drop dead!
Trackback: http://tb.donews.net/TrackBack.aspx?PostId=920571