首先定义一个名词:向上匹配。对于 除WM_COMMAND以外的消息,在消息网中都只能是从派生类流向基类。派生类及基类都有自己的消息映射表AFX_MSGMAP_ENTRY。消息映射表中存储了类所有重写的消息处理。当前给定了一个消息,为了执行该消息所对应的处理函数,需要在当前类中搜索消息映射表AFX_MSGMAP_ENTRY,若消息的ID存在该表中,那么说明当前类重写了该消息的处理方式,那么就调用相应的处理函数;若消息的ID不存在该表中,说明当前类没有重写该消息,于是就向上追溯,找到其基类,重复上述ID的比较,直到找到或者到达CCmdTarget为止。
这个过程从当前类中匹配,找不到就向上从基类匹配,依然找不到则继续向上……称之为向上匹配。注意这个过程只针对除WM_COMMAND以外的消息,即WM_xxx。所以WM_xxx消息只会从派生类流向基类,不会横流或逆流。
总之,WM_xxx消息只会纵向流动。
但WM_COMMAND消息则不一定。若一个CFrameWnd接收到消息,则消息会如下顺序传递:
CFrameWnd->CView->CDocumet->CDocumetTemplate
->CFrameWnd
->CWinApp
该顺序是固定的。如果一个CView或者一个CDcoumet接收到消息,消息同样是按照上面的顺序传递,只是起点从各自的类开始而已。
也就是说,WM_COMMAND消息的传递顺序是:先View,后Dcoument,再CWnd(实际上是CCmdTarget),最后CWinApp。
现在,CWinThread::Run产生了一个消息。该消息首先会被发送给AfxWndProc
① AfxWndProc是个全局函数,也是所有消息的推动引擎起点。AfxWndProc会在CWinThread::Run中被调用,也就是说,CWinThread::Run产生消息,并将消息发送给AfxWndProc。该函数的作用是将消息传递给AfxCallWndProc函数。
② AfxCallWndProc是个全局函数,该函数的作用是将传来的消息发送给当前类的WindowProc函数处理。由于WindowProc是个虚函数,所以每个类调用的WindowProc是不一定相同的。
③ WindowProc接收到消息后,会判断该消息是否为WM_COMMAND消息。若不是,那么只要从当前类开始,向上匹配找到消息处理函数即可;若是WM_COMMAND消息,那么就从CCmdTarget的所有派生类中来找对应的实现函数OnCommand,寻找过程见④。若最终找到了,就执行相应的处理函数;若最终没有找到,就执行默认的DefWindowProc。
④ 从当前类开始,判断OnCommand函数的重写情况。一般在重写函数的末尾,都会调用其基类的OnCommand函数。于是这样不停上溯,当上溯到该类的主要基类时,比如CFrameWnd,会在该基类的OnCommand函数中调用虚函数OnCmdMsg。
⑤ OnCmdMsg函数是个虚函数,于是根据当前的类对象,会调用不同的OnCmdMsg函数。在这个OnCmdMsg函数里,会获取其他主要基类(见过程⑥)并对这些基类的OnCmdMsg函数加以匹配调用。每个基类最终都会回溯到CCmdTarget中的OnCmdMsg函数,在这里面会调用GetMessageMap来获取所有命令,并对命令的ID进行向上匹配。但GetMessageMap是个虚函数,所以获取的命令也是当前类的消息映射表。若第一个基类匹配失败,就继续匹配第二个基类。直到匹配到合适的命令ID,或者匹配失败,回到步骤③中调用默认的DefWindowProc。
⑥ 获取其他主要基类的顺序是:
CFrameWnd->CView->CDocumet->CDocumetTemplate
->CFrameWnd
->CWinApp
消息发送给CFrameWnd,OnCmdMsg()函数开始处理消息:
CFrameWnd::OnCmdMsg()
{
//1
if(pView->OnCmdMsg())
{
return TRUE;
}
//2
if(CWnd::OnCmdMsg())
{
return TRUE;
}
//3
if(pApp->OnCmdMsg())
{
return TRUE;
}
}
由上可知CFrameWnd::OnCmdMsg()函数内有三个分支,分别用1,2,3表示。
首先消息进入分支1,注意此时主体是pView:
CView::OnCmdMsg()
{
//①
if(CWnd::OnCmdMsg())
{
return TRUE;
}
//②
m_pDocumet->OnCmdMsg();
}
进入分支1,又产生了两个新的分支,分别用①,②表示。
然后消息进入分支①。
由于是CWnd::OnCmdMsg(),但CWnd并没有改写基类CCmdTarget的函数OnCmdMsg(),所以实际上分支①进入的是CCmdTarget::OnCmdMsg():
CCmdTarget::OnCmdMsg()
{
向上匹配
}
在CCmdTarget::OnCmdMsg()中,开始对消息进行向上匹配。注意此时主体是pView。若匹配成功,则调用相应处理函数,并返回TRUE。从而消息传递终止。
若匹配不成功,则消息继续传递,进入分支②。
CDocumet::OnCmdMsg()
{
//③
if(CCmdTarget::OnCmdMsg())
{
return TRUE;
}
//④
m_pDocTemplate->OnCmdMsg();
}
此时,主体变成了m_pDocumet。在分支②中,同样出现了两个分支③,④。
先进入分支③。同样是进入到CCmdTarget::OnCmdMsg()中开始向上匹配,注意此时的主体是m_pDocumet。
若依然没有匹配成功,则进入分支④,主体变成m_pDocTemplate,并进一步进入到CCmdTarget::OnCmdMsg()中开始向上匹配。
若依然没有匹配成功,则一路返回,返回到最初的分支1,第一个if语句判断失败。进入到第二个if语句,即分支2。此时主体变成了CFrameWnd。然后进入CWnd::OnCmdMsg()。
//2
if(CWnd::OnCmdMsg())
{
return TRUE;
}
但CWnd并没有改写基类CCmdTarget的函数OnCmdMsg(),所以实际上进入CCmdTarget::OnCmdMsg(),开始向上匹配。
若依然没有匹配成功,则一路返回,返回到最初的分支2,第二个if语句判断失败。进入到第三个if语句,即分支3。此时主体变成了pApp。然后进入pApp->OnCmdMsg()。
//3
if(pApp->OnCmdMsg())
{
return TRUE;
}
但CWinApp并没有改写基类CCmdTarget的函数OnCmdMsg()(注意中间跳过了CWinThread),所以实际上进入CCmdTarget::OnCmdMsg(),开始向上匹配。
若匹配成功,则调用相应的处理函数;
若匹配失败,则说明没有该命令消息的处理函数。返回FALSE。