⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 mfc教程_ 消息映射的实现.htm

📁 MFC (Microsoft Foundation Class Library)中的各种类结合起来构成了一个应用程序框架
💻 HTM
📖 第 1 页 / 共 5 页
字号:
<P>该函数是虚拟函数。</P>
<P align=justify>和WindowProc一样,由于当前对象的类CTview没有覆盖该函数,所以CWnd的OnWndMsg被调用。</P>
<P>在CWnd中,MFC使用OnWndMsg来分别处理各类消息:</P>
<P>如果是WM_COMMAND消息,交给OnCommand处理;然后返回。</P>
<P>如果是WM_NOTIFY消息,交给OnNotify处理;然后返回。</P>
<P>如果是WM_ACTIVATE消息,先交给_AfxHandleActivate处理(后面5.3.3.7节会解释它的处理),再继续下面的处理。</P>
<P>如果是WM_SETCURSOR消息,先交给_AfxHandleSetCursor处理;然后返回。</P>
<P>如果是其他的Windows消息(包括WM_ACTIVATE),则</P>
<DIR>
<DIR>
<P>首先在消息缓冲池进行消息匹配,</P>
<P>若匹配成功,则调用相应的消息处理函数;</P>
<DIR>
<DIR>
<P>若不成功,则在消息目标的消息映射数组中进行查找匹配,看它是否处理当前消息。这里,消息目标即CTview对象。</P>
<DIR>
<P>如果消息目标处理了该消息,则会匹配到消息处理函数,调用它进行处理;</P>
<P>否则,该消息没有被应用程序处理,OnWndMsg返回FALSE。</P>
<P align=justify></P></DIR></DIR></DIR></DIR></DIR>
<P align=justify>关于Windows消息和消息处理函数的匹配,见下一节。</P>
<P align=justify></P>
<P align=justify>缺省处理函数DefWindowProc将在讨论对话框等的实现时具体分析。</P>
<OL>
  <OL>
    <OL>
      <OL>
        <P align=justify>
        <LI><A name=_Toc445889021></A><A name=_Toc445782424></A><A 
        name=_Toc457298978></A><B>Windows消息的查找和匹配</B> 
        <P></P>
        <P>CWnd或者派生类的对象调用OnWndMsg搜索本对象或者基类的消息映射数组,寻找当前消息的消息处理函数。如果当前对象或者基类处理了当前消息,则必定在其中一个类的消息映射数组中匹配到当前消息的处理函数。</P>
        <P>消息匹配是一个比较耗时的任务,为了提高效率,MFC设计了一个消息缓冲池,把要处理的消息和匹配到的消息映射条目(条目包含了消息处理函数的地址)以及进行消息处理的当前类等信息构成一条缓冲信息,放到缓冲池中。如果以后又有同样的消息需要同一个类处理,则直接从缓冲池查找到对应的消息映射条目就可以了。</P>
        <P>MFC用哈希查找来查询消息映射缓冲池。消息缓冲池相当于一个哈希表,它是应用程序的一个全局变量,可以放512条最新用到的消息映射条目的缓冲信息,每一条缓冲信息是哈希表的一个入口。</P>
        <P>采用AFX_MSG_CACHE结构描述每条缓冲信息,其定义如下:</P>
        <P align=justify>struct AFX_MSG_CACHE</P>
        <P align=justify>{</P>
        <P align=justify>UINT nMsg;</P>
        <P align=justify>const AFX_MSGMAP_ENTRY* lpEntry;</P>
        <P align=justify>const AFX_MSGMAP* pMessageMap;</P>
        <P align=justify>};</P>
        <P>nMsg存放消息ID,每个哈希表入口有不同的nMsg。</P>
        <P>lpEnty存放和消息ID匹配的消息映射条目的地址,它可能是this所指对象的类的映射条目,也可能是这个类的某个基类的映射条目,也可能是空。</P>
        <P>pMessageMap存放消息处理函数匹配成功时进行消息处理的当前类(this所指对象的类)的静态成员变量messageMap的地址,它唯一的标识了一个类(每个类的messageMap变量都不一样)。</P>
        <P>this所指对象是一个CWnd或其派生类的实例,是正在处理消息的MFC窗口对象。</P>
        <P>哈希查找:使用消息ID的值作为关键值进行哈希查找,如果成功,即可从lpEntry获得消息映射条目的地址,从而得到消息处理函数及其原型。</P>
        <P>如何判断是否成功匹配呢?有两条标准:</P>
        <P 
        align=justify>第一,当前要处理的消息message在哈希表(缓冲池)中有入口;第二,当前窗口对象(this所指对象)的类的静态变量messageMap的地址应该等于本条缓冲信息的pMessagMap。MFC通过虚拟函数GetMessagMap得到messageMap的地址。</P>
        <P align=justify>如果在消息缓冲池中没有找到匹配,则搜索当前对象的消息映射数组,看是否有合适的消息处理函数。</P>
        <P align=justify>如果匹配到一个消息处理函数,则把匹配结果加入到消息缓冲池中,即填写该条消息对应的哈希表入口:</P>
        <P align=justify>nMsg=message; </P>
        <P align=justify>pMessageMap=this-&gt;GetMessageMap; </P>
        <P align=justify>lpEntry=查找结果</P>
        <P 
        align=justify>然后,调用匹配到的消息处理函数。否则(没有找到),使用_GetBaseMessageMap得到基类的消息映射数组,查找和匹配;直到匹配成功或搜寻了所有的基类(到CCmdTarget)为止。</P>
        <P 
        align=justify>如果最后没有找到,则也把该条消息的匹配结果加入到缓冲池中。和匹配成功不同的是:指定lpEntry为空。这样OnWndMsg返回,把控制权返还给AfxCallWndProc函数,AfxCallWndProc将继续调用DefWndProc进行缺省处理。</P>
        <P align=justify></P>
        <P 
        align=justify>消息映射数组的搜索在CCmdTarget::OnCmdMsg函数中也用到了,而且算法相同。为了提高速度,MFC把和消息映射数组条目逐一比较、匹配的函数AfxFindMessageEntry用汇编书写。</P>
        <P align=justify>const AFX_MSGMAP_ENTRY* AFXAPI</P>
        <P align=justify><I>AfxFindMessageEntry</I>(const AFX_MSGMAP_ENTRY* 
        lpEntry,</P>
        <P align=justify>UINT nMsg, UINT nCode, UINT nID)</P>
        <P>第一个参数是要搜索的映射数组的入口;第二个参数是Windows消息标识;第三个参数是控制通知消息标识;第四个参数是命令消息标识。</P>
        <P>对Windows消息来说,nMsg是每条消息不同的,nID和nCode为0。</P>
        <P>对命令消息来说,nMsg固定为WM_COMMAND,nID是每条消息不同,nCode都是CN_COMMAND(定义为0)。</P>
        <P>对控制通知消息来说,nMsg固定为WM_COMMAND或者WM_NOTIFY,nID和nCode是每条消息不同。</P>
        <P>对于Register消息,nMsg指定为0XC000,nID和nCode为0。在使用函数AfxFindMessageEntry得到匹配结果之后,还必须判断nSig是否等于message,只有相等才调用对应的消息处理函数。</P>
        <P align=justify></P>
        <LI><A name=_Toc445889022></A><A name=_Toc445782425></A><A 
        name=_Toc457298979></A><B>Windows消息处理函数的调用</B> 
        <P></P>
        <P align=justify>对一个Windows消息,匹配到了一个消息映射条目之后,将调用映射条目所指示的消息处理函数。</P>
        <P 
        align=justify>调用处理函数的过程就是转换映射条目的pfn指针为适当的函数类型并执行它:MFC定义了一个成员函数指针mmf,首先把消息处理函数的地址赋值给该函数指针,然后根据消息映射条目的nSig值转换指针的类型。但是,要给函数指针mmf赋值,必须使该指针可以指向所有的消息处理函数,为此则该指针的类型是所有类型的消息处理函数指针的联合体。</P>
        <P align=justify>对上述过程,MFC的实现大略如下:</P>
        <P align=justify>union MessageMapFunctions mmf;</P>
        <P align=justify>mmf.pfn = lpEntry-&gt;pfn;</P>
        <P align=justify>swithc (value_of_nsig){</P>
        <P align=justify>…</P>
        <P align=justify>case AfxSig_is: //OnCreate就是该类型</P>
        <P align=justify>lResult = (this-&gt;*mmf.pfn_is)((LPTSTR)lParam);</P>
        <P align=justify>break;</P>
        <P align=justify>…</P>
        <P align=justify>default:</P>
        <P align=justify>ASSERT(FALSE); break;</P>
        <P align=justify>}</P>
        <P align=justify>…</P>
        <P align=justify>LDispatchRegistered: // 处理registered windows 
        messages</P>
        <P align=justify>ASSERT(message &gt;= 0xC000);</P>
        <P align=justify>mmf.pfn = lpEntry-&gt;pfn;</P>
        <P align=justify>lResult = (this-&gt;*mmf.pfn_lwl)(wParam, lParam);</P>
        <P align=justify>…</P>
        <P>如果消息处理函数有返回值,则返回该结果,否则,返回TRUE。</P>
        <P align=justify>对于图4-1所示的例子,nSig等于AfxSig_is,所以将执行语句</P>
        <P align=justify>(this-&gt;*mmf.pfn_is)((LPTSTR)lParam)</P>
        <P align=justify>也就是对CTview::OnCreate的调用。</P>
        <P>顺便指出,对于Registered窗口消息,消息处理函数都是同一原型,所以都被转换成lwl型(关于Registered窗口消息的映射,见4.4.2节)。</P>
        <P>综上所述,标准Windwos消息和应用程序消息中的Registered消息,由窗口过程直接调用相应的处理函数处理:</P>
        <P>如果某个类型的窗口(C++类)处理了某条消息(覆盖了CWnd或直接基类的处理函数),则对应的HWND窗口(Winodws 
        window)收到该消息时就调用该覆盖函数来处理;如果该类窗口没有处理该消息,则调用实现该处理函数最直接的基类(在C++的类层次上接近该类)来处理,上述例子中如果CTview不处理WM_CREATE消息,则调用上一层的CWnd::OnCreate处理;</P>
        <P>如果基类都不处理该消息,则调用DefWndProc来处理。</P>
        <P align=justify></P>
        <LI><A name=_Toc457298980></A><B>消息映射机制完成虚拟函数功能的原理</B> 
        <P></P></LI></OL></OL></OL></OL>
<P>综合对Windows消息的处理来看,MFC使用消息映射机制完成了C++虚拟函数的功能。这主要基于以下几点:</P>
<UL>
  <P align=justify>
  <LI>所有处理消息的类从CCmdTarget派生。 
  <P></P>
  <P align=justify></P>
  <LI>使用静态成员变量_messageEntries数组存放消息映射条目,使用静态成员变量messageMap来唯一地区别和得到类的消息映射。 
  <P></P>
  <P align=justify></P>
  <LI>通过GetMessage虚拟函数来获取当前对象的类的messageMap变量,进而得到消息映射入口。 
  <P></P>
  <P align=justify></P>
  <LI>按照先底层,后基层的顺序在类的消息映射数组中搜索消息处理函数。基于这样的机制,一般在覆盖基类的消息处理函数时,应该调用基类的同名函数。 
  <P></P></LI></UL>
<P align=justify>以上论断适合于MFC其他消息处理机制,如对命令消息的处理等。不同的是其他消息处理有一个命令派发/分发的过程。</P>
<P align=justify>下一节,讨论命令消息的接受和处理。</P>
<OL>
  <OL>
    <OL>
      <P align=justify>
      <LI><A name=_Toc445889024></A><A name=_Toc445782427></A><A 
      name=_Toc452640909></A><A name=_Toc457298981></A><B>对命令消息的接收和处理</B> 
      <P></P>
      <OL>
        <P align=justify>
        <LI><B><A name=_Toc445889025></A><A name=_Toc445782428></A><A 
        name=_Toc457298982></A>MFC标准命令消息的发送</B> 
        <P></P></LI></OL></LI></OL></OL></OL>
<P 
align=justify>在SDI或者MDI应用程序中,命令消息由用户界面对象(如菜单、工具条等)产生,然后送给主边框窗口。主边框窗口使用标准MFC窗口过程处理命令消息。窗口过程把命令传递给MFC主边框窗口对象,开始命令消息的分发。MFC边框窗口类CFrameWnd提供了消息分发的能力。</P>
<P align=justify>下面,还是通过一个例子来说明命令消息的处理过程。</P>
<P 
align=justify>使用AppWizard产生一个单文档应用程序t。从help菜单选择“About”,就会弹出一个ABOUT对话框。下面,讨论从命令消息的发出到对话框弹出的过程。</P>
<P align=justify>首先,选择“ 
About”菜单项的动作导致一个Windows命令消息ID_APP_ABOUT的产生。Windows系统发送该命令消息到边框窗口,导致它的窗口过程AfxWndProc被调用,参数1是边框窗口的句柄,参数2是消息ID(即WM_COMMAND),参数3、4是消息参数,参数3的值是ID_APP_ABOUT。接着的系列调用如图4-3所示。</P><IMG 
height=437 hspace=12 src="" width=397 align=left> 
<P align=justify></P>
<P align=justify>下面分别讲述每一层所调用的函数。</P>
<P 
align=justify>前4步同对Windows消息的处理。这里接受消息的HWND窗口是主边框窗口,因此,AfxWndProc根据HWND句柄得到的MFC窗口对象是MFC边框窗口对象。</P>
<P 
align=justify>在4.2.2节谈到,如果CWnd::OnWndMsg判断要处理的消息是命令消息(WM_COMMAND),就调用OnCommand进一步处理。由于OnCommand是虚拟函数,当前MFC窗口对象是边框窗口对象,它的类从CFrameWnd类导出,没有覆盖CWnd的虚拟函数OnCommand,而CFrameWnd覆盖了CWnd的OnCommand,所以,CFrameWnd的OnCommand被调用。换句话说,CFrameWnd的OnCommand被调用是动态约束的结果。接着介绍的本例子的有关调用,也是通过动态约束而实际发生的函数调用。</P>
<P align=justify>接着的有关调用,将不进行为什么调用某个类的虚拟或者消息处理函数的分析。</P>
<P align=justify>(1)CFrameWnd的OnCommand函数</P>
<P align=justify>BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)</P>
<P 
align=justify>参数wParam的低阶word存放了菜单命令nID或控制子窗口ID;如果消息来自控制窗口,高阶word存放了控制通知消息;如果消息来自加速键,高阶word值为1;如果消息来自菜单,高阶word值为0。</P>
<P align=justify>如果是通知消息,参数lParam存放了控制窗口的句柄hWndCtrl,其他情况下lParam是0。</P>
<P align=justify>在这个例子里,低阶word是ID_APP_ABOUT,高阶word是1;lParam是0。</P>
<P 
align=justify>MFC对CFrameWnd的缺省实现主要是获得一个机会来检查程序是否运行在HELP状态,需要执行上下文帮助,如果不需要,则调用基类的CWnd::OnCommand实现正常的命令消息发送。</P>
<P align=justify>(2)CWnd的OnCommand函数</P>
<P align=justify>BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)</P>
<P align=justify>它按一定的顺序处理命令或者通知消息,如果发送成功,返回TRUE,否则,FALSE。处理顺序如下:</P>
<P align=justify>如果是命令消息,则调用OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &amp;state, 
NULL)测试nID命令是否已经被禁止,如果这样,返回FALSE;否则,调用OnCmdMsg进行命令发送。关于CN_UPDATE_COMMAND_UI通知消息,见后面用户界面状态的更新处理。</P>
<P 
align=justify>如果是控制通知消息,则先用ReflectLastMsg反射通知消息到子窗口。如果子窗口处理了该消息,则返回TRUE;否则,调用OnCmdMsg进行命令发送。关于通知消息的反射见后面4.4.4.3节。OnCommand给OnCmdMsg传递四个参数:nID,即命令消息ID;nCode,如果是通知消息则为通知代码,如果是命令消息则为NC_COMMAND(即0);其余两个参数为空。</P>
<P align=justify>(3)CFrameWnd的OnCmdMsg函数</P>
<P align=justify>BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,</P>
<P align=justify>AFX_CMDHANDLERINFO* pHandlerInfo)</P>
<P 
align=justify>参数1是命令ID;如果是通知消息(WM_C

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -