消息

由《afx_msg 是什么东东?》所述引发了一个新的问题: 
那些 BEGIN_MESSAGE_MAP....END_MESSAGE_MAP是什么东西呢?
下面来讲一下MFC的消息映射机制与命令绕行
  Windows 程序的本质系借着消息来维持脉动。每一个消息都有一个代码,并以WM_ 开头的常数表示之。消息在传统SDK 程序方法中的流动以及处置方式。各种消息之中,来自菜单或工具栏者,都以WM_COMMAND 表示,所以这一类消息我们又称之为命令消息(Command Message),其wParam 记录着此一消息来自哪一个菜单项目。消息会循着Application Framework 规定的路线,游走于各个对象之间,直到找到它的依
(消息处理函数)。找不到的话,Framework 最终就把它交给::DefWindowProc 函数去处理。对于一般消息(如WM_MOVE、WM_SIZE、WM_CREATE 等)也是天经地义的。但是今天Application Framework 比传统的SDK 程序多出了一个Document/View 架构,试想,如果菜单上有个命令项关乎文件的处理,那么让这个命令消息流到Document 类别去不是最理想吗?一旦流入Document 大本营,我们(程序员)就可以很方便地取得Document 成员变量、调用Document 成员函数,做爱做的事。但是Document 不是窗口,也没有对应的窗口类别,怎么让消息能够七拐八弯地流往Document 类别去?甚至更往上流向Application 类别去?这就是所谓的命令绕行机制!
 而为了让消息的流动有线路可循,MFC 必须做出一个巨大的网,实现所有可能的路线,这个网就是所谓的消息映射地图(Message map)。最后,MFC 还得实现一个消息推动引擎,让消息依Framework 的意旨前进,该拐的时候拐,该弯的时候弯,这个邦浦机制埋藏在各个类别的WindowProc、OnCommand、OnCmdMsg、DefWindowProc 虚拟函数中。
没有命令绕行机制,Document/View 架构就像断了条胳臂,会少掉许多功用。  
   很快你就会看到所有的秘密。很快地,它们统统不再对你构成神秘。
  消息分类Windows 的消息都是以WM_xxx 为名,WM_ 的意思是"Windows Message"。消息可以是来自硬件的「输入消息」,例如WM_LBUTTONDOWN,也可以是来自USER 模块的「窗口管理消息」,例如WM_CREATE。这些消息在MFC 程序中都是隐晦的(我的意思是不像在SDK 程序中那般显明),我们不必在MFC 程序中撰写switch case 指令,不必一一识别并处理由系统送过来的消息;所有消息都将依循Framework 制定的路线,并参照路中是否有拦路虎(你的消息映射表格)而流动。WM_PAINT 一定流往你的OnPaint 函数去,WM_SIZE 一定流往你的OnSize 函数去。
  所有的消息在MFC 程序中都是暗潮汹涌,但是表面无波。
 
MFC 把消息分为三大类:
■ 命令消息(WM_COMMAND):命令消息意味着「使用者命令程序做某些动作」。
凡由UI 对象产生的消息都是这种命令消息,可能来自菜单或加速键或工具栏
按钮,并且都以WM_COMMAND 呈现。如何分辨来自各处的命令消息?SDK程序主要靠消息的wParam 辨识之,MFC 程序则主要靠菜单项目的识别码(menu ID)辨识之-- 两者其实是相同的。什么样的类别有资格接受命令消息?凡衍生自CCmdTarget 之类别,皆有资格。从command target 的字面意义可知,这是命令消息的目的地。也就是说,凡衍生自CCmdTarget 者,它的骨子里就有了一种特殊的机制。把整张MFC 类别阶层图摊开来看,几乎构造应用程序的最重要的几个类别都衍生自CCmdTarget,剩下的不能接收消息的,是像CFile、CArchive、CPoint、CDao(数据库)、Collection Classes(纯粹数据处理)、GDI 等等「非主流」类别。
■ 标准消息- 除WM_COMMAND 之外,任何以WM_ 开头的都算是这一类。任何衍生自CWnd 之类别,均可接收此消息。
■ Control Notification - 这种消息由控制组件产生,为的是向其父窗口(通常是对话盒)通知某种情况。例如当你在ListBox 上选择其中一个项目,ListBox 就会产生LBN_SELCHANGE 传送给父窗口。这类消息也是以WM_COMMAND 形式呈现。
 
万流归宗Command Target(CCmdTarget)
  你可以在程序的许多类别之中设计拦路虎(我是指「消息映射表格」),接收并处理讯息。只要是CWnd 衍生类别,就可以拦下任何Windows 消息。与窗口无关的MFC 类别(例如CDocument 和CWinApp)如果也想处理消息,必须衍生自CCmdTarget,并且只可能收到WM_COMMAND 命令消息。
   会产生命令消息的,不外就是UI 对象:菜单项目和工具栏按钮都是。命令消息必须有一个对应的处理函数,把消息和其处理函数「绑」在一块儿,这动作称为CommandBinding,这个动作将由一堆宏完成。通常我们不直接手工完成这些宏内容,也就是说我们并不以文字编辑器一行一行地撰写相关的码,而是藉助于ClassWizard。一个Command Target 对象如何知道它可以处理某个消息?答案是它会看看自己的消息映射表。消息映射表使得消息和函数的对映关系形成一份表格,进而全体形成一张网,当Command Target 对象收到某个消息,便可由表格得知其处理函数的名称。
 
三个奇怪的宏,一张巨大的网
  试着思考这个问题:C++ 的继承与多态性质,使衍生类别与基础类别的成员函数之间有着特殊的关联。但这当中并没有牵扯到Windows 消息。的确,C++ 语言完全没有考虑Windows 消息这一回事(那当然)。如何让Windows 消息也能够在对象导向以及继承性质中扮演一个角色?既然语言没有支持,只好自求多福了。消息映射机制的三个相关宏就是MFC 自求多福的结果。
 「消息映射」是MFC 内建的一个消息分派机制,只要利用数个宏以及固定形式的写法,类似填表格,就可以让Framework 知道,一旦消息发生,该循哪一条路递送。每一个类别只能拥有一个消息映射表格,但也可以没有。下面是Scribble Document 建立消息映射表的动作:首先你必须在类别声明档(.H)声明拥有消息映射表格:
class CScribbleDoc : public CDocument
{
...
DECLARE_MESSAGE_MAP()
};
 
然后在类别实作档(.CPP)实现此一表格:
BEGIN_MESSAGE_MAP(CScribbleDoc, CDocument)
//{{AFX_MSG_MAP(CScribbleDoc)
ON_COMMAND(ID_EDIT_CLEAR_ALL, OnEditClearAll)
ON_COMMAND(ID_PEN_THICK_OR_THIN, OnPenThickOrThin)
...
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
这其中出现三个宏。第一个宏BEGIN_MESSAGE_MAP 有两个参数,分别是拥有此消息映射表之类别,及其父类别。第二个宏是ON_COMMAND,指定命令讯息的处理函数名称。第三个宏END_MESSAGE_MAP 作为结尾记号。至于夹在BEGIN_ 和END_ 之中奇奇怪怪的说明符号//}} 和//{{,是ClassWizard 产生的,也是用来给它自己看的。记住,前面我就说了,很少人会自己亲手键入每一行码,因为ClassWizard 的表现相当不俗。夹在BEGIN_ 和END_ 之中的宏,除了ON_COMMAND,还可以有许多种。标准的Windows 消息并不需要由我们指定处理函数的名称。标准消息的处理函数,其名称也是「标准」的(预设的),像是:
 
宏名称  对映消息  消息处理函数
ON_WM_CHAR     WM_CHAR    OnChar
ON_WM_CLOSE     WM_CLOSE  OnClose
ON_WM_CREATE  WM_CREATE  OnCreate
ON_WM_DESTROY WM_DESTROY OnDestroy
ON_WM_LBUTTONDOWN  WM_LBUTTONDOWN  OnLButtonDown
ON_WM_LBUTTONUP  WM_LBUTTONUP  OnLButtonUp
ON_WM_MOUSEMOVE  WM_MOUSEMOVE  OnMouseMove
ON_WM_PAINT  WM_PAINT  OnPaint
...
 
消息映射网的形成: BEGIN_/ON_/END_
前置准备工作完成了,接下来的课题是如何实现并填充图9-1 的数据结构内容。当然你马上就猜到了,使用的是另一组宏:
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_WM_PAINT()
ON_WM_CREATE()
...
END_MESSAGE_MAP()
 
如果没有把BEGIN_MESSAGE_MAP 宏中的两个参数(也就是类别本身及其父类别的名称)按照规矩来写,可能会发生什么结果呢?消息可能在不应该流向某个类别时流了过去,在应该被处理时却又跳离了。总之,完美的机制有了破绽。程序没当掉算你幸运!






MFC消息详解
VC++ 2009-05-31 17:19:40 阅读418 评论0   字号:大中小 订阅 
转载自:http://blog.csdn.net/Image_Graphics/archive/2006/11/22/1405436.aspx
1. 怎样使用MFC发送一个消息 
    首先,应获取接收消息的CWnd类对象的指针;
    然后,调用CWnd的成员函数SendMessage( )。
        LRESULT Res=pWnd->SendMessage(UINT Msg, WPARAM wParam, LPARAM lParam);
        pWnd指针指向目标CWnd类对象。变量Msg是消息,wParam和lParam变量包含消息的参数,如鼠标单击哪里或选择了什么菜单项。目标窗口返回的消息结果放在变量Res中。
        发送消息到一个没有CWnd类对象的窗口,可以用下列目标窗口的句柄直接调用Windows API:
        LRESULT Res=::SendMessage(HWND hWnd, UINT Msg,  WPARAM wParam, LPARAM lParam);
        这里的hWnd是目标窗口的句柄。
2. 怎样用MFC寄送一个消息
    用MFC寄送一个消息与发送一个消息几乎相同,但寄送时用PostMessage( ) ,而不是用SendMessage( );返回值Res也不一样,Res不是一个由目标窗口返回的值,而是一个布尔值,用来表示消息是否成功地放到消息队列中。
3. 检索一个寄送消息
    正常情况下,一旦消息被寄送后,应用程序在后台发送它。但是在特殊情况下,需要你自己去删除一个消息,例如想在应用程序接收到某种消息之前停止应用程序。有两种方法可以从应用程序消息队列中删除一个消息,但这两种方法都没有涉及MFC。
■ 第一种方法:在不干扰任何事情之下窥视消息队列,看看一个消息是否在那里。
    BOOL res=::PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg ) ;
■ 第二种方法:实际上是等待,一直等到一个新的消息到达队列为止,然后删除并返回该消息。
    BOOL res=::GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);
    在这两种方法中,变量hWnd指定要截获消息的窗口,如果该变量设为NULL,所有窗口消息将被截获。wMsgFilterMin和wMsgFilterMax变量与SendMessage( )中的变量Msg相对应,指定查看消息的范围。如果用"0,0",则所有的消息都将被截获。如果用WM_KEYFIRST,WM_KEYLAST或WM_MOUSEFIRST,WM_MOUSELAST,则所有键盘或鼠标的消息将被截获。wRemoveMsg变量指定PeekMessage( )是否应该真正地从队列中删除该消息(而GetMessage( )总是删除消息),该变量可以取两个值:
    ■ PM_REMOVE,PeekMessage( )将删除消息。
    ■ PM_NOREMOVE,PeekMessage( )将把消息留在队列里,并返回它的一个拷贝。
    当然,如果把消息留在消息队列中,然后再次调用PeekMessage( )查看相同类型的消息,则将返回完全相同的消息。
    lpMsg变量是一个指向MSG结构的指针,MSG包含检索到的消息。
    typedef struct tagMSG {
                        HWND hwnd; // window handle message is intended for
                        UINT message;
                        WPARAM wParam;
                        LPARAM lParam;
                        DWORD time; // the time the message was put in the queue
                        POINT pt; // the location of the mouse cursor when the
                                       // message was put in the queue
                        } MSG;
4. MFC怎样接收一个寄送的消息
    MFC处理一个寄送和发送消息的唯一明显不同是:寄送的消息要在应用程序的消息队列中花费一些时间,在消息泵(message pump)弹出它之前,它要一直在队列中。
    消息泵
    MFC应用程序中的消息泵在CWinApp的成员函数Run()中。应用程序开始运行时,Run()就被调用,Run()把时间分割成两部分。一部分用来执行后台处理,如取消临时CWnd对象;另一部分用来检查消息队列。当一个新的消息进来时,Run()抽取它——即用GetMessage( )从队列中取出该消息,运行两个消息翻译函数,然后用DispatchMessage( )函数调用该消息预期的目标窗口进程。
    消息泵调用的两个翻译函数是PreTranslateMessage( )和::TranslateMessage( )。目标窗口的MFC类可调用PreTranslateMessage在发送消息给它之前进行消息翻译,例如,CFrameWnd用PreTranslateMessage( )将加速键(如,Ctrl+S存储文件)转换为命令消息。翻译前的消息通常被处理掉,而翻译后的消息(如果有的话)将被重新寄送到队列里。::TranslateMessage是一个窗口函数,将原始键码转换为键字符。消息一旦被DispatchMessage()发送,MFC处理它就像处理SendMessage()发送的消息一样。
5. MFC怎样处理一个接收到的消息
    处理接收到的消息的目的非常简单:将消息指向一个函数,该函数通过消息中的消息标识符处理它。非MFC窗口用简单的case语句来实现该目标,每个case语句执行一些函数,或调用其他一些函数。
    MainWndProc(HWND hWnd, UINT message, W PARAM wParam,LPARAM lParam)
    {
        switch(message)
        {
        case WM_CREATE:
            : : :
        break;
        case WM_PAINT:
            : : :
        break;
        default:
        return(DefWindowProc(hWnd,message,wParam,lParam));
        }
        return(NULL);
    }
    任何遗漏的消息将被传输到一个默认的消息处理函数,但是,case语句不能很好地适应C++和封装技术。在C++环境中,要求消息被一个专门处理该类型消息的类的成员函数处理。因此,MFC不采用case语句,而采用更加复杂和回旋的方法,但它允许用私有类处理消息,而只需做下面三件事情:
    ■ 从将要接收消息的CWnd类对象派生类(对于命令消息是CCmdTarget)。
    ■ 在派生类中写一个处理消息的成员函数。
    ■ 在类中定义一个查找表(叫做消息映像),该表具有成员函数的条目和它要处理的消息的标识符。
    然后,MFC依次调用下面的函数,指引输入消息到处理函数。
    1) AfxWndProc( )接收消息,寻找消息所属的CWnd对象,然后调用AfxCallWndProc( )。
    2) AfxCallWndProc( )存储消息(消息标识符和参数)供未来参考,然后调用WindowProc( )。
    3) WindowProc( ) 发送消息给OnWndMsg( ) ,然后,如果消息未被处理,则发送给DefWindowproc( )。
    4) OnWndMsg( )要么为WM_COMMAND消息调用OnCommand( ),要么为WM_NOTIFY消息调用OnNotify( )。任何被遗漏的消息都将是一个窗口消息。OnWndMsg( )搜索类的消息映像,以找到一个能处理任何窗口消息的处理函数。如果OnWndMsg( )不能找到这样的处理函数,则把消息返回到WindowProc( ),由它将消息发送给DefWindowProc( )。
    5) OnCommand()查看这是不是一个控件通知(lParam不是NULL);如果它是,OnCommand( )就试图将消息映射到制造通知的控件;如果它不是一个控件通知,或者控件拒绝映射的消息,OnCommand( )就调用OnCmdMsg( )。
    6) OnNotify( )也试图将消息映射到制造通知的控件;如果映射不成功,OnNotify( )就调用相同的OnCmdMsg( )函数。
    7) 根据接收消息的类,OnCmdMsg( )将在一个称为命令传递(Command Routing)的过程中潜在地传递命令消息和控件通知。例如,如果拥有该窗口的类是一个框架类,则命令和通知消息也被传递到视图和文档类,并为该类寻找一个消息处理函数。
为什么要消息映像?
    这毕竟是C++语言;为什么OnWndMsg( )不为每个窗口消息调用一个预定义的虚拟函数?因为它太占CPU。若是那样,当扫描一个消息映像以加速该过程时,OnWndMsg( )可能会做出意想不到的事情,并陷入汇编器。注意通过重载WindowProc( )、OnWndMsg( )、OnCommand( )、OnNotify( ) 或OnCmdMsg( )可以修改这一过程。重载OnWndMsg( )可以在窗口消息被排序之前插入该过程。重载OnCommand( )或OnNotify( )可以在消息被反射之前插入该过程。
    ****************************************************************************************
       如何使用SendMessage自定义消息
SendMessage的基本结构如下:
SendMessage(
    HWND hWnd,  //消息传递的目标窗口或线程的句柄。
    UINT Msg, //消息类别(可以是一些系统消息,也可以是自己定义的消息)
    WPARAM wParam, //参数1 (WPARAM 其实是与UINT同类型) 
    LPARAM lParam); //参数2
其中一些参数的由来如下:
//typedef unsigned int UINT;
//typedef UINT WPARAM;
//typedef LONG LPARAM;
//typedef LONG LRESULT;
例如可以用以下语句:
::SendMessage(this->m_hWnd, WM_MY_DOSOME, (WPARAM) 0, (LPARAM) 0);
这里我发送的消息是本窗体接收的,所以句柄用:this->m_hWnd,
这里的消息类别WM_MY_DOSOME 是我自定义的。
在接收消息的窗体或线程所在的头文件里:
#define WM_MY_DOSOME WM_USER+1 // do something
表示要做一些事情。我们发了一个消息出去,那么接收方要能识别这个消息是干什么,就是通过消息类别来区分,并且开始去做这个消息对应要处理的事情。如下:
一:编写一个事情:
我们在接收窗体里定义一个这样的事情(过程),
afx_msg LRESULT DoSomeThing(WPARAM iParam1,LPARAM iParam2)
{
 MessageBox("收到消息了,我要开始做一些事情了。","收到",MB_OK);
 //在这里可以运用iParam1,iParam2 来做一些事情。
 return 0;
}
这个事情有3点大家要需要注意,非常重要:
 1:使用了afx_msg,并且要将afx_msg LRESULT DoSomeThing(WPARAM iParam1,LPARAM iParam2)
改写到头文件的 
//{{AFX_MSG 
    //。。。改写到这里,颜色会变成灰的。这一点非常重要。
//}}AFX_MSG
2:参数有2个,WPARAM iParam1,LPARAM iParam2,哪怕没有东西传进来也要写,不然会吃苦头的,vc里不会提醒你少写了一个,但一些莫名奇妙的事情会发生。
3:类型用 LRESULT,函数需要 return 0; 
二:让接收方知道什么时候做这个事情:
我们在
//{{AFX_MSG_MAP
   //。。。这里写上
   ON_MESSAGE(WM_MY_DOSOME,DoSomeThing)
//}}AFX_MSG_MAP
到这里,当你用SendMessage,发了一个WM_MY_DOSOME 类型的消息过来的时候,接收方就会去调用DoSomeThing(WPARAM iParam1,LPARAM iParam2)函数。
*************************************************************************************
如何使用PostMessage自定义消息
我们来看看这个消息的函数原型:
在CWnd::PostMessage中定义
BOOL PostMessage (
   UINT message,
   WPARAM wParam = 0,
   LPARAM lParam = 0 
);
Parameters:
message 
Specifies the message to be posted.
wParam 
Specifies additional message information. The content of this parameter depends on the message being posted.
lParam 
Specifies additional message information. The content of this parameter depends on the message being posted.
Return Value
Nonzero if the message is posted; otherwise 0. 
所以一般的当点击时,消息触发就会发送一个WM_MYMESSAGE消息,故而调用CownermessageDlg::OnMyMessage(WPARAM wParam, LPARAM lParam)这个函数。
 
如何在VC++中加入自定义消息,我的做法:这里我以一个对话框为例子,环境:VS 2008,新建一个工程,选择对话框:以编译器给出的对话框为蓝本,自己新建一个按钮如图:
 
把名字改成如图的,其他比如ID什么的都不改了,默认。
1 首先在ownermessageDlg.h中定义消息:例如
define WM_MYMESSAGE (WM_USER+100)
 
2 在.h文件中,加入消息响应函数,如:
afx_msg LRESULT OnMyMessage(WPARAM w,LPARAM l); 
该函数有规定的格式:
afx_msg LRESULT Function_Name(WPARAM w,LPARAM l);
 
3 在ownermessageDlg.cpp中加入消息响应宏
在响应该消息的类中,在消息响应块
BEGIN_MESSAGE_MAP(CownermessageDlg, CDialog)
   ON_WM_SYSCOMMAND()
   ON_WM_PAINT()
   ON_WM_QUERYDRAGICON()
   //}}AFX_MSG_MAP
END_MESSAGE_MAP() 
中加入以下语句:
ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)
成为如下样式:
BEGIN_MESSAGE_MAP(CownermessageDlg, CDialog)
   ON_WM_SYSCOMMAND()
   ON_WM_PAINT()
   ON_WM_QUERYDRAGICON()
   ON_MESSAGE(WM_MYMESSAGE,OnMyMessage)
   //}}AFX_MSG_MAP
END_MESSAGE_MAP() 
 
4 在.CPP文件中,编辑消息响应函数,如下:
LRESULT CownermessageDlg::OnMyMessage(WPARAM wParam, LPARAM lParam) 
{
   MessageBox(_T("My Message!"));
   return 1;
}
 
5.在对话框中为新增的按钮添加单击响应,则ownermessageDlg.cpp中的MESSAGE_MAP将变成:
BEGIN_MESSAGE_MAP(CownermessageDlg, CDialog)
   ON_WM_SYSCOMMAND()
   ON_WM_PAINT()
   ON_WM_QUERYDRAGICON()
   ON_MESSAGE(WM_MYMESSAGE,OnMyMessage)
   //}}AFX_MSG_MAP
   ON_BN_CLICKED(IDC_BUTTON1, &CownermessageDlg::OnBnClickedButton1)
END_MESSAGE_MAP()
 
void CownermessageDlg::OnBnClickedButton1()
{
   // TODO: Add your control notification handler code here
   //。。。在这里增加 PostMessage 函数投递消息
   PostMessage(WM_MYMESSAGE, IDC_BUTTON1);   
                       // PostMessage(……)就是响应WM_MYMESSAGE消息
                      // 其实这样写也是可以的:PostMessage(WM_MYMESSAGE);
}














消息分类
 Windows的消息都是以WM_xxx为名,WM_的意思是”Windows Message”.
MFC把消息分为三大类:
# 命令消息(WM_COMMAND):命令消息意味着”使用者命令程序做某些操作”.凡是UI对象产生的消息都是这种命令消息,可能来自菜单或加速键或工具栏按钮,并且都以WM_COMMAND呈现.如何分辨来自各处的命令消息?SDK程序主要靠消息的wParam识别之,MFC程序则主要靠菜单项的识别码(menu ID)识别之,其实两者是相同的.
 什么样的类有资格接受命令消息?凡派生自CCmdTarget的类皆有资格.从command target的字面意思可知,这是命令消息的目的地.也就是说,凡派生自CCmdTarget的类,就有一种特殊的机制.
# 标准消息:除WM_COMMAND之外,任何以WM_开头的都酸这一类.任何派生在CWnd的类,均可接受此消息.
# Control Notification:这种消息由控件产生,为的是其父窗口(通常是对话框)通知某种情况.例如当字ListBox上选择其中一个项目,ListBox就会产生LBN_SEDCHANGE传送给父窗口,这类消息也是以WM_COMMAND形式呈现.
 
Command Target( CCmdTarget )
 程序中的许多类都可以接受并处理消息.只要是CWnd的派生类,就可以拦下任何Windows消息.与窗口无关的MFC类,如果也想处理消息,必须派生自CCmdTarget,并且只可能接受到WM_COMMAND命令消息.
 会产生命令消息的,不外就是UI对象:菜单项和工具栏都是.命令消息必须有一个对应的处理函数.把消息和其处理函数”绑”在一起,这种操作称为:CommandBinding,这个操作将由一堆宏完成.
一个Command Target对象根据自己的消息映射表知道它可以处理某个消息.消息映射表使得消息和函数的对应关系形成一份表格,进而全体形成一张网.
 
三个奇怪的宏
 “消息映射”是MFC内建的一个消息分派机制,只要利用数个宏以及固定形式的写法,类似填表格,就可以让Framewok知道,一旦消息发生,该走哪一条路.
 每个类只能拥有一个消息映射表格,但也可以没有.
 第一个宏BEGIN_MESSAGE_MAP有两个参数,分别是拥有此消息映射表之类,及其父类.第二个宏是ON_COMMAND,指定命令消息的处理函数名称.第三个宏END_MESSAGE_MAP作为结尾记号.
 
DECLARE_MESSAGE_MAP宏
 消息映射的本质其实是一个巨大的数据结构,用来为诸如WM_PAINT这样的标准消息决定流动路线,使它得以流到父类去:也用来为WM_COMMAND这个特殊消息决定流动路线,使它能够七怪八弯地流到类层次结构的旁支去.
#define DECLARE_MESSAGE_MAP() \
Private:
    Static const AFX_MESSAGE_ENTRY _messageEntries[];\
Protected:
    Static AFX_DATA const AFX_MSGMAP messageMap;\
    Virtual const AFX_MSGMAP * GetMessageMap() const; \
其中, AFX_MESSAGE_ENTRY是一个结构体,主要作用是让消息对应于其函数.
AFX_MSGMAP也是一个结构体,它里面有个参数pBaseMap是一个指向”基类之映射表”的指针,它提供了一个走访整个继承链表的方法,有效地实现消息映射的继承性.派生类将自动地继承其基类中所处理的消息,意思是,若基类处理过A消息,其派生类即使未设计A消息之消息映射表项目,也具有对A消息的处理能力.
 消息的传递


消息的流动路线已隐隐有脉络可寻,此脉络是指由BEGIN_MESSAGE_MAP和END_MESSAGE_END以及许多ON_WM_XXX宏所构成的消息映射网。
整个MFC中,拥有虚函数WindowProc者包括CWnd, CcontrolBar, ColeControl, ColePropertyPage,Cdialog,CreflectorWnd,CparkingWind.一般窗口都派生自CWnd。
 
一般Windows消息
 CWnd::windowProc调用的OnWndMsg是用来分辨并处理消息的专职函数:如果是命令消息,就交给OnCommand处理;若是通知消息(Notification),就交给OnNotify处理 




本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/dadalan/archive/2008/12/13/3507102.aspx














MFC消息映射与消息传递


 
lizhongjun1984 
30位粉丝 

1楼


                 1.Windows消息概览      
对于消息,程序员应该不陌生。WM_CREATE,WM_PAINT等等都是Windows程序设计中必不可缺少的组成部分。大多有关MFC Win32编程的书籍都将Windows消息分为三大类即: 
* 标准消息:   任何以WM_开头的消息(WM_COMMAND除外);如:WM_QUIT,WM_CREATE; 
* 命令消息:   WM_COMMAND; 
* 子窗口通知: 由子窗口(大多为控件)产生并发送到该控件所属的父窗口的消息。(注意:此类消息也以WM_COMMAND形式出现) 






                     2.MFC消息映射网的组成元素  
                        
class theClass  
{  
     ...//  
    DECLARE_MESSAGE_MAP()  
};  


//in xx.cpp  
BEGIN_MESSAGE_MAP(theClass, baseClass)  
     ON_COMMAND( ID_MYCMD, OnMyCommand )  
     ON_WM_CREATE()  
END_MESSAGE_MAP()  
...//  
    
//这些宏的定义如下:  
//in Afxwin.h  
#define DECLARE_MESSAGE_MAP() \  
private: \  
    static const AFX_MSGMAP_ENTRY _messageEntries[]; \  
protected: \  
    static const AFX_MSGMAP messageMap; \  
    static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \  
    virtual const AFX_MSGMAP* GetMessageMap() const; \  


#define BEGIN_MESSAGE_MAP(theClass, baseClass) \  
    const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \  
      { return &theClass::messageMap; } \  
    const AFX_MSGMAP* theClass::GetMessageMap() const \  
      { return &theClass::messageMap; } \  
     AFX_COMDAT const AFX_MSGMAP theClass::messageMap = \  
     { &baseClass::GetThisMessageMap, &theClass::_messageEntries[0] }; \  
     AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \  
     { \  


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


2009-9-19 16:55 
回复 


 
lizhongjun1984 
30位粉丝 

2楼




DECLARE_MESSAGE_MAP()宏为每个类添加了四个东东, 
消息映射表messageMap、消息入口结构数组_messageEntries[] 以及两个获取消息映射表的函数,注意其中一个为virtual 
BEGIN_MESSAGE_MAP(theClass, baseClass)和END_MESSAGE_MAP()宏则初始化了它们 






                          3.MFC消息映射表      
下面我们看看消息映射表messageMap和消息入口结构AFX_MSGMAP_ENTRY的定义: 


//in Afxwin.h  
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_PTR nSig;   // signature type (action) or pointer to message #  
     AFX_PMSG pfn;    // routine to call (or special value)  
};  




struct AFX_MSGMAP  
{  
     #ifdef _AFXDLL  
    const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();//基类的映射表指针,本程序将使用  
     #else  
    const AFX_MSGMAP* pBaseMap;  
     #endif  
    const AFX_MSGMAP_ENTRY* lpEntries;  
};  


其中AFX_MSGMAP结构中包含一个基类的映射表指针和一个指向消息入口结构AFX_MSGMAP_ENTRY的指针。 
  




                         4.MFC消息映射宏展开     
    
上面的宏展开后代码如下:(以CMaimFrame为例) 


//in MaimFrm.h  
class CMaimFrame : public CFrameWnd  
{  
   ...//  
private:  
     static const AFX_MSGMAP_ENTRY _messageEntries[];  
protected:  
    static const AFX_MSGMAP messageMap;  
    static const AFX_MSGMAP* PASCAL GetThisMessageMap();  
    virtual const AFX_MSGMAP* GetMessageMap() const;  
};  


//in MaimFrm.cpp  
const AFX_MSGMAP* PASCAL CMaimFrame::GetThisMessageMap()  
{  
    return &CMaimFrame::messageMap;  
}  
const AFX_MSGMAP* CMaimFrame::GetMessageMap() const  
{  
    return &CMaimFrame::messageMap;  
}  
AFX_COMDAT const AFX_MSGMAP theClass::messageMap =  
{  
     &CFrameWnd::GetThisMessageMap,  
     &CMaimFrame::_messageEntries[0]  
};  
AFX_COMDAT const AFX_MSGMAP_ENTRY CMaimFrame::_messageEntries[] =  
{  
     {          ...//                       }  
               ...  
     {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }  
};  


2009-9-19 16:55 
回复 


221.221.157.* 3楼
众所周知,windows是基于消息驱动的,作好消息处理是WINDOWS编程的关键任务之一,用VC制作WINDOWS程式同样离不开消息的处理。虽然VC++6的类向导可以完成绝大部分工作,但不幸的是,它并不能完成所有的工作。这就要求我们对 VC中消息的处理有一个比较清淅的认识。只有这样才可能在必要的时候亲自动手完成一些复杂的消息映射处理。  


  在MFC中消息是通过一种所谓的消息映射机制来处理的。其实质是一张消息及其处理函数的一一对应表以及分析处理这张表的应用框架内部的一些程序代码.这样的好处是可以避免像早期的SDK编程一样需要罗列一大堆的CASE语句来处理各种消息.由于不同种类的消息其处理方法是不同的,所以我们有必要先弄清楚 WINDOWS消息的种类。   


  背景:  


  WINDOWS 消息的种类  


  WINDOWS中消息主要有以下三种类型:  


  1、标准的WINDOWS消息:这类消息是以WM_为前缀,不过WM_COMMAND例外。 例如: WM_MOVE、WM_QUIT等.  


  2、命令消息:命令消息以WM_COMMAND为消息名.在消息中含有命令的标志符ID,以区分具体的命令.由菜单,工具栏等命令接口对象产生.  


  3、控件通知消息:控件通知消息也是以WM_COMMAND为消息名.由编辑框,列表框,子窗口发送给父窗口的通知消息.在消息中包含控件通知码.以区分具体控件的通知消息.  


  其中标准的WINDOWS消息及控件通知消息主要由窗口类即直接或间接由CWND类派生类处理.相对标准WINDOWS消息及控件通知消息而言,命令消息的处理对象范围就广得多.它不仅可以由窗口类处理,还可以由文档类,文档模板类及应用类所处理。  
   
  方法:  


  不同种类消息的映射方法。  


  在以上三种消息中,标准的WINDOWS消息映射是相当简单的。可直接通过类向导完成不同消息的映射处理,所以不在本文讨论之列。  


  凡是从CcmdTarget类派生的类都可以有消息映射.消息映射包括如下两方面的内容:  


  在类的定义文件中(.H)中加上一条宏调用:  


  DECLARE_MESSAGE_MAP()  


  通常这条语句中类定义的最后.  


  在类的实现文件(.CPP)中加上消息映射表:  


  BEGIN_MESSAGE_MAP(类名,父类名)  


    ………..  


   消息映射入口项.  


   ……….   


   END_MESSAGE_MAP( )  


  幸运的是除了某些类(如没有基类的类或直接从CobjectO类派生的类)外.其它许多类均可由类向导生成.尽管生成的类只是一个框架,需要我们补充内容.但消息映射表已经为我们加好了.只是入口项有待我们加入.


























































MFC技术内幕系列之(四)---MFC消息映射与消息传递内幕
MFC和VC++ 2008-08-19 19:33:17 阅读55 评论0   字号:大中小 订阅 
引言:     Windows操作系统是以消息为基础,事件驱动的。作为程序员了解操作系统的消息传递机制是非常必要的。Microsoft的MFC又它自己的一套支持Windows操作系统消息机制的技术--消息映射(Message Mapping)和命令传递(Command Routing),在这篇文章中我就详细的挖掘一下MFC的消息映射技术以及命令传递技术。 
正文:                        ///                        /*     1.Windows消息概览      */                        //     对于消息,程序员应该不陌生。WM_CREATE,WM_PAINT等等都是Windows程序设计中必不可缺少的组成部分。大多有关MFC Win32编程的书籍都将Windows消息分为三大类即:     * 标准消息:   任何以WM_开头的消息(WM_COMMAND除外);如:WM_QUIT,WM_CREATE;     * 命令消息:   WM_COMMAND;     * 子窗口通知: 由子窗口(大多为控件)产生并发送到该控件所属的父窗口的消息。(注意:此类消息也                    以WM_COMMAND形式出现)     消息类型我们已经了解了,下面我们就来看看消息映射是如何工作的:                        //                        /* 2.MFC消息映射网的组成元素 */                        //       我的前几篇文章中涉及到了MFC内部建立的一些“网”技术,比如“执行期类型识别网”等,这回我们将建立一个消息映射网,这个网的建立与前面相同的是它也利用了一些神秘的宏。下面我们就来掀开它们的神秘面纱。    我们先简单地看看这些宏在程序源文件中的什么地方?    //in xx.h    class theClass {       ...//      DECLARE_MESSAGE_MAP()    };    //in xx.cpp    BEGIN_MESSAGE_MAP(theClass, baseClass) ON_COMMAND( ID_MYCMD, OnMyCommand )         ON_WM_CREATE()    END_MESSAGE_MAP()    ...//       这些宏的定义如下:    //in Afxwin.h    #define DECLARE_MESSAGE_MAP() \    private: \ static const AFX_MSGMAP_ENTRY _messageEntries[]; \    protected: \ static const AFX_MSGMAP messageMap; \ static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \ virtual const AFX_MSGMAP* GetMessageMap() const; \
   #define BEGIN_MESSAGE_MAP(theClass, baseClass) \ const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \    { return &theClass::messageMap; } \ const AFX_MSGMAP* theClass::GetMessageMap() const \    { return &theClass::messageMap; } \ AFX_COMDAT const AFX_MSGMAP theClass::messageMap = \ { &baseClass::GetThisMessageMap, &theClass::_messageEntries[0] }; \ AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \ { \
   #define END_MESSAGE_MAP() \    {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ }; \    DECLARE_MESSAGE_MAP()宏为每个类添加了四个东东,包括那个重要的消息映射表messageMap和消息入口结构数组AFX_MSGMAP_ENTRY _messageEntries[];BEGIN_MESSAGE_MAP(theClass, baseClass)和END_MESSAGE_MAP()宏则初始化了它们,随后我将带领大家看看这个初始化过程。
                       ///                        /*      3.MFC消息映射表       */                        //    下面我们看看消息映射表messageMap和消息入口结构AFX_MSGMAP_ENTRY的定义: //in Afxwin.h 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_PTR nSig;   // signature type (action) or pointer to message # AFX_PMSG pfn;    // routine to call (or special value) };
struct AFX_MSGMAP { #ifdef _AFXDLL const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();//基类的映射表指针,本程序将使用 #else const AFX_MSGMAP* pBaseMap; #endif const AFX_MSGMAP_ENTRY* lpEntries; }; 其中AFX_MSGMAP结构中包含一个基类的映射表指针和一个指向消息入口结构AFX_MSGMAP_ENTRY的指针。
                       /                        /*    4.MFC消息映射宏展开     */                        /
上面的宏展开后代码如下:(以CMaimFrame为例)    //in MaimFrm.h    class CMaimFrame : public CFrameWnd {     ...//     private:         static const AFX_MSGMAP_ENTRY _messageEntries[];     protected: static const AFX_MSGMAP messageMap; static const AFX_MSGMAP* PASCAL GetThisMessageMap(); virtual const AFX_MSGMAP* GetMessageMap() const; }; //in MaimFrm.cpp     const AFX_MSGMAP* PASCAL CMaimFrame::GetThisMessageMap()    { return &CMaimFrame::messageMap; }     const AFX_MSGMAP* CMaimFrame::GetMessageMap() const    { return &CMaimFrame::messageMap; }     AFX_COMDAT const AFX_MSGMAP theClass::messageMap = { &CFrameWnd::GetThisMessageMap, &CMaimFrame::_messageEntries[0] };     AFX_COMDAT const AFX_MSGMAP_ENTRY CMaimFrame::_messageEntries[] =         {           {         ...//                      }                     ...           {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } };    相信大家看了后大多源代码都能够理解,但是AFX_MSGMAP_ENTRY结构还是能够引起我们的兴趣的。下面让我们看看_messageEntries[]是如何被初始化的:    我们还是举例来说明吧!(还是CMainFrame为例吧)    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_CREATE()         ON_COMMAND( ID_MYCMD, OnMyCommand )    END_MESSAGE_MAP()       大家看到了夹在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP()之间的宏,这些宏可分为基类,一类是Windows预定义消息宏(比如:ON_WM_CREATE(),ON_WM_DESTROY()等定义在afxmsg_.h中的Message map tables for Windows messages),一类是自定义的ON_COMMAND宏以及类似的如ON_UPDATE_COMMAND_UI等宏 。    //in afxmsg_.h    // Message map tables for Windows messages    #define ON_WM_CREATE() \ { WM_CREATE, 0, 0, 0, AfxSig_is, \    (AFX_PMSG) (AFX_PMSGW) \    (static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > (OnCreate)) },    #define ON_COMMAND(id, memberFxn) \ { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, \    static_cast<AFX_PMSG> (memberFxn) },
   AFX_MSGMAP_ENTRY结构初始化过程:    AFX_COMDAT const AFX_MSGMAP_ENTRY CMaimFrame::_messageEntries[] =         {           { WM_CREATE, 0, 0, 0, AfxSig_is,    (AFX_PMSG) (AFX_PMSGW)               (static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > (OnCreate)) },           { WM_COMMAND, CN_COMMAND, (WORD)ID_MYCMD, (WORD)ID_MYCMD, AfxSigCmd_v, \    static_cast<AFX_PMSG> ( OnMyCommand) },                  {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } };    现在一切都清楚了吧!
                       //                        /*    5.MFC消息映射网的连接   */                        //    MFC消息映射网的连接也是在初始化过程中完成的,其建立过程很简单。主要有关成员有:    private:         static const AFX_MSGMAP_ENTRY _messageEntries[];    protected: static const AFX_MSGMAP messageMap;    和BEGIN_MESSAGE_MAP()宏开后的    AFX_COMDAT const AFX_MSGMAP theClass::messageMap = { &baseClass::GetThisMessageMap, &theClass::_messageEntries[0] };    该宏将pfnGetBaseMap赋值为其基类的messageMap地址;将AFX_MSGMAP_ENTRY* lpEntries赋值为该类的 _messageEntries[0];    这样一个类不仅拥有本类的messageMap,而且还拥有其基类的messageMap,依此类推MFC消息映射网的连接就建立了,最终的基类都是CCmdTarget
                       //                        /*    6.MFC命令传递机制概述   */                        //    有了MFC消息映射网就为命令传递打下了坚实的基础。Win32API程序员都熟悉传统的API编程都有一个 WndProc回调函数来集中处理各种的Windows消息,然而在MFC中我们却怎么也看不见WndProc回调函数的踪影了。而MFC命令传递机制恰是为了将各种消息“拐弯抹角”地送到各个对应的"WndProc"函数的一种技术。MFC是如何将各种Windows消息准确的送到期该区的地方呢? MFC使用了钩子函数等技术来保证其准确性和全面性。    不知大家是否还记得MFC在注册窗口类时作了什么?    BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)//部分源代码 {         ...//         // common initialization WNDCLASS wndcls; memset(&wndcls, 0, sizeof(WNDCLASS));   // start with NULL defaults wndcls.lpfnWndProc = DefWindowProc; ...//    } 可以看到MFC注册时将wndcls.lpfnWndProc赋值为DefWindowProc函数,那么实际上是否消息都是由它处理的呢?显然不可能,MFC又不知道我们要处理什么消息。那么还有什么函数能帮我们处理消息呢?这就是我要讲的; 在MFC技术内幕系列之(二)----《 MFC文档视图结构内幕》中曾提到“CWnd::CreateEx函数调用了AfxHookWindowCreate(this);后者是干什么的呢?其实它与消息映射和命令传递有关。”    实际上MFC命令传递机制就是从这里AfxHookWindowCreate(this)开始的。还是老办法看看代码吧: //in wincore.cpp void AFXAPI AfxHookWindowCreate(CWnd* pWnd) { ...// if (pThreadState->m_hHookOldCbtFilter == NULL) {    pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,     _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());    if (pThreadState->m_hHookOldCbtFilter == NULL)     AfxThrowMemoryException(); } ...// } 该函数设置了消息钩子,其钩子处理函数为_AfxCbtFilterHook;这里简介一下钩子函数:用我的理解,钩子就是能给你一个在某个消息到达其默认的处理函数之前处理该消息机会的工具。与钩子有关的函数主要有三个: HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId ); LRESULT CallNextHookEx(HHOOK hhk, int nCode,    WPARAM wParam,   LPARAM lParam ); BOOL UnhookWindowsHookEx( HHOOK hhk   // handle to hook procedure); 关于这三个函数我也不想多解释,大家看看MFC有关文档吧!这里主要讲的是在AfxHookWindowCreate(CWnd* pWnd)中注册的钩子函数的类型(hook type)--WH_CBT; 有关WH_CBT,MFC文档时如是说的: Installs a hook procedure that receives notifications useful to a computer-based training (CBT) application. The system calls this function(这里指的是_AfxCbtFilterHook) before activating, creating, destroying, minimizing, maximizing, moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event from the system message queue; before setting the keyboard focus; or before synchronizing with the system message queue. A computer-based training (CBT) application uses this hook procedure to receive useful notifications from the system.                        /                        /*    7.偷换“窗口函数”      */                        /
   这会知道了吧,当发生窗口(包括子窗口)发生被激活,创建,撤销,最小化等时候,应用程序将调用 _AfxCbtFilterHook函数;下面就让我们看看_AfxCbtFilterHook函数做了个啥: //in wincore.cpp // Window creation hooks LRESULT CALLBACK _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam) { _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); if (code != HCBT_CREATEWND) {    // wait for HCBT_CREATEWND just pass others on...    return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,     wParam, lParam); }
...//   CWnd* pWndInit = pThreadState->m_pWndInit;    HWND hWnd = (HWND)wParam;    WNDPROC oldWndProc;    if (pWndInit != NULL)    {                  #ifdef _AFXDLL     AFX_MANAGE_STATE(pWndInit->m_pModuleState);                  #endif
    // the window should not be in the permanent map at this time     ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);
    // connect the HWND to pWndInit...     pWndInit->Attach(hWnd);     // allow other subclassing to occur first     pWndInit->PreSubclassWindow();//***
    WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();     ASSERT(pOldWndProc != NULL);
    // subclass the window with standard AfxWndProc     WNDPROC afxWndProc = AfxGetAfxWndProc();//***     oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,      (DWORD_PTR)afxWndProc);//***     ASSERT(oldWndProc != NULL);     if (oldWndProc != afxWndProc)      *pOldWndProc = oldWndProc;
    pThreadState->m_pWndInit = NULL;    } ...// lCallNextHook: LRESULT lResult = CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,    wParam, lParam);
#ifndef _AFXDLL if (bContextIsDLL) {    ::UnhookWindowsHookEx(pThreadState->m_hHookOldCbtFilter);    pThreadState->m_hHookOldCbtFilter = NULL; } #endif return lResult; }
void CWnd::PreSubclassWindow() { // no default processing } // always indirectly accessed via AfxGetAfxWndProc WNDPROC AFXAPI AfxGetAfxWndProc() { #ifdef _AFXDLL return AfxGetModuleState()->m_pfnAfxWndProc; #else return &AfxWndProc; #endif }
原来_AfxCbtFilterHook函数偷换了窗口函数,将原来的DefWndProc换成AfxWndProc函数.                                                /*   8.MFC的“WndProc”函数   */                            AfxWndProc函数就可以说是MFC的“WndProc”函数,它也是MFC中消息传递的开始,其代码如下: //in wincore.cpp // The WndProc for all CWnd's and derived classes LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) { // special message which identifies the window as using AfxWndProc if (nMsg == WM_QUERYAFXWNDPROC)    return 1;
// all other messages route through message map CWnd* pWnd = CWnd::FromHandlePermanent(hWnd); ASSERT(pWnd != NULL); ASSERT(pWnd->m_hWnd == hWnd); return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam); }
// Official way to send message to a CWnd LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0) { _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); MSG oldState = pThreadState->m_lastSentMsg;   // save for nesting pThreadState->m_lastSentMsg.hwnd = hWnd; pThreadState->m_lastSentMsg.message = nMsg; pThreadState->m_lastSentMsg.wParam = wParam; pThreadState->m_lastSentMsg.lParam = lParam;          ...//        // in debug builds and warn the user. LRESULT lResult; TRY { #ifndef _AFX_NO_OCC_SUPPORT    // special case for WM_DESTROY    if ((nMsg == WM_DESTROY) && (pWnd->m_pCtrlCont != NULL))     pWnd->m_pCtrlCont->OnUIActivate(NULL); #endif                // special case for WM_INITDIALOG    CRect rectOld;    DWORD dwStyle = 0;    if (nMsg == WM_INITDIALOG)     _AfxPreInitDialog(pWnd, &rectOld, &dwStyle);
   // delegate to object's WindowProc    lResult = pWnd->WindowProc(nMsg, wParam, lParam);//***
   // more special case for WM_INITDIALOG    if (nMsg == WM_INITDIALOG)     _AfxPostInitDialog(pWnd, rectOld, dwStyle); } CATCH_ALL(e) {    ...// } END_CATCH_ALL
pThreadState->m_lastSentMsg = oldState; return lResult; }
// main WindowProc implementation LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // OnWndMsg does most of the work, except for DefWindowProc call LRESULT lResult = 0; if (!OnWndMsg(message, wParam, lParam, &lResult))    lResult = DefWindowProc(message, wParam, lParam); return lResult; }
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult) { LRESULT lResult = 0; union MessageMapFunctions mmf; mmf.pfn = 0;         // special case for commands if (message == WM_COMMAND) {    if (OnCommand(wParam, lParam))    {     lResult = 1;     goto LReturnTrue;    }    return FALSE; }         // special case for notifies if (message == WM_NOTIFY) {    NMHDR* pNMHDR = (NMHDR*)lParam;    if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))     goto LReturnTrue;    return FALSE; }         ...//         const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap(); UINT iHash; iHash = (LOWORD((DWORD_PTR)pMessageMap) ^ message) & (iHashMax-1); AfxLockGlobals(CRIT_WINMSGCACHE); AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash]; const AFX_MSGMAP_ENTRY* lpEntry; if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap) {    // cache hit    lpEntry = pMsgCache->lpEntry;    AfxUnlockGlobals(CRIT_WINMSGCACHE);    if (lpEntry == NULL)     return FALSE;
   // cache hit, and it needs to be handled    if (message < 0xC000)     goto LDispatch;    else     goto LDispatchRegistered; } else {    // not in cache, look for it    pMsgCache->nMsg = message;    pMsgCache->pMessageMap = pMessageMap;
#ifdef _AFXDLL    for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;     pMessageMap = (*pMessageMap->pfnGetBaseMap)()) #else    for (/* pMessageMap already init'ed */; pMessageMap != NULL;     pMessageMap = pMessageMap->pBaseMap) #endif    {     // Note: catch not so common but fatal mistake!!     //      BEGIN_MESSAGE_MAP(CMyWnd, CMyWnd) #ifdef _AFXDLL     ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)()); #else     ASSERT(pMessageMap != pMessageMap->pBaseMap); #endif
    if (message < 0xC000)     {      // constant window message      if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,       message, 0, 0)) != NULL)      {       pMsgCache->lpEntry = lpEntry;       AfxUnlockGlobals(CRIT_WINMSGCACHE);       goto LDispatch;      }     }     else     {      // registered windows message            lpEntry = pMessageMap->lpEntries;     while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) != NULL)      {       UINT* pnID = (UINT*)(lpEntry->nSig);       ASSERT(*pnID >= 0xC000 || *pnID == 0);        // must be successfully registered       if (*pnID == message)       {        pMsgCache->lpEntry = lpEntry;        AfxUnlockGlobals(CRIT_WINMSGCACHE);        goto LDispatchRegistered;       }       lpEntry++;      // keep looking past this one      }     }    }
   pMsgCache->lpEntry = NULL;    AfxUnlockGlobals(CRIT_WINMSGCACHE);    return FALSE; }    LDispatch: ASSERT(message < 0xC000);
mmf.pfn = lpEntry->pfn;
switch (lpEntry->nSig) { default:    ASSERT(FALSE);    break;
case AfxSig_b_D_v:    lResult = (this->*mmf.pfn_b_D)(CDC::FromHandle(reinterpret_cast<HDC>(wParam)));    break;         ...// LDispatchRegistered:    // for registered windows messages ASSERT(message >= 0xC000); ASSERT(sizeof(mmf) == sizeof(mmf.pfn)); mmf.pfn = lpEntry->pfn; lResult = (this->*mmf.pfn_l_w_l)(wParam, lParam);
LReturnTrue: if (pResult != NULL)    *pResult = lResult; return TRUE; }    该源代码有整整700行,不知道是不是MFC源码中最长的一个函数,在这里我只列出部分有代表性的源码。从源代码中大家也可以看得出该函数主要用于分辨消息并将消息交于其处理函数。MFC为了加快消息得分检速度在AfxFindMessageEntry函数中甚至使用了汇编代码。在CWnd::OnWndMsg中得不到处理的消息则交给CWnd::DefWindowProc(相当于MFC的默认DefWindowProc函数)处理,其代码为: // Default CWnd implementation LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam) { if (m_pfnSuper != NULL)    return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);
WNDPROC pfnWndProc; if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)    return ::DefWindowProc(m_hWnd, nMsg, wParam, lParam); else    return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam); } CWnd::DefWindowProc这调用了传统win32程序员熟悉的::DefWindowProc(m_hWnd, nMsg, wParam, lParam); 在挖掘上面源代码的同时你也看到了消息的传递路线。在MFC中CWnd以及派生于CWnd的类都拥有虚函数 CWnd::WndProc(...)。                        /                        /* 9.MFC各类消息的"行走路径 " */                        /    在篇头我就将消息分了类而且到目前我们已经了解了消息的传递机制了,下面我就具体的某类消息来看看其传递路径。    无论什么消息都有AfxWndProc进入,到达CWnd::OnWndMsg函数分检消息;    对于标准消息:        标准消息一般都沿其消息映射表从本类到父类逐层查找其处理函数,若没查到着交给::DefWindowProc        处理。    对于命令消息:        命令消息除了能像标准消息一样从本类到父类逐层查找其处理函数外,有时他们可能还要拐弯。        再回头看看CWnd::OnWndMsg源码是如何处理WM_COMMAND消息的:         if (message == WM_COMMAND) {    if (OnCommand(wParam, lParam))    {     lResult = 1;     goto LReturnTrue;    }    return FALSE; }         原来交给了CWnd::OnCommand(wParam, lParam)         下面看看一个SDI程序中命令消息在Frame,View,Document以及CWinApp对象之间的传递路线。         //in winfrm.cpp         BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam) // return TRUE if command invocation was attempted        { HWND hWndCtrl = (HWND)lParam; UINT nID = LOWORD(wParam);
CFrameWnd* pFrameWnd = GetTopLevelFrame(); ASSERT_VALID(pFrameWnd); if (pFrameWnd->m_bHelpMode && hWndCtrl == NULL &&    nID != ID_HELP && nID != ID_DEFAULT_HELP && nID != ID_CONTEXT_HELP) {    // route as help    if (!SendMessage(WM_COMMANDHELP, 0, HID_BASE_COMMAND+nID))     SendMessage(WM_COMMAND, ID_DEFAULT_HELP);    return TRUE; }
// route as normal command return CWnd::OnCommand(wParam, lParam);         }                 //in wincore.cpp         // CWnd command handling         BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam) // return TRUE if command invocation was attempted        { ...//          return OnCmdMsg(nID, nCode, NULL, NULL);//CFrameWnd::OnCmdMsg        }              // CFrameWnd command/message routing        BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)       { CPushRoutingFrame push(this);
// pump through current view FIRST CView* pView = GetActiveView(); if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))    return TRUE;
// then pump through frame if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))    return TRUE;
// last but not least, pump through app CWinApp* pApp = AfxGetApp(); if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))    return TRUE;
return FALSE;        }        Frame的COMMAND传递顺序是View--->Frame本身-->CWinApp对象。                //in viewcore.cpp        BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)       { // first pump through pane if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))    return TRUE;
// then pump through document if (m_pDocument != NULL) {    // special state for saving view before routing to document    CPushRoutingView push(this);    return m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); }
return FALSE;       }       View的COMMAND传递顺序是View本身--->Document
     //in doccore.cpp      BOOL CDocument::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)      { if (CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))    return TRUE;
// otherwise check template if (m_pDocTemplate != NULL &&    m_pDocTemplate->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))    return TRUE;
return FALSE;      }      Document的COMMAND传递顺序是Document本身--->Document Template      由这个例子我们可以清楚地看到WM_COMMAND的传递路径了。 对于子窗口通知:由于子窗口通知通常以WM_COMMAND形式出现,所以它的传递路径也大致与WM_COMMAND相同,这里就不详述了。  
                       ///                        /*       10.收尾工作          */                        / 至此,MFC消息映射与消息传递的内幕已基本被揭开,若想更深刻的理解,你就得在平时的程序开发中夺观察多思考了。
















 
MFC中的消息传递过程
2008年07月22日 星期二 19:20
MFC的标准命令发送路径:
1. 送给活动(Active)视处理,调用活动视的OnCmdMsg。由于当前对象是MFC视对象,所以,OnCmdMsg将搜索CTview及其基类的消息映射数组,试图得到相应的处理函数。 
2. 如果视对象自己不处理,则视得到和它关联的文档,调用关联文档的OnCmdMsg。由于当前对象是MFC视对象,所以,OnCmdMsg将搜索CTdoc及其基类的消息映射数组,试图得到相应的处理函数。 
3. 如果文档对象不处理,则它得到管理文档的文档模板对象,调用文档模板的OnCmdMsg。由于当前对象是MFC文档模板对象,所以,OnCmdMsg将搜索文档模板类及其基类的消息映射数组,试图得到相应的处理函数。 
4. 如果文档模板不处理,则把没有处理的信息逐级返回:文档模板告诉文档对象,文档对象告诉视对象,视对象告诉边框窗口对象。最后,边框窗口得知,视、文档、文档模板都没有处理消息。 
5. CFrameWnd的OnCmdMsg继续调用CWnd::OnCmdMsg(斜体表示有类属限制)来处理消息。由于CWnd没有覆盖OnCmdMsg,故实际上调用了函数CCmdTarget::OnCmdMsg。由于当前对象是MFC边框窗口对象,所以OnCmdMsg函数将搜索CMainFrame类及其所有基类的消息映射数组,试图得到相应的处理函数。CWnd没有实现OnCmdMsg却指定要执行其OnCmdMsg函数,可能是为了以后MFC给CWnd实现了OnCmdMsg之后其他代码不用改变。 
这一步是边框窗口自己尝试处理消息。
2. 如果边框窗口对象不处理,则送给应用程序对象处理。调用CTApp的OnCmdMsg,由于实际上CTApp及其基类CWinApp没有覆盖OnCmdMsg,故实际上调用了函数CCmdTarget::OnCmdMsg。由于当前对象是MFC应用程序对象,所以OnCmdMsg函数将搜索CTApp类及其所有基类的的消息映射入口数组,试图得到相应的处理函数 
3. 如果应用程序对象不处理,则返回FALSE,表明没有命令目标处理当前的命令消息。这样,函数逐级别返回,OnCmdMsg告诉OnCommand消息没有被处理,OnCommand告诉OnWndMsg消息没有被处理,OnWndMsg告诉WindowProc消息没有被处理,于是WindowProc调用DefWindowProc进行缺省处理。






 
 
 
 
 
 
 
 
 
 
 
 
 
 
MFC的消息机制 收藏 
今天重新整理MFC的消息机制,最终的结果应该是利用win32程序模拟一个MFC的消息链。


1.标准消息  
除WM_COMMAND之外,所有以WM_开头的消息。 从CWnd派生的类,都可以接收到这类消息。  


2.命令消息  
来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。 CCmdTarget派生的类,都可以接收到这类消息。  


3.通告消息 
由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。 例如当你在ListBox上选择其中一个项目,ListBox就会产生LBN_SELCHANGE传送给父视窗。从CCmdTarget派生的类,都可以接收到这类消息。


万流归宗 Command Target(CCmdTarget) 


一个Command Target 物件如何知道它可以处理某个讯息?答案是它會看看自己的讯息映 
射表。訊息映射表使得讯息和函式的对映关系形成㆒份表格,进而全体形成一张网,当 
Command Target 物件收到某个讯息,便可由表格得知其处理函式的名称。


 


三个特别的宏:
在MFC中,我们可以找到如下三个宏


1、DECLARE_MASSAGE_MAP()
2、BEGINE_MASSAGE_MAP(CLASS, BASSCLASS)
3、END_MASSAGE_MAP()
下面来分析这三个宏


1  DECLARE_MESSAGE_MAP() 
作用:为一个消息响应类声明必需的成员变量和成员函数。


#define DECLARE_MESSAGE_MAP() 


private: 


     static const AFX_MSGMAP_ENTRY _messageEntries[]; 


protected: 


     static const AFX_MSGMAP messageMap; 


     virtual const AFX_MSGMAP* GetMessageMap() const; 


可以看出DECLARE_MESSAGE_MAP() 宏中定义了两个静态成员函数和一个重载的虚函数


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_PTR nSig;       // signature type (action) or pointer to message # 
     AFX_PMSG pfn;    // routine to call (or special value) 
};


再看看AFX_MESSAGE结构


struct AFX_MSGMAP 

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


可见结构体AFX_MSGMAP中定义了两个指针,pBaseMap指向另一个AFX_MSGMAP,lpEntries指向一个消息入口表。可以推想,在响应消息时,一定是在lpEntries指向的消息入口表中寻找响应函数,也可能会在pBaseMap指向的结构体中做同样的响应函数寻找操作(有点不理解)。 


重载的虚函数GetMessageMap,可以猜测只是用来返回成员messageMap的地址而已


2 BEGIN_MESSGAE_MAP(CLASS, BASECLASS)
作用:定义DECLARE_MESSAGE_MAP宏声明的静态变量。


#define BEGIN_MESSAGE_MAP(theClass, baseClass)  
const AFX_MSGMAP* theClass::GetMessageMap() const 
         { return &theClass::messageMap; }  
     AFX_COMDAT const AFX_MSGMAP theClass::messageMap = 
     { &baseClass::messageMap, &theClass::_messageEntries[0] };  
     AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = 
     { 


BEGIN_MESSAGE_MAP宏有两个参数,theClass表示为当前类,bassClass为当前类的父类。


BEGIN_MESSAGE_MAP宏首先定义了函数GetMessageMap的函数体,如前文所述,直接返回当前类的成员变量messageMap的地址。


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


然后初始化了当前类的成员变量messageMap。messageMap的pBaseMap指针指向其父类的messageMap成员,lpEntries指针指向当前类的_messageEntries数组的首地址。


AFX_COMDAT const AFX_MSGMAP theClass::messageMap = 
     { &baseClass::messageMap, &theClass::_messageEntries[0] };


最后,定义了_messageEntries数组初始化代码的开始部分。


AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = 
     { 


3、END_MESSAGE_MAP()
作用:定义_messageEntries数组初始化代码的结束部分。


#define END_MESSAGE_MAP()  
         {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }  
     };  
在DECLARE_MESSAGE_MAP和END_MESSAGE_MAP之间还有一些宏,如ON_COMMAND、ON_WM_CREATE等,这些宏最终都会被生成一条AFX_MSGMAP_ENTRY结构体数据,并成为_messageEntries消息映射表数据的一个元素。我们以常见的ON_COMMAND宏为例。ON_COMMAND宏的源代码为:


#define ON_COMMAND(id, memberFxn)  
     { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v,  
         static_cast<AFX_PMSG> (memberFxn) },


通过以上分析,我们可以得到一个链表式的数据结构,子类的messageMap成员为链表的头节点。链表的每个节点都包含一个消息入口表。MFC的消息系统的标准备消息处理函数CCmdTarget::OnCmdMsg正是通过这样一个链表查找到消息的响应函数,并调用该函数来响应消息。


 


实例展开前面的三个宏:
假设有这样的视图类class CTestmfcView : public CView,里面有一个消息响应函数:OnMenuTest,则展开宏后,头文件形如:


class CTestmfcView : public CView 
{


//。。。 
protected: 
    afx_msg void OnMenuTest(); 
private: 
    static const AFX_MSGMAP_ENTRY _messageEntries[]; 
protected: 
    static AFX_DATA const AFX_MSGMAP messageMap; 
    virtual const AFX_MSGMAP* GetMessageMap() const; 


};


实现文件形如:


const AFX_MSGMAP* CTestmfcView::GetMessageMap() const 

    return &CTestmfcView::messageMap; 

AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CTestmfcView::messageMap = 

    &CView::messageMap, &CTestmfcView::_messageEntries[0] 
}; 
AFX_COMDAT const AFX_MSGMAP_ENTRY CTestmfcView::_messageEntries[] = 

    //ON_COMMAND(ID_MENU_TEST, OnMenuTest) 
    { WM_COMMAND, CN_COMMAND, (WORD)ID_MENU_TEST, (WORD)ID_MENU_TEST, AfxSig_vv, (AFX_PMSG)&OnMenuTest }, 
    {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } 
}; 


void CTestmfcView::OnMenuTest() 

    // TODO: Add your command handler code here 
    AfxMessageBox("dfd"); 
}


由以上代码即可发现,实际上程序通过虚函数得到本类的指针AFX_MSGMAP,而这个指针包含两个值,一个是指向基类的AFX_MSGMAP*,一个是指向本类函数入口的指针AFX_MSGMAP_ENTRY*,这样,当程序运行到CWnd::OnWndMsg()里面,通过如下代码:


const AFX_MSGMAP* pMessageMap; 


pMessageMap = GetMessageMap();//注意,此为虚函数,实际调用的为各子类的函数


即可得到当前类的AFX_MSGMAP* ,然后在CCmdTarget::OnCmdMsg中通过以下代码:


const AFX_MSGMAP_ENTRY* lpEntry;


lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID);可找到消息函数的入口地址,至此便可调用函数OnMenuTest()了(调用此函数是通过函数指针实现)。


在MFC消息流中,还有消息的上溯,拐弯等一系列的变化,这些还需要进一步深入研究。






本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/luhouxiang/archive/2010/07/11/5726945.aspx








1. 从CWnd派生的类都可以接收标准消息
2.命令消息是来自菜单、加速键和工具栏按钮的消息
3.通告消息是控件产生的消息,如按钮的单击、列表框的选择,他们都是以WM_COMMAND形式呈现的,从CCmdTarget派生的类,都可以接收到这类消息
2. 是窗口就可以接收消息了,线程也可以接收消息
这么说吧,是线程处理的消息循环


---
http://www.chrometalking.com/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值