windows消息机制

1. 引言
Windows 在操做系统平台占有绝对统治地位,基于Windows 的编程和开发愈来愈普遍。
Dos 是过程驱动的,而Windows 是事件驱动的[6],这种差异的存在使得不少Dos 程序员不能
习惯Windows 的程序开发。而不少Windows 程序开发人员也只是对消息运行机制只知其一;不知其二,
想要掌握Windows 编程的核心,必须深入理解消息机制。事件驱动围绕着消息的产生与处
理展开,事件驱动是靠消息循环机制来实现的。也能够理解为消息是一种报告有关事件发生
的通知,消息是Windows 操做系统的灵魂,掌握了消息运行机制就掌握了Windows 编程的
神兵利器。本文将首先阐述Windows 的编程原理,继而对Windows 的消息运行机制进行分
析,并讲述对消息的处理。MFC 是一个广为使用的编程类库,对Windows 的消息机制进行
了良好的封装,因此,在第二部分将着重讨论MFC 的消息映射,最后结合编程实际,经过
对MFC 消息映射的分析,很是巧妙的加以应用,以帮助解决实际问题程序员

 

2. Windows 消息运行机制
在介绍Windows 消息运行机制以前,首先介绍一下消息的概念。
2.1 消息的概念和表示
消息(Message)指的就是Windows 操做系统发给应用程序的一个通告[5],它告诉应用
程序某个特定的事件发生了。好比,用户单击鼠标或按键都会引起Windows 系统发送相应
的消息。最终处理消息的是应用程序的窗口函数,若是程序不负责处理的话系统将会做出默
认处理。
从数据结构[4]的角度来讲,消息是一个结构体,它包含了消息的类型标识符以及其余的
一些附加信息。
系统定义的结构体MSG[1]用于表示消息,MSG 具备以下定义形式:
typedef struct tagMSG
{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;编程

 

其中hwnd 是窗口的句柄,这个参数将决定由哪一个窗口过程函数对消息进行处理;message
是一个消息常量,用来表示消息的类型;wParam 和lParam 都是32 位的附加信息,具体表
示什么内容,要视消息的类型而定;time 是消息发送的时间;pt 是消息发送时鼠标所在的位
置。数组

 

2.2 Windows 编程原理
Windows 是一消息(Message)驱动式系统,Windows 消息提供了应用程序与应用程序
之间、应用程序与Windows 系统之间进行通信的手段。应用程序要实现的功能由消息来触
发,并靠对消息的响应和处理来完成。Windows 系统中有两种消息队列,一种是系统消息队
列,另外一种是应用程序消息队列。计算机的全部输入设备由 Windows 监控,当一个事件发
生时,Windows 先将输入的消息放入系统消息队列中,而后再将输入的消息拷贝到相应的应
用程序队列中,应用程序中的消息循环从它的消息队列中检索每个消息并发送给相应的窗
口函数中。一个事件的发生,到达处理它的窗口函数必须经历上述过程。
所谓消息就是描述事件发生的信息,Windows 程序是事件驱动的,用这一方法编写程序
避免了死板的操做模式,由于Windows 程序的执行顺序将取决于事件的发生顺序,具备不
可预知性。Windows 操做系统,计算机硬件,应用程序之间具备如图1 所示的关系数据结构

箭头1 说明操做系统可以操纵输入输出设备,例如让打印机打印;箭头2 说明操做系统
可以感知输入输出设备的状态变化,如鼠标单击,按键按下等,这就是操做系统和计算机硬
件之间的交互关系,应用程序开发者并不须要知道他们之间是如何作到的,咱们须要了解的
操做系统与应用程序之间如何交互。箭头3 是应用程序通知操做系统执行某个具体的操做,
这是经过调用操做系统的API 来实现的;操做系统可以感知硬件的状态变化,可是并不决
定如何处理,而是把这种变化转交给应用程序,由应用程序决定如何处理,向上的箭头4
说明了这种转交状况,操做系统经过把每一个事件都包装成一个称为消息结构体MSG 来实现
这个过程,也就是消息响应,要理解消息响应,首先须要了解消息的概念和表示。并发

 

2.3 Windows 消息循环
消息循环[1]是Windows 应用程序存在的根本,应用程序经过消息循环获取各类消息,并
经过相应的窗口过程函数,对消息加以处理;正是这个消息循环使得一个应用程序可以响应
外部的各类事件,因此消息循环每每是一个Windows 应用程序的核心部分。
Windows 的消息机制如图2 所示:框架

 

Windows 操做系统为每一个线程维持一个消息队列,当事件产生时,操做系统感知这一事
件的发生,并包装成消息发送到消息队列,应用程序经过GetMessage()函数取得消息并存于
一个消息结构体中,而后经过一个TranslateMessage()和DispatchMessage()解释和分发消息,
下面的代码描述了Windows 的消息循环。
while(GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
TranslateMessage(&msg)对于大多数消息而言不起做用,可是有些消息,好比键盘按键按
下和弹起(分别对于KeyDown 和KeyUp 消息),却须要经过它解释,产生一个WM_CHAR
消息。DispatchMessage(&msg)负责把消息分发到消息结构体中对应的窗口,交由窗口过程
函数处理。GetMessage()在取得WM_QUIT 以前的返回值都为TRUE,也就是说只有获取到
WM_QUIT 消息才返回FALSE,才能跳出消息循环。函数

 

2.4 消息的处理
取得的消息将交由窗口处理函数进行处理,对于每一个窗口类Windows 为咱们预备了一个
默认的窗口过程处理函数DefWindowProc(),这样作的好处是,咱们能够着眼于咱们感兴趣
的消息,把其余不感兴趣的消息传递给默认窗口过程函数进行处理。每个窗口类都有一个
窗口过程函数,此函数是一个回调函数,它是由Windows 操做系统负责调用的,而应用程
序自己不能调用它。以switch 语句开始,对于每条感兴趣的消息都以一个case 引出。
LRESULT CALLBACK WndProc
(
HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam
)
{

switch(uMsgId)
{工具

case WM_TIMER://对WM_TIMER 定时器消息的处理过程
return 0;
case WM_LBUTTONDOWN://对鼠标左键单击消息的处理过程
reurn 0;
. …
default:
return DefWindowProc(hwnd,uMsgId,wParam,lParam);
}
}
对于每条已经处理过的消息都必须返回0,不然消息将不停的重试下去;对于不感兴趣
的消息,交给DefWindowProc()函数进行处理,并须要返回其处理值。spa


3. MFC 的消息映射
MFC 是Windows 下编程的微软基础类库,封装了大部分Windows API 和Windows 控件,
提供了一套消息映射和命令响应机制,方便了应用程序的开发。MFC 只是经过对Windows
消息映射的进行封装,使得添加消息响应变得更为简单,但深究起来,与Windows 消息机
制有同样的底层实现。操作系统


3.1 MFC 消息映射的实现
在MFC 的框架结构下,“消息映射”是经过巧妙的宏定义,造成一张消息映射表格来进
行的。这样一旦消息发生,Framework 就能够根据消息映射表格来进行消息映射和命令传递。
首先在须要进行消息处理的类的头文件(.H)里,都会含有DECLARE_MESSAGE_MAP()
宏,声明该类拥有消息映射表格。
而后在类应用程序文件(.CPP)实现这一表格
BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass)
//{{AFX_MSG_MAP(CInheritClass)
ON_COMMAND(ID_EDIT_COPY,OnEditCopy)
………
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
这里主要进行消息映射的实现,把它和消息处理函数联系在一块儿。其中出现三个宏,第
一个宏是BEGIN_MESSAGE_MAP 有两个参数,分别是拥有消息表格的类,及其父类。第
二个宏是ON_COMMAND , 指定命令消息的处理函数名称。第三个是
END_MESSAGE_MAP()做为结尾符号。
DECLARE_MESSAGE_MAP 宏定义里包含了MFC 定义的两个新的数据结构;
AFX_MSGMAP_ENTRY 和AFX_MSGMAP;其中AFX_MSGMAP_ENTRY 结构包含了
一个消息的全部相关信息,而AFX_MSGMAP 主要做用有两个,一是用来获得基类的消息映
射入口地址。二是获得自己的消息映射入口地址。
实际上,MFC 把全部的消息一条条填入到AFX_MSGMAP_ENTRY 结构中去,造成一
个数组,该数组存放了全部的消息和与它们相关的参数。同时经过AFX_MSGMAP 能获得
该数组的首地址,同时获得基类的消息映射入口地址。当自己对该消息不响应的时候,就可
以上溯到基类的消息映射表寻找对应的消息响应。
MFC 经过钩子函数_AfxCbtFilterHook()截获消息,并在此函数中把窗口过程函数设置为

AfxWindProc,而原来的窗口过程函数被保存在成员变量m_pfnSuper 中。
在MFC 框架下,经过下面的步骤来对消息进行映射[7]。
1 函数AfxWndProc 接收Windows 操做系统发送的消息。
2 函数AfxWndProc 调用函数AfxCallWndProc 进行消息处理,这里一个进步是把对句柄的
操做转换成对CWnd 对象的操做。
3 函数AfxCallWndProc 调用CWnd 类的方法WindowProc 进行消息处理。
4 WindowProc 调用OnWndMsg 进行正式的消息处理,即把消息派送到相关的方法中去处理。
5 若是OnWndMsg 方法没有对消息进行处理的话,就调用DefWindowProc 对消息进行处理。
这就是MFC 对消息调用过程的巧妙封装。


3.2 MFC 消息分类


1 命令消息(WM_COMMAND)
好比菜单项的选择,工具栏按钮点击等发出该消息。全部派生自CCmdTarget 的类都有
能力接收WM_COMMAND 消息。


2 标准消息(WM_XXX)
好比窗口建立,窗口销毁等。全部派生自CWnd 的类才有资格接收标准消息。


3 通告消息(WM_NOTIFY)
这是有控件向父窗口发送的消息,标示控件自己状态的变化。好比下拉列表框选项的改
变CBN_SELCHANGE 和树形控件的TVN_SELCHANGED 消息都是通告消息。
Window 9x 版及之后的新控件通告消息再也不经过WM_COMMAND 传送,而是经过
WM_NOTIFY 传送, 可是老控件的通告消息, 好比CBN_SELCHANGE 仍是经过
WM_COMMAND 消息发送。


4 自定义消息
利用MFC 编程,可使用自定义消息。使用自定义消息须要遵循必定的步骤[2]并须要
本身编写消息响应函数

 


4. MFC 消息的灵活运用
在此,咱们给出一个示例程序,演示对MFC 消息的灵活运用,经过此例的剖析,将加
深咱们对MFC 消息的理解。


4.1 示例功能描述
本示例程序将演示这样一种效果:
对话框上有一个CTabCtrl 控件,一个CComboBox 控件,两个按钮Button1 和Button2。
CTabCtrl 控件有两个标签页Tab1 和Tab2;CComboxBox 有两个选项:选项1 和选项2;通
过按钮(Button1 和Button2)单击,分别发送CTabCtrl 控件的TCN_SELCHANGE 消息和
下拉列表框的CBN_SELCHANGE 消息,在各自的消息响应函数中只是简单的对控件选项作
切换和给出提示信息。
单击Button1 将选中标签页Tab1 和下拉列表框的选项1,并弹出提示信息;单击Button2
将选中标签页Tab2 和下拉列表框的选项2,并弹出提示信息。


4.2 程序设计思路
TCN_SELCHANGE 消息和CBN_SELCHANGE 消息都属于通告消息,此消息由子控件

发送给父窗口,在MSDN 中查询发现TCN_SELCHANGE 消息是以WM_NOTIFY 消息的形
式发送,在MSDN 中查询WM_NOTIFY 消息:
idCtrl = (int) wParam;
pnmh = (LPNMHDR) lParam;
也就是说,WPARAM 参数传递发送此消息的控件标识,LAPAM 参数一个指向NMHDR
结构体的指针。NMHDR 结构体定义以下:
typedef struct tagNMHDR
{
HWND hwndFrom;
UINT idFrom;
UINT code;
}
NMHDR; 其中hwndFrom 标识发送消息控件的句柄,idFrom 是发送消息控件的ID,code
则是消息码,若是要发送TCN_SELCHANGE 消息,则以TCN_SELCHANGE 填充。
查询MSDN 发现, 由CComboBox 控件发送的CBN_SELCHANGE 消息以
WM_COMMAND 消息发送,WPARAM 的高字节传递CComboBox 控件的ID,低字节发送
消息码CBN_SELCHANGE,而LPARAM 则传送发送此消息的控件句柄。
因此咱们能够经过在按钮控件的单击响应函数里分别发送WM_NOTIFY 和
WM_COMMAND 消息来引发TCN_SELCHANGE 和CBN_SELCHANGE 消息响应函数的调
用,分别在两控件消息响应函数中实现选项改变和消息提示便可,遵守这种思路,咱们就可
以实现咱们想要的功能。


4.3 程序实现步骤
启动VC++6.0,新建基于对话框的应用程序MsgTest.
在对话框上添加1 个CTabCtrl 控件,一个CComboBox 控件,2 个按钮Button1 和Button2;
给IDC_TAB1 和IDC_COMBO1 分别关联控件成员变量m_tab1 和m_cb1;为两按钮分
别添加按钮单击响应函数。
在对话框的OnInitDlg()函数中为CTabCtrl 控件添加两个标签页,Tab1 和Tab2;为
ComboBox 添加选项1 和2;代码以下:
m_tab1.InsertItem(0,"Tab1");
m_tab1.InsertItem(1,"Tab2");
m_cb1.AddString("选项1");
m_cb1.AddString("选项2");
用ClassWizard 为CTabCtrl 添加消息响应TCN_SELCHANGE,为CComboBox 添加消息
响应CBN_SELCHANGE。在OnSelchangeTab1()函数中添加代码
int nIndex=m_tab1.GetCurSel();
CString str;
str.Format("%d",nIndex+1);
MessageBox("Tab"+str+" selected!");
在OnSelchangeCombo1()函数中添加代码:
int nIndex=m_cb1.GetCurSel();
CString str;
str.Format("%d",nIndex+1);

 

MessageBox("ComboBox 选项"+str+" selected!");
在按钮1 的响应函数OnButton1()中添加代码:
m_tab1.SetCurSel(0);
NMHDR nmhdr;
nmhdr.code=TCN_SELCHANGE;
nmhdr.hwndFrom=GetDlgItem(IDC_TAB1)->m_hWnd;
nmhdr.idFrom=IDC_TAB1;
SendMessage(WM_NOTIFY,(WPARAM)IDC_TAB1,(LPARAM)&nmhdr);
m_cb1.SetCurSel(0);
WPARAM wParam=0;
WPARAM lParam=0;
wParam=IDC_COMBO1;
wParam= wParam | (CBN_SELCHANGE<<16);
lParam=(WPARAM)(GetDlgItem(IDC_COMBO1)->m_hWnd);
SendMessage(WM_COMMAND, wParam, lParam);
在按钮2 的响应函数OnButton2()中添加相似代码,只须要把m_tab1.SetCurSel(0)和
m_cb1.SetCurSel(0)分别改为m_tab1.SetCurSel(1)和m_cb1.SetCurSel(1)。
经过SendMessage() 函数向控件的父窗口也就是对话框窗口发送相应的消息,
TCN_SELCHANGE 是以WM_NOTIFY 消息的形式发送,参数WPARAM 标识发送
TCN_SELCHANGE 消息的控件ID,LPARAM 是一个NMHDR 结构体的指针,此结构体的
成员code 标识发送什么通告消息,此处是TCN_SELCHANGE,hwndFrom 是发送消息的控
件句柄, 程序中用GetDlgItem()->m_hWdn 得到, idFrom 是发送消息的控件ID 。
CBN_SELCHANGE 以WM_COMMAND 消息的形式发送,一样的,经过查阅MSDN,能够
对此消息的两个参数进行赋值,以保证消息的正确发送。
经过上面的5 个步骤,咱们的程序就编写完成了,单击Button1,能够发现,CTabCtrl
切换到了Tab1 标签页,CComboBox 选择了“选项1”,并弹出消息对话框。因而可知确实引
起了消息响应函数的调用,完成了预约的功能。
经过查阅MSDN,能够获得其余消息的发送和包装形式,咱们能够方便的加以利用,完
成更为复杂的功能,能够说,掌握了Windows 的消息机制,就掌握了Windows 编程的核心。

 

5. 总结Windows 消息机制是Windows 编程的本质和核心,对Windows 消息机制的理解能提升咱们Windows 程序开发的能力。本文首先阐述Windows 的消息机制,而后讲解了MFC 的消息映射,消息分类,最后经过示例程序,讲解如何借助MSDN,灵活运用消息编程,解决实际问题。本文对Windows 下的程序开发具备必定的参考和借鉴意义。