MFC真可谓是件艺术品,不过刚开始学的时候一头雾水,竟不相信MFC是用C++的写出来的,最近有时间深入学习了MFC的消息映射,感觉收获不小,还是把学习成果记下来比较好。
首先看看MFC的窗口类(如CWnd)与窗口过程。
Windows是基于事件机制的,任何窗口都能发送和处理消息,每一个窗口都对应着自己的消息处理函数,即通常所说的窗口过程(WindowProc)。 窗口过程通常是在WNDCLASSEX的lpfnWndProc变量中指定的,然后调用RegisterClassEx注册窗口 类,lpfnWndProc要求是全局的或是类的静态成员,而MFC的窗口和类对象是一一对应的,在类中定义的窗口过程 (CWnd::WindowProc)并非类的静态成员,那么窗口消息是怎样传给窗口对象的WindowProc函数去处理的呢?MFC中定义了一个全局 的AfxWndProc函数,AfxWndProc是MFC中所有的窗口共用的窗口过程。这里要注意在AfxEndDeferRegisterClass中 注册窗口类时并没有把AfxWndProc赋给lpfnWndProc,而是把DefWindowProc赋给了lpfnWndProc。真正把 AfxWndProc指定为窗口过程的是在CWnd::CreateEx函数中,CWnd::CreateEx中先后调用了 SetWindowsHookEx、CreateWindowEx和UnhookWindowsHookEx,SetWindowsHookEx安装了一 个WH_CBT类型的钩子,在调用CreateWindowEx时(在CreateWindowEx返回之前)窗口会发送WM_CREATE、 WM_NCCREATE等消息,钩子过程CBTProc会在窗口消息WM_CREATE、 WM_NCCREATE等发送前被调用,并提前得到窗口的句柄值。钩子过程CBTProc的任务是把窗口句柄赋给窗口对象(CWnd::m_hWnd), 并调用SetWindowLong把窗口过程替换成AfxWndProc(如是控件还要保留原窗口过程,用CallWindowProc进行默认处理)。 在这有人可能会问,为什么不在AfxEndDeferRegisterClass中直接指定AfxWndProc呢?当然是有原因的:其一是控件的窗口过程必须用SetWindowLong来替换,其二是消息WM_CREATE、 WM_NCCREATE等是在CreateWindowEx返回前发送的,CWnd::WindowProc在处理这些消息时CWnd::m_hWnd必须是已经被初始化的,这个就是由前面的CBTProc完成的。
好现在我们只要关注AfxWndProc了。AfxWndProc是如何把消息分配给各个窗口对象的窗口过程的呢?在MFC中有一个全局的映射表(还没到 消息映射,呵呵),这个表是窗口句柄到窗口对象的映射(即通过窗口句柄就能找到窗口对象的地址),找到了窗口对象就可以把消息处理的任务交给 CWnd::WindowProc了(调用pWnd->WindowProc)。
下面就是消息映射了
其实这就简单了,因为这时只需关注CWnd::WindowProc和消处理函数(如onCreate)了。在MFC中定义了几个 宏:DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP等,其实把这几个宏换回来就很好理解 了。为了便于理解,我把这些宏简化一下:
//
typedef struct _MSGMAP_ENTRY
{
UINT nMessage; //消息
void (CWnd::*pfn)(); //消息处理函数据
}MSGMAP_ENTRY;
DECLARE_MESSAGE_MAP相当于
static MSGMAP_ENTRY _MessageEntry[]; //定义了一个映射表
BEGIN_MESSAGE_MAP、END_MESSAGE_MAP和两者之间的宏相当于
MSGMAP_ENTRY CWnd::_MessageEntry[] =
{
{WM_CREATE, &onCreate}, //第一个消息映射
{WM_CLOSE, &onClose}, //第二个消息映射
{0, 0} //消息映射结尾
};
CWnd::WindowProc之不过是在_MessageEntry[]查找有没有定义的消息,如有,则调用相应的处理函数,如没有则调用CWnd::DefWindowProc
还想提一下Delphi中的相关处理,Delphi是不是用了同样的方法呢?答案是否定的,Delphi用汇编语句把类的非静态成员函数的地址赋给lpfnWndProc,这个也很有意思,当用用C++也可这么做。