MFC的消息映射机制
添加消息处理函数不管是自动还是手动添加都有三个步骤:
1.在类定义中加入消息处理函数的函数声明,以afx_msg开头。例如MainFrm.h中WM_CREATE的消息处理函数的函数声明:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
2.在类的消息映射表中添加该消息的消息映射入口项。例如WM_CREATE的消息映射入口项:ON_WM_CREATE()。
3.在类实现中添加消息处理函数的函数实现。例如,MainFrm.cpp中WM_CREATE的消息处理函数的实现:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
......
}
通过以上三个步骤以后,WM_CREATE等消息就可以在窗口类中被消息处理函数处理了。
消息映射除了在CMainFrame的实现文件中添加消息映射表外,在类的定义文件MainFrm.h中还会添加一个宏调用:
DECLARE_MESSAGE_MAP()
一般这个宏调用写在类定义的结尾处。
在MSDN中查看DECLARE_MESSAGE_MAP()
,给出的解释是:
只要是CcmdTarget派生出来的类必须提供消息映射来处理消息,所以使用DECLARE_MESSAGE_MAP()
在类中进行声明。
// example for DECLARE_MESSAGE_MAP
class CMyWnd : public CFrameWnd
{
//
Member declarations
DECLARE_MESSAGE_MAP( )
};
然后在cpp文件中定义类的成员函数,使用BEGIN_MESSAGE_MAP
和END_MESSAGE_MAP
组成消息映射表
// example for BEGIN_MESSAGE_MAP
BEGIN_MESSAGE_MAP( CMyWindow, CFrameWnd )
ON_WM_LBUTTONDOWN() //消息映射表
ON_COMMAND( IDM_ABOUT, OnAbout )
END_MESSAGE_MAP( )
在BEGIN_MESSAG_MAP
和END_MESSAGE_MAP
之间的内容成为消息映射入口项。
消息映射表中的ON_WM_LBUTTONDOWN()
,这个消息映射宏的作用就是把鼠标左键按下消息(WM_LBUTTONDOWN)与一个消息响应函数关联起来(就是把WM_LBUTTONDOWN消息与OnLButtonDown函数关联起来)。通过这种机制,一旦有消息产生,程序就会调用相应的消息响应函数来进行处理。
所以,经过以上分析可以知道,一个MFC消息响应函数在程序中有三处相关信息:
- 消息处理函数的函数声明
- 用来关联消息和消息响应函数的宏
- 消息处理函数的函数实现。
在Windows中,当有消息产生时,操作系统会把这条消息放到应用程序的消息队列,应用程序通过GetMessage函数从这个对列中取出一条具体的消息,并通过DispatchMessage函数把消息交给操作系统,然后操作系统调用程序的窗口过程,即窗口过程函数WinProc函数进行处理。该函数利用Switch-Case结构来对消息进行判别并分类处理。
然而在MFC中,并不是按照这种途径进行处理,主需要按照前面介绍的,定义三处信息之后,就可以实现消息的响应处理。MFC中采用的这种消息处理机制称为MFC消息映射机制。
MFC消息映射机制的具体实现方法是:在每个能接受和处理消息的类中,定义一个消息和消息函数静态对照表(消息映射表)。在消息映射表中,消息与消息对应的消息处理函数指针是成对出现的。某个类能处理的所有消息及其对应的消息处理函数的地址都列在这个类所对应的消息映射表中。当有消息需要处理时,程序只需要搜索该消息映射表,查看表中是否有该消息,就可知道该类能否处理次消息。如果能处理该消息,则同样依照消息映射表找到并调用对应的消息处理函数。
MFC消息映射机制的实际实现过程
MFC在后台维护了一个窗口句柄与对应的对象指针的对应表。
假设在Cview类创建了它的子类CDrawView类,与CDrawView类对象相关的有一个窗口,窗口当然有它的句柄,该句柄与CDrawView类对象的一个指针(即CdrawView *)存在一一对应的关系。
当收到某一消息时,消息的第一个参数就指明该消息与哪个窗口句柄有关,通过这种对应关系,就可以找到与之相关的对象指针。然后把这个指针传递给应用程序框架类窗口的基类,基类会调用一个WindowProc的函数。该函数定义在WinCore.cpp中。
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;
}
WindowProc函数为一个虚函数,同时在函数内部调用了一个OnWndMsg函数,真正的消息路由也就是由此函数完成的。
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;
}
........
}
OnWndMsg函数的处理过程是:首先判断是否有消息响应函数。
判断方法是在相应的窗口类中查找所需的消息响应函数。因为传递给WindowProc函数的是窗口子类的指针,所以OnWndMsg函数会到相应的子类头文件中去查找,看看DECLARE_MESSAGE_MAP()
宏之上,afx_msg
宏之后是否有相应的消息响应函数的声明;再到子类的源文件中,看看BEGIN_MESSAGE_MAP()
和END_MESAGE_MAP()
这两个宏之间是否有相应的消息映射宏。
如果通过上述步骤找到了消息响应函数,那么就会接着调用此函数来对消息进行处理。
如果没有找到,那么则就交由子类处理。
通过以上步骤,MFC就实现了具体的消息映射,从而完成对消息的响应。
简单来说就是:程序中产生一个消息,该消息传递的参数会标明是由哪个窗口产生的,由一一对应的关系会找到该窗口类对象的指针,然后将指针传递给基类,基类调用WinProc函数,WinProc函数会调用OnWndMsg函数,而此函数会在指针指向的那个子类中的头文件和源文件去寻找消息响应的函数。如果有,则由此函数处理该消息,否则将由基类处理。