mfc 消息隐射机制

一.引言
VC++的MFC类库实际上是Windows下C++编程的一套最为流行的类库。它合理的封装了WIN32 API函数,并设计了一套方便的消息映射机制。
二.SDK下的消息机制实现
Windows的消息都是和线程相对应的。即Windows会把消息发送给和该消息相对应的线程。在SDK的模式下,程序是通过GetMessage函数从和某个线程相对应的消息队列里面把消息取出来并放到一个特殊的结构里面,一个消息的结构是一个如下的STRUCTURE。

typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;

  • hwnd:和窗口过程相关的窗口的句柄
  • message:消息的ID号
  • wParam、lParam:表示和消息相关的参数
  • time:消息发送的时间
  • pt:消息发送时的鼠标的位置
    TranslateMessage:把虚键消息翻译成字符消息并放到响应的消息队列里面
    DispatchMessage:把消息分发到相关的窗口过程,然后窗口过程根据消息的类型对不同的消息进行相关的处理;

三.MFC的消息实现机制
在MFC的框架结构下,可以进行消息处理的类的头文件里面都会含有DECLARE_MESSAGE_MAP()宏,这里主要进行消息映射和消息处理函数的声明。可以进行消息处理的类的实现文件里一般都含有如下的结构。
下面主要进行消息映射的实现和消息处理函数的实现。能够进行消息处理的类都是基于CCmdTarget类的,也就是说CCmdTarget类是所有可以进行消息处理类的父类。CCmdTarget类是MFC处理命令消息的基础和核心。
BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass)
//{{AFX_MSG_MAP(CInheritClass)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

MFC定义了下面的两个主要结构:

  1. 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)
};
  1. AFX_MSGMAP_ENTRY结构包含了一个消息的所有相关信息,其中:
  • nMessage为Windows消息的ID号
  • nCode为控制消息的通知码
  • nID为Windows控制消息的ID
  • nLastID表示如果是一个指定范围的消息被映射的话,nLastID用来表示它的范围。
  • nSig表示消息的动作标识
  • AFX_PMSG pfn 它实际上是一个指向和该消息相应的执行函数的指针。
  struct AFX_MSGMAP
  {
    #ifdef _AFXDLL
    const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
    #else
    const AFX_MSGMAP* pBaseMap;
    #endif
    const AFX_MSGMAP_ENTRY* lpEntries;
  };

AFX_MSGMAP主要作用是两个:

  • 用来得到基类的消息映射入口地址。
  • 得到本身的消息映射入口地址。

MFC把所有的消息一条条填入到AFX_MSGMAP_ENTRY结构中去,形成一个数组,该数组存放了所有的消息和与它们相关的参数。同时通过AFX_MSGMAP能得到该数组的首地址,同时得到基类的消息映射入口地址,这是为了当本身对该消息不响应的时候,就调用其基类的消息响应。

MFC是如何让窗口过程来处理消息的

  1. 所有MFC的窗口类都通过钩子函数_AfxCbtFilterHook截获消息,并且在钩子函数_AfxCbtFilterHook中把窗口过程设定为AfxWndProc。原来的窗口过程保存在成员变量m_pfnSuper中
  2. 所以在MFC框架下,一般一个消息的处理过程
  • 函数AfxWndProc接收Windows操作系统发送的消息;
  • 函数AfxWndProc调用函数AfxCallWndProc进行消息处理,这里一个进步是把对句柄的操作转换成对CWnd对象的操作。
  • 函数AfxCallWndProc调用CWnd类的方法WindowProc进行消息处理。注意AfxWndProc和AfxCallWndProc都是AFX的API函数。而WindowProc已经是CWnd的一个方法。所以可以注意到在WindowProc中已经没有关于句柄或者是CWnd的参数了。
  • 方法WindowProc调用方法OnWndMsg进行正式的消息处理,即把消息派送到相关的方法中去处理。消息是如何派送的呢?实际上在CWnd类中都保存了一个AFX_MSGMAP的结构,而在AFX_MSGMAP结构中保存有所有我们用ClassWizard生成的消息的数组的入口,我们把传给OnWndMsg的message和数组中的所有的message进行比较,找到匹配的那一个消息。实际上系统是通过函数AfxFindMessageEntry来实现的。找到了那个message,实际上我们就得到一个AFX_MSGMAP_ENTRY结构,而我们在上面已经提到AFX_MSGMAP_ENTRY保存了和该消息相关的所有信息,其中主要的是消息的动作标识和跟消息相关的执行函数。然后我们就可以根据消息的动作标识调用相关的执行函数,而这个执行函数实际上就是通过ClassWizard在类实现中定义的一个方法。这样就把消息的处理转化到类中的一个方法的实现上。
  • 如果OnWndMsg方法没有对消息进行处理的话,就调用DefWindowProc对消息进行处理。这是实际上是调用原来的窗口过程进行缺省的消息处理。

所以如果正常的消息处理的话,MFC窗口类是完全脱离了原来的窗口过程,用自己的一套体系结构实现消息的映射和处理。即先调用MFC窗口类挂上去的窗口过程,再调用原先的窗口过程。并且用户面对和消息相关的参数不再是死板的wParam和lParam,而是和消息类型具体相关的参数。比如和消息WM_LbuttonDown相对应的方法OnLButtonDown的两个参数是nFlags和point。nFlags表示在按下鼠标左键的时候是否有其他虚键按下,point更简单,就是表示鼠标的位置。

MFC窗口类消息传递中还提供了两个函数: WalkPreTranslateTree:、 PreTranslateMessage;他们是这样工作的:

  1. 利用MFC框架生成的程序,都是从CWinApp开始执行的,而CWinapp实际继承了CWinThread类。在CWinThread的运行过程中会调用窗口类中的WalkPreTranslateTree方法。而WalkPreTranslateTree方法实际上就是从当前窗口开始查找愿意进行消息翻译的类,直到找到窗口没有父类为止。
  2. 在WalkPreTranslateTree方法中调用了PreTranslateMessage方法。实际上PreTranslateMessage最大的好处是我们在消息处理前可以在这个方法里面先做一些事情。举一个简单的例子,比如我们希望在一个CEdit对象里,把所有的输入的字母都以大写的形式出现。我们只需要在PreTranslateMessage方法中判断message是否为WM_CHAR,如果是的话,把wParam(表示键值)由小写字母的值该为大写字母的值就实现了这个功能。

三.MFC的消息响应顺序

  1. 几个消息函数解释
  • AfxWndProc:该函数负责接收消息,找到消息所属的CWnd对象,然后调用AfxCallWndProc
  • AfxCallWndProc:该函数负责保存消息(保存的内容主要是消息标识符和消息参数)供应用程序以后使用,然后调用WindowProc()函数
  • WindowProc:该函数负责发送消息到OnWndMsg()函数,如果未被处理,则调用DefWindowProc()函数
  • OnWndMsg:该函数的功能首先按字节对消息进行排序,对于WM_COMMAND消息,调用OnCommand()消息 响应函数,对于WM_NOTIFY消息调用OnNotify()消息响应函数。任何被遗漏的消息将是一个窗口消息。OnWndMsg()函数搜索类的消息映像,以找到一个能处理任何窗口消息的处理函数。如果OnWndMsg()函数不能找到这样的处理函数的话,则把消息返回到WindowProc()函数,由它将消息发送给DefWindowProc()函数;
  • OnCommand:该函数查看这是不是一个控件通知(lParam参数不为NULL,如果lParam参数为空的话,说明该消息不是控件通知),如果它是,OnCommand()函数会试图将消息映射到制造通知的控件;如果他不是一个控件通知(或者如果控件拒绝映射的消息)OnCommand()就会调用OnCmdMsg()函数;
  • OnCmdMsg:根据接收消息的类,OnCmdMsg()函数将在一个称为命令传递(Command Routing)的过程中潜在的传递命令消息和控件通知。
  1. 创建窗口的顺序
  • PreCreateWindow() 该函数是一个重载函数,在窗口被创建前,可以在该重载函数中改变创建参数
  • PreSubclassWindow() 这也是一个重载函数,允许首先子分类一个窗口
  • OnGetMinMaxInfo() 该函数为消息响应函数,响应的是WM_GETMINMAXINFO消息,允许设置窗口的最大或者最小尺寸
  • OnNcCreate() 该函数也是一个消息响应函数,响应WM_NCCREATE消息,发送消息以告诉窗口的客户区即将被创建
  • OnNcCalcSize() 该函数也是消息响应函数响应WM_NCCALCSIZE消息,作用是允许改变窗口客户区大小
  • OnCreate() 该函数也是一个消息响应函数,响应WM_CREATE消息,发送消息告诉一个窗口已经被创建
  • OnSize() 该函数也是一个消息响应函数,响应WM_SIZE消息,发送该消息以告诉该窗口大小已经发生变化
  • OnMove() 消息响应函数,响应WM_MOVE消息,发送此消息说明窗口在移动
  • OnChildNotify() 该函数为重载函数,作为部分消息映射被调用,告诉父窗口即将被告知一个窗口刚刚被创建
  1. 关闭窗口的顺序(非模态窗口)
  • OnClose() 消息响应函数,响应窗口的WM_CLOSE消息,当关闭按钮被单击的时候发送此消息
  • OnDestroy() 消息响应函数,响应窗口的WM_DESTROY消息,当一个窗口将被销毁时,发送此消息
  • OnNcDestroy() 消息响应函数,响应窗口的WM_NCDESTROY消息,当一个窗口被销毁后发送此消息
  • PostNcDestroy() 重载函数,作为处理OnNcDestroy()函数的最后动作,被CWnd调用;
  1. 打开模态对话框的顺序
  • DoModal()是重载函数,重载DoModal()成员函数。
  • PreSubclassWindow()也是重载函数,允许首先子分类一个窗口。
  • OnCreate()是消息响应函数,响应WM_CREATE消息,发送此消息以告诉一个窗口已经被创建。
  • OnSize()也是消息响应函数,响应WM_SIZE消息,发送此消息以告诉窗口大小发生变化。
  • OnMove()也是消息响应函数,响应WM_MOVE消息,发送此消息以告诉窗口正在移动。
  • OnSetFont()也是消息响应函数,响应WM_SETFONT消息,发送此消息以允许改变对话框中控件的字体。
  • OnInitDialog()也是消息响应函数,响应WM_INITDIALOG消息,发送此消息以允许初始化对话框中的控件或者创建新控件。
  • OnShowWindow()也是消息响应函数,响应WM_SHOWWINDOW消息,该函数被ShowWindow()函数调用。
  • OnCtlColor()也是消息响应函数,响应WM_CTLCOLOR消息,被父窗口发送已改变对话框或对话框上面控件的颜色。
  • OnChildNotify()是重载函数,作为WM_CTLCOLOR消息的结果发送。
  1. 关闭模态对话框的顺序
  • OnClose()是消息响应函数,响应WM_CLOSE消息,当"关闭"按钮被单击的时候,该函数被调用。
  • OnKillFocus()也是消息响应函数,响应WM_KILLFOCUS消息,当一个窗口即将失去键盘输入焦点以前被发送。
  • OnDestroy()也是消息响应函数,响应WM_DESTROY消息,当一个窗口即将被销毁时,被发送。
  • OnNcDestroy()也是消息响应函数,响应WM_NCDESTROY消息,当一个窗口被销毁以后被发送。
  • PostNcDestroy()也是重载函数,作为处理OnNcDestroy()函数的最后动作被CWnd调用。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容