MFC的消息响应机制说明

MFC的快速理解:

1.MFC的设计者们在设计MFC时,有一个主要的方向就是尽可能使得MFC的代码要小,速度尽可能快。为了这个方向,工程师们使用了许多技巧,主要表现在宏的运用上,实

现MFC的消息映射的机制就是其中之一。

2.MFC消息映射机制有关的宏有下面几个:

 1)DECLARE_MESSAGE_MAP()宏

 2)BEGIN_MESSAGE_MAP(theClass, baseClass)宏

 3)END_MESSAGE_MAP()宏

弄懂MFC消息映射机制的最好办法是将找出一个具体的实例,将这些宏展开,并找出相关的数据结构。

  DECLARE_MESSAGE_MAP()

  DECLARE_MESSAGE_MAP()宏的定义如下:

  #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()作下面三件事:

定义一个长度不定的静态数组变量_messageEntries];

定义一个静态变量messageMap;

定义一个虚拟函数GetMessageMap();  

在DECLARE_MESSAGE_MAP()宏中,涉及到MFC中两个对外不公开的数据结构AFX_MSGMAP_ENTRY和AFX_MSGMAP。为了弄清楚消息映射,有必要考察一下这两

个数据结构的定义。

  AFX_MSGMAP_ENTRY的定义:

  struct AFX_MSGMAP_ENTRY
  {
   UINT nMessage; // windows message
   UINT nCode; // control code or WM_NOTIFY code
   UINT nID; // control ID (or 0 for windows messages)
   UINT nLastID; // used for entries specifying a range of control id's
   UINT nSig; // signature type (action) or pointer to message #
   AFX_PMSG pfn; // routine to call (or special value)
  };

结构中各项的含义已经注释,从上面的定义你是否看出,AFX_MSGMAP_ENTRY结构实际上定义了消息和处理此消息的动作之间的映射关系。

因此静态数组变量_messageEntries]实际上定义了一张表,表中的每一项指定了相应的对象所要处理的消息和处理此消息的函数的对应关系,这张表也称为消息映射表。

再看看AFX_MSGMAP的定义。

  struct AFX_MSGMAP
  {
   const AFX_MSGMAP* pBaseMap;
   const AFX_MSGMAP_ENTRY* lpEntries;
   };  

不难看出,AFX_MSGMAP定义了一单向链表,链表中每一项的值是一指向消息映射表的指针(实际上就是_messageEntries的值)。通过这个链表,使得在某个类中调用

基类的的消息处理函数很容易,因此,“父类的消息处理函数是子类的缺省消息处理函数”就“顺理成章”了。

在后面的“MFC窗口的消息处理”一节中会对此作详细的讲解。

由上述可见,在类的头文件中主要定义了两个数据结构:消息映射表和单向链表。

  BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()

  它们的定义如下:

  #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::_messageEntries0]

  }; 

  AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries] 

  { 
   #define END_MESSAGE_MAP() 

   {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } 

  }; 

对应BEGIN_MESSAGE_MAP()的定义可能不是一下子就看得明白,不过不要紧,举一例子就很清楚了。对于BEGIN_MESSAGE_MAP(CView, CWnd),VC预编译器将其展

开成下面的形式:

  const AFX_MSGMAP* CView::GetMessageMap() const
  {
   return &CView::messageMap;
  }

  AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CView::messageMap 
  {
   &CWnd::messageMap,
   &CView::_messageEntries0
  };
  AFX_COMDAT const AFX_MSGMAP_ENTRY CView::_messageEntries
  {

至于END_MESSAGE_MAP()则不过定义了一个表示映射表结束的标志项,我想大家对于这种简单的技巧应该是很熟悉的,无需多述。

我想大家也已经想到了ON_COMMAND这样的宏的具体作用了,不错它们只不过定义了一种类型的消息映射项,看看ON_COMMAND的定义:

  #define ON_COMMAND(id, memberFxn) 
  {

    WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn

  },  

  根据上面的定义,ON_COMMAND(ID_FILE_NEW, OnFileNew)将被VC预编译器展开如下:

  {    

    WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&OnFileNew

  },

3.MFC的消息映射机制已经清楚了,现在提出并解答两个问题以作为对这一节的小结。

为什么不直接使用虚拟函数实现消息处理函数呢?

  MFC的设计者们在设计MFC时有一个很明确的目标,就是使得“MFC的代码尽可能小,速度尽可能快”,如果采用虚拟函数,那么对于所有的窗口消息,都必须有一个与之对

应的虚拟函数,因而对每一个从CWnd派生的类而言,都会有一张很大的虚拟函数表vtbl。但是在实际应用中,一般只对少数的消息进行处理,大部分都交给系统缺省处理,所以

表中的大部分项都是无用项,这样做就浪费了很多内存资源,这同MFC设计者们的设计目标是相违背的。当然,MFC所使用的方法只是解决这类问题的方式之一,不排除还有

其他的解决方式,但就我个人观点而言,这是一种最好的解决方式,体现了很高的技巧性,值得我们学习。  

  第二个问题,是由上面的问题引申出来的。如果在子类和父类中出现了相同的消息出来函数,VC编译器会怎么处理这个问题呢?VC不会将它们看作错误,而会象对

待虚拟函数类似的方式去处理,但对于消息处理函数(带afx_msg前缀),则不会生成虚拟函数表vtbl。

MFC下一个消息的处理过程是一般是这样的。

1)_AfxCbtFilterHook截获消息(这是一个钩子函数)

2)_AfxCbtFilterHook把窗口过程设定为AfxWndProc。

3)函数AfxWndProc接收Windows操作系统发送的消息。

4)函数AfxWndProc调用函数AfxCallWndProc进行消息处理。

5)函数AfxCallWndProc调用CWnd类的方法WindowProc进行消息处理。

4.如何添加自己的消息?

我们已经了解了WINDOW的消息机制,如何加入我们自己的消息呢?好我们来看一个标准的消息处理程序是这个样子的,在 CWnd 类中预定义了标准 Windows 消息 (WM_XXXX

WM是WINDOW MESSAGE的缩写) 的默认处理程序。类库基于消息名命名这些处理程序。例如,WM_PAINT 消息的处理程序在 CWnd 中被声明为:

afx_msg void OnPaint();

afx_msg 关键字通过使这些处理程序区别于其他 CWnd 成员函数来表明 C++ virtual 关键字的作用。但是请注意,这些函数实际上并不是虚拟的,而是通过消息映射实现的。

我们在本文的一开始便说明了为什么要这样做。

所有能够进行消息处理的类都是基于CCmdTarget类的,也就是说CCmdTarget类是所有可以进行消息处理类的父类。CCmdTarget类是MFC处理命令消息的基础和核心。

若要重写基类中定义的处理程序,只需在派生类中定义一个具有相同原型的函数,并创建此处理程序的消息映射项。我们通过ClassWizard可以建立大多数窗口消息或自定义的

消息,通过ClassWizard可以自动建立消息映射,和消息处理函数的框架,我们只需要把我们要做的事情填空,添加你要做的事情到处理函数。这个非常简单,就不细说了。但是

也许我们需要添加一些ClassWizard不支持的窗口消息或自定义消息,那么就需要我们亲自动手建立消息映射和消息处理的框架,通常步骤如下:

第一步:定义消息。Microsoft推荐用户自定义消息至少是WM_USER+100,因为很多新控件也要使用WM_USER消息。

#define WM_MYMESSAGE (WM_USER + 100)

第二步:实现消息处理函数。该函数使用WPRAM和LPARAM参数并返回LPESULT。

LPESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
  // TODO: 处理用户自定义消息,填空就是要填到这里。
  return 0;
}
第三步:在类头文件的AFX_MSG块中说明消息处理函数:

// {{AFX_MSG(CMainFrame)

afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

第四步:在用户类的消息块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中。

ON_MESSAGE( WM_MYMESSAGE, OnMyMessage )

5.MFC的机制大概就是这些了,如需在深入学习,可以看孙鑫的《VC++深入详解》或者是侯杰的《深入浅出MFC》。

                                               改变自己,从现在做起-----------久馆

 

posted on 2020-11-19 22:26  DanielandCalf  阅读(687)  评论(0编辑  收藏  举报

导航