您的位置:首页 > 其它

MFC的消息响应机制说明

2020-11-19 22:26 288 查看

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]实际上定义了一张表,表中的每一项指定了相应的对 1044 象所要处理的消息和处理此消息的函数的对应关系,这张表也称为消息映射表。

再看看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所使用的方法只是解决这类问题的方式之一,不排除还有

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

  第二个问题,是由上面的问题引申出来的。如果在子类和父类中出现了相同的消息出来函 56c 数,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 关键字的作用。但是请注意,这些函数实际上并不是虚拟的,而是通过消息映射实现的。

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

所有能够进行消息处理的类都是基于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)< ad8 br>{
  // 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》。

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

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: