传统的DOS程序
..........
事件:按下鼠标,按下键盘,按下游戏手柄,将U盘插入USB接口,都将产生事件。比如说按下鼠标左键,将产生鼠标左键被按下的事件。
消息:当鼠标被按下,产生了鼠标按下事件,windows侦测到这一事件的发生,随即发出鼠标被按下的消息到消息队列中,这消息附带了一系列相关的事件信息,比如鼠标哪个键被按了,在哪个窗口被按的,按下点的坐标是多少?如此等等。
句柄:句柄是一个无符号整数值,操作系统用来标识各种对象。应用程序可以通过句柄来控制这些对象,这是windows的一种保护机制,不允许程序员直接控制对象,也是为使用不同语言的windows程序员提供一种统一的操纵windows对象的方式。
以上三个概念,是windows程序设计中常用到的概念。系统内的各种对象之间的通信就是靠它们来完成的。由外部触发事件,产生消息,消息里面带有需要处理这个消息的对象的句柄,操作系统根据这一句柄通知该对象有这么一个消息来了,那对象便知道要根据这消息的内容处理它了。
Windows 程序的进行系依靠外部发生的事件来驱动。换句话说,程序不断等待(利用一个while回路),等待任何可能的输入,然后做判断,然后再做适当的处理。
上述的「输入」是由操作系统捕捉到之后,以消息形式(一种数据结构)进入程序之中。
操作系统通过USER模块(WINDOWS三大模块之一)捕捉外围设备(如键盘和鼠标)所发生的事件。
在Windows程序中,消息是由MSG结构体来表示的。MSG结构体的定义如下(参见MSDN):
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
该结构体中各成员变量的含义如下:
第一个成员变量hwnd表示消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。在Windows程序中,用HWND类型的变量来标识窗口。
第二个成员变量message指定了消息的标识符。在Windows中,消息是由一个数值来表示的,不同的消息对应不同的数值。但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏(WM是Window Message的缩写)的形式,XXX对应某种消息的英文拼写的大写形式。例如,鼠标左键按下消息是WM_LBUTTONDOWN,键盘按下消息是WM_KEYDOWN,字符消息是WM_CHAR,等等。在程序中我们通常都是以WM_XXX宏的形式来使用消息的。
第三、第四个成员变量wParam和lParam,用于指定消息的附加信息。例如,当我们收到一个字符消息的时候,message成员变量的值就是WM_CHAR,但用户到底输入的是什么字符,那么就由wParam和lParam来说明。wParam、lParam表示的信息随消息的不同而不同。如果想知道这两个成员变量具体表示的信息,可以在MSDN中关于某个具体消息的说明文档查看到。读者可以在VC++的开发环境中通过goto definition查看一下WPARAM和LPARAM这两种类型的定义,可以发现这两种类型实际上就是unsigned int和long。
最后两个变量分别表示消息投递到消息队列中的时间和鼠标的当前位置。接受并处理消息的主角就是窗口,每一个窗口都应该有一个函数负
责处理消息,程序员必须负责设计这个所谓的“窗口函数”。如果窗口获得一个消息,则这个窗口函数必须判断消息的类别,决定处理方式。
消息机制
系统消息队列
这是一个系统唯一的队列,输入设备(键盘、鼠标或者其他)的驱动程序会把用户的操作输入转化成消息放置于系统队列中,然后系统会把此消息转到目标窗口所在线程的消息队列中等待处理。
线程消息队列(应用程序消息队列)
应用程序消息队列这个名称是历史遗留,在 32 位(以及之后的 64 位)系统中,正确的名称应该是线程消息队列。每一个 GUI 线程都会维护这样一个线程消息队列。(这个队列只有在线程调用 User 或者 GDI 函数时才会创建,默认并不创建)。然后线程消息队列中的消息会被本线程的消息循环(有时也被称为消息泵)派送到相应的窗口过程(也叫窗口回调函数)处理。
消息的生命期
消息产生的源头有两个,一个是系统,一个是应用程序。系统产生的消息又可以大致分为两类,一类是由输入设备导致的,例如 WM_MOUSEMOVE,一类是 User 部分(或者是系统内的其他部分通过 User 部分)为了实现自身的正常行为或者管理功能而主动生成的,如 WM_WINDOWPOSCHANGED。
产生的方式也有两种,一种称为发送(Send),另一种称为投递(Post,也有译作张贴的),对应于大家极为熟悉的两个 API,SendMessage 和 PostMessage。系统产生的消息,虽然我们看不到代码,不过我们还是可以粗略地划拨一下,基本上所有的输入类消息,都是以投递的方式抵达应用的,而其他的消息,则大部分是采取了发送方式。
至于应用程序,可以随意选用适合自己的消息产生方式。
用vs新建一个纯C的(非基于MFC框架的)windows窗口程序即可看到整个完整的基于消息的实现流程。