用户操作
[即时聊天] [发私信] [加为好友]
armmanID:armman
100462次访问,排名949,好友0人,关注者8人。
armman的文章
原创 261 篇
翻译 0 篇
转载 73 篇
评论 20 篇
最近评论
czdvcc:wow power leveling
czdvcc:wow power leveling
czdvcc:wow power leveling
czdvcc:wow power leveling
czdvcc:wow power leveling
文章分类
    收藏
      相册
      存档
      软件项目交易
      订阅我的博客
      XML聚合  FeedSky
      订阅到鲜果
      订阅到Google
      订阅到抓虾
      订阅到BlogLines
      订阅到Yahoo
      订阅到GouGou
      订阅到飞鸽
      订阅到Rojo
      订阅到newsgator
      订阅到netvibes

      原创 MFC的消息映射与命令传递收藏

      新一篇: 文档视结构 | 旧一篇: MFC的串行化编程

      消息映射与命令传递体现了MFC与SDK的不同。在SDK编程中,没有消息映射的概念,它有明确的回调函数,通过一个switch语句去判断收到了何种消息,然后对这个消息进行处理。所以,在SDK编程中,会发送消息和在回调函数中处理消息就差不多可以写SDK程序了。

       MFC中,看上去发送消息和处理消息比SDK更简单、直接,但不直观。举个例子,若想自定义一个消息,SDK是非常简单直观的,用一条语句:
      SendMessage(hwnd,message/*一个大于或等于WM_USER的数字*/,wparam,lparam),
      之后就可以在回调函数中处理了。但MFC就不同了,因为通常不直接去改写窗口的回调函数,所以只能对照原来的MFC代码,把消息放到恰当的地方。

       MFC的自动化给我们提供了很大的方便,比如,所有的MFC窗口都使用同一窗口过程,即所有的MFC窗口都有一个默认的窗口过程。不象在SDK编程中,要为每个窗口类写一个窗口过程。

        对消息映射,最直接地猜想是:消息映射就是用一个数据结构把“消息”与“响应消息函数名”串联起来。这样,当窗口感知消息发生时,就对结构查找,找到相应的消息响应函数执行。这个想法也不能简单实现:每个不同的MFC窗口类,对同一种消息,有不同的响应方式。即对同一种消息,不同的MFC窗口会有不同的消息响应函数。

       我们设计窗口基类(CWnd)时,让它对每种不同的消息都来一个消息响应,并把这个消息响应函数定义为空虚函数。这样,从CWnd派生的窗口类对所有消息都有了一个空响应,我们要响应一个特定的消息就重载这个消息响应函数就可以了。但这样做的结果,一个几乎什么也不做的CWnd类要有几百个“多余”的函数,那怕这些消息响应函数都为纯虚函数,每个CWnd对象也要背负着一个巨大的虚拟表,这也得不偿失。

       消息映射的目的,不是为是更加快捷地向窗口过程添加代码,而是一种机制的改变。如果不想改变窗口过程函数,那么应该在哪里进行消息响应呢?可以用HOOK技术,抢在消息队列前把消息抓取,把消息响应提到窗口过程的外面。再者,不同的窗口,会有不同的感兴趣的消息,所以每个MFC窗口都应该有一个表把感兴趣的消息和相应消息响应函数连系起来。然后得出——消息映射机制执行步骤是:当消息发生,用HOOK技术把本来发送到窗口过程的消息抓获,然后对照一下MFC窗口的消息映射表,如果是表里面有的消息,就执行其对应的函数。

       用HOOK技术,理论上可以在不改变窗口过程函数的情况下,完成消息响应。MFC确实是这样做,但实际操作起来可能比想象差别很大。

       现在我们来编写消息映射表,先定义一个结构,这个结构至少有两个项:一是消息ID,二是响应该消息的函数。如下:
      struct AFX_MSGMAP_ENTRY
      {
      UINT nMessage;        //感兴趣的消息
      AFX_PMSG pfn;        //响应以上消息的函数指针
      }

       只有两个成员的结构连接起来的消息映射表是不成熟的。Windows消息分为标准消息、控件消息和命令消息,每类型的消息包含数百不同ID、不同意义、不同参数的消息。要准确地判别发生了何种消息,必须再增加几个成员。还有,对于AFX_PMSG pfn,实际上等于作以下声明:
      void (CCmdTarget::*pfn)();
      (提示:AFX_PMSG为类型标识,具体声明是:typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);)

       pfn是一个不带参数和返回值的CCmdTarget类型函数指针,只能指向CCmdTarget类中不带参数和返回值的成员函数,这样pfn更为通用,但我们响应消息的函数许多需要传入参数的。为了解决这个矛盾,还要增加一个表示参数类型的成员。当然,还有其它……
      最后,我们消息映射表成员结构如下定义:
      struct AFX_MSGMAP_ENTRY
      {
      UINT nMessage;         //Windows 消息ID
      UINT nCode;            // 控制消息的通知码
      UINT nID;              //命令消息ID范围的起始值
      UINT nLastID;          //命令消息ID范围的终点
      UINT nSig;             // 消息的动作标识
      AFX_PMSG pfn;
      };

       有了以上消息映射表成员结构,就可以定义一个AFX_MSGMAP_ENTRY类型的数组,容纳消息映射项。定义如下:
      AFX_MSGMAP_ENTRY _messageEntries[];
      但这样还不够,每个AFX_MSGMAP_ENTRY数组,只能保存着当前类感兴趣的消息,而这仅仅是我们想处理的消息中的一部分。对于一个MFC程序,一般有多个窗口类,里面都应该有一个AFX_MSGMAP_ENTRY数组。MFC还有一个消息传递机制,可以把自己不处理的消息传送给别的类进行处理。为了能查找各个MFC对象的消息映射表,我们还要增加一个结构,把所有的AFX_MSGMAP_ENTRY数组串联起来。
      于是,我们定义了一个新结构体:
      struct AFX_MSGMAP
      {
      const AFX_MSGMAP* pBaseMap;               //指向别的类的AFX_MSGMAP对象
      const AFX_MSGMAP_ENTRY* lpEntries;        //指向自身的消息表
      };

       在每个打算响应消息的类中这样声明一个变量:AFX_MSGMAP messageMap,让其中的pBaseMap指向基类或另一个类的messageMap,那么将得到一个AFX_MSGMAP元素的单向链表。这样,所有的消息映射信息形成了一张消息网。仅有消息映射表还不够,它只能把各个MFC对象的消息、参数与相应的消息响应函数连成一张网。为了方便查找,MFC在上面的类中插入了两个函数(其中theClass代表当前类):
      一个是_GetBaseMessageMap(),用来得到基类消息映射的函数。函数原型如下:
      const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \
      { return &baseClass::messageMap; } \
      别一个是GetMessageMap() ,用来得到自身消息映射的函数。函数原型如下:
      const AFX_MSGMAP* theClass::GetMessageMap() const \
      { return &theClass::messageMap; } \

       有了消息映射表之后,当消息发生以后,其对应的响应函数如何被调用。大家知道,所有的MFC窗口,都有一个同样的窗口过程——AfxWndProc(…)。对于这个庞大的消息映射机制,MFC要做的事情很多,如优化消息,增强兼容性等,这一大量的工作,有些甚至用汇编语言来完成,

       已定型的AfxWndProc,对所有消息最多只能提供一种默认的处理方式。这当然不是我们想要的。我们想通过AfxWndProc最终执行消息映射网中对应的函数。那么,这个执行路线是怎么样的呢?
      从AfxWndProc下去,最终会调用到一个函数OnWndMsg。请看代码:
      LRESULT CALLBACK AfxWndProc(HWND hWnd,UINT nMsg,WPARAM wParam, LPARAM lParam)
      {    
          ……
          CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);    //把对句柄的操作转换成对CWnd对象。
          Return AfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam);
      }

      把对句柄的操作转换成对CWnd对象是很重要的一件事,因为AfxWndProc只是一个全局函数,当然不知怎么样去处理各种windows窗口消息,所以它把处理权交给windows窗口所关联的MFC窗口对象。几乎可以想象得到AfxCallWndProc要做的事情,它当中有一句:
      pWnd->WindowProc(nMsg,wParam,lParam);

      到此,MFC窗口过程函数变成了自己的一个成员函数。WindowProc是一个虚函数,可以通过改写这个函数去响应不同的消息。

       WindowProc会调用到CWnd对象的另一个成员函数OnWndMsg,下面看看大概的函数原形是怎么样的:
      BOOL CWnd::OnWndMsg(UINT message,WPARAM wParam,LPARAM lParam,LRESULT* pResult)
      {
              if(message==WM_COMMAND)
              {
                  OnCommand(wParam,lParam);
                  ……
              }
              if(message==WM_NOTIFY)
              {
                  OnCommand(wParam,lParam,&lResult);
                  ……
              }
        const AFX_MSGMAP* pMessageMap; pMessageMap=GetMessageMap();
        const AFX_MSGMAP_ENTRY* lpEntry;
        /*以下代码作用为:用AfxFindMessageEntry函数从消息入口pMessageMap处查找指定消息,若找到,返回指定消息映射表成员的指针给lpEntry。然后执行该结构成员的pfn所指向的函数*/ 
        if((lpEntry=AfxFindMessageEntry(pMessageMap->lpEntries,message,0,0)!=NULL)
        {
            lpEntry->pfn();
         /*注意:真正MFC代码中没有用这一条语句。上面提到,不同的消息参数代表不同的意义和不同的消息     响应函数有不同类型的返回值。而pfn是一个不带参数的函数指针,所以真正的MFC代码中,要根据对象lpEntry的消息的动作标识nSig给消息处理函数传递参数类型。这个过程包含很复杂的宏代换,大家在此知道:找到匹配消息,执行相应函数就行!*/
         }
      )

       OnWndMsg能根据传进来的消息参数,查找到匹配的消息和执行相应的消息响应。但这还不够,平常响应菜单命令消息的时候,原本属于框架窗口(CFrameWnd)的WM_COMMAND消息,却可以放到视对象或文档对象中去响应。其原理如下:
      看上面函数OnWndMsg原型中看到以下代码:
      if(message==WM_COMMAND)
      {
          OnCommand(wParam,lParam);
          ……
      }

       即对于命令消息,实际上是交给OnCommand函数处理。而OnCommand是一个虚函数,即WM_COMMAND消息发生时,最终是发生该消息所对应的MFC对象去执行OnCommand。比如点框架窗口菜单,即向CFrameWnd发送一个WM_COMMAND,将会导致CFrameWnd::OnCommand(wParam,lParam)的执行。
      且看该函数原型
      BOOL CFrameWnd::OnCommand(WPARAM wParam,LPARAM lParam)
      {
          ……
          return CWnd:: OnCommand(wParam,lParam);
      }
      可以看出,它最后把该消息交给CWnd:: OnCommand处理。再看:
      BOOL CWnd::OnCommand(WPARAM wParam,LPARAM lParam)
      {
          ……
          return OnCmdMsg(nID,nCode,NULL,NULL);
      }

       这里虽然是执行CWnd类的函数,但由于这个函数在CFrameWnd:: OnCmdMsg里执行,即当前指针是CFrameWnd类指针,再有OnCmdMsg是一个虚函数,所以如果CFrameWnd改写了OnCommand,程序会执行CFrameWnd::OnCmdMsg(…)。
      对CFrameWnd::OnCmdMsg(…)函数原理扼要分析如下:
      BOOL CFrameWnd:: OnCmdMsg(…)
      {
          CView pView = GetActiveView(); //得到活动视指针。
          if(pView-> OnCmdMsg(…))
            return TRUE;              //若CView类对象或其派生类对象已经处理该消息,则返回。
          ……                    //否则,向下执行,交给文档、框架、及应用程序执行自身的OnCmdMsg。
      }

      到此,CFrameWnd:: OnCmdMsg完成了把WM_COMMAND消息传递到视对象、文档对象及应用程序对象实现消息响应。

       现在,看看MFC中的“神秘代码”。先看DECLARE_MESSAGE_MAP()宏,它在MFC中定义如下:
      #define DECLARE_MESSAGE_MAP() \
      private: \
          static const AFX_MSGMAP_ENTRY _messageEntries[]; \        
      protected: \
          static AFX_DATA const AFX_MSGMAP messageMap; \
          virtual const AFX_MSGMAP* GetMessageMap() const; \
      可以看出DECLARE_MESSAGE_MAP()定义了我们熟悉的两个结构和一个函数,显而易见,这个宏为每个需要实现消息映射的类提供了相关变量和函数。

       看一下BEGIN_MESSAGE_MAP,END_MESSAGE_MAP和ON_COMMAND三个宏,它们在MFC中定义如下(其中ON_COMMAND与另外两个宏并没有定义在同一个文件中):
      #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[] = \
      { \

      #define ON_COMMAND(id, memberFxn) \
          { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },

      #define END_MESSAGE_MAP() \
      {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
      }; \

      看下面例子,假设我们框架中有一菜单项为“Test”,即定义了如下宏:
      BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
          ON_COMMAND(ID_TEST, OnTest)
      END_MESSAGE_MAP()
      宏展开之后得到如下代码:
      const AFX_MSGMAP* CMainFrame::GetMessageMap() const
      { return &CMainFrame::messageMap; }
      ///以下填入消息表映射信息
      const AFX_MSGMAP CMainFrame::messageMap =         
      { &CFrameWnd::messageMap, &CMainFrame::_messageEntries[0] };
      //下面填入保存着当前类感兴趣的消息,可填入多个AFX_MSGMAP_ENTRY对象
      const AFX_MSGMAP_ENTRY CMainFrame::_messageEntries[] =
      {
        // 加入的ID_TEST消息参数
       { WM_COMMAND,CN_COMMAND,(WORD)ID_TEST,(WORD)ID_TEST,AfxSig_vv,(AFX_PMSG)&OnTest };  
       {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } //本类的消息映射的结束项
      };

      大家知道,要完成ID_TEST消息映射,还要定义和实现OnTest函数。在此即要在头文件写afx_msg void OnTest()并在源文件中实现它。根据以上的知识,知道了当ID为ID_TEST的命令消息发生,最终会执行到我们写的OnTest函数。

       

      发表于 @ 2007年03月13日 21:26:00|评论(loading...)|编辑

      新一篇: 文档视结构 | 旧一篇: MFC的串行化编程

      评论:没有评论。

      发表评论  


      当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
      Csdn Blog version 3.1a
      Copyright © armman