在 C++程序架构 一文中,我们看到,程序是由一些层次和模块组成的,那么,这些模块之间, 以及你的程序和windows 之间,是如何传递信息呢?在windows 的平台上,传递信息是由 windows message 消息机制来负责的,这是Windows 的核心部分。
传统的MS-DOS程序主要采用顺序的。关联的、过程驱动的程序设计方法。一个过程是一系列预先定义好的操作序列的组合,它具有一定的开头、中间过程和结束。程序直接控制程序事件和过程的顺序。这样的程序设计方法是面向程序而不是面向用户的,交互性差,用户界面不够友好,因为它强迫用户按照某种不可更改的模式进行工作。
Windows是一个消息(Message)驱动系统,它不是由事件的顺序来控制,而是由事件的发生来控制,而这种事件的发生是随机的、不确定的,并没有预定的顺序,这样就允许程序的用户用各种合理的顺序来安排程序的流程。任何事件的触发与响应均要通过消息的作用才能得以完成。(本质用的是一个特殊的事件同步机制,使用多种常规线程间同步机制实现。)
在SDK编程中,对消息的获取与分发主要是通过消息循环来完成的,而在MFC编程中则是通过采取消息映射的方式对其进行处理的。相比而言,这样的处理方式要简单许多,这也是符合面向对象编程中尽可能隐含实现细节的原则。
while(GetMessage(&msg, NULL,0, 0)) { TranslateMessage(&msg); DispatchMessage (&msg); } |
这里不得不提一下Windows编程原理:
我们已经知道Windows 是一消息(Message)驱动式系统,Windows 消息提供了应用程序与应用程序之间、应用程序与Windows 系统之间进行通讯的手段。应用程序要实现的功能由消息来触发,并靠对消息的响应和处理来完成。Windows 系统中有两种消息队列,一种是系统消息队列,另一种是应用程序消息队列。计算机的所有输入设备由 Windows 监控,当一个事件发生时,Windows 先将输入的消息放入系统消息队列中,然后再将输入的消息拷贝到相应的应用程序队列中,应用程序中的消息循环从它的消息队列中检索每一个消息并发送给相应的窗口函数中。一个事件的发生,到达处理它的窗口函数必须经历上述过程。所谓消息就是描述事件发生的信息,Windows 程序是事件驱动的,用这一方法编写程序避免了死板的操作模式,因为Windows 程序的执行顺序将取决于事件的发生顺序,具有不可预知性。Windows 操作系统,计算机硬件,应用程序之间具有下所示的关系:
箭头1 说明操作系统能够操纵输入输出设备,例如让打印机打印;箭头2 说明操作系统能够感知输入输出设备的状态变化,如鼠标单击,按键按下等,这就是操作系统和计算机硬件之间的交互关系,应用程序开发者并不需要知道他们之间是如何做到的,我们需要了解的操作系统与应用程序之间如何交互。箭头3 是应用程序通知操作系统执行某个具体的操作,这是通过调用操作系统的API 来实现的;操作系统能够感知硬件的状态变化,但是并不决定如何处理,而是把这种变化转交给应用程序,由应用程序决定如何处理,向上的箭头4说明了这种转交情况,操作系统通过把每个事件都包装成一个称为消息结构体MSG 来实现。这个过程,也就是消息响应,要理解消息响应,首先需要了解消息的概念和表示。
在介绍Windows 消息运行机制之前,首先介绍一下消息的概念。
在我们的通常认识上,消息事实就是一个数值,他唯一的定义了一个事件,向Windows发出一个通知,告诉应用程序某个事情发生了。
我们检查一下消息相关的各个回调函数的原型就会发现,表示消息的那个参数的数据类型是 UINT,也就是无符号的整数类型。不过,我们通常也会发现,消息往往还附带有两个其他类型的数据,一个是 WPARAM 类型的,一个是 LPARAM 类型的,如果算上消息的目标窗口的句柄,那么,一个消息以及相关信息才能够说是比较完整。为什么说是比较呢?看一下 MSG 这个结构的定义就会发现,其实还有另外两个我们不太经常使用的数据,是与一条消息有关系的。MSG 的完整声明如下:
typedef struct { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG, *PMSG; |
前四项正是我们已经提及过的,而后两项,一个表示消息发生时的时间,一个表示此消息发生时的按屏幕坐标表示的鼠标光标的位置。
从这个结构也可以看出,我们经常所说的消息,更多是指代表了一个确定的消息的数值。
Windows消息类型
Windows 的消息分类并不好分(如果非要划分的话,可以分为系统定义的消息和应用程序定义的消息),不过有一个区段划分。
系统定义消息(System-Defined Messages)
在SDK中事先定义好的消息,非用户定义的,其范围在[0x0000, 0x03ff]之间, 可以分为以下三类:
窗口消息(Windows Message)
与窗口的内部运作有关,如创建窗口,绘制窗口,销毁窗口等。可以是一般的窗口,也可以是Dialog,控件等。
如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL...
命令消息(Command Message)
这是一类特殊的窗口消息,他用来处理从一个窗口发送到另一个窗口的用户请求,如单击菜单项或工具栏或控件时就会产生命令消息。 WM_COMMAND, LOWORD(wParam)表示菜单项,工具栏按钮或控件的ID。如果是控件, HIWORD(wParam)表示控件消息类型 。
控件通知(Notify Message)
控件通知消息是指这样一种消息,一个窗口内的子控件发生了一些事情,需要通知父窗口。这是最灵活的消息格式, 其Message, wParam, lParam分别为:WM_NOTIFY, 控件ID,指向NMHDR的指针。NMHDR包含控件通知的内容, 可以任意扩展。
其中窗口消息及控件通知消息主要由窗口类即直接或间接由CWND类派生类处理。相对窗口消息及控件通知消息而言,命令消息的处理对象范围就广得多,它不仅可以由窗口类处理,还可以由文档类,文档模板类及应用类所处理。
另外,在MFC中引入了反射消息机制(Reflect Message),在这里我也先将其列在消息类型中:
MFC反射消息(Reflect Message)
在Windows的消息处理中,子窗口的发给其父窗口的通知消息只能由其父窗口进行处理,这使得子窗口的自身能动性大大降低(你想,它连改变自己的背景色,处理一个自身滚动问题都要其父窗口来完成),为了解决这个问题,在MFC中引入了 反射消息 “Reflect Message”的概念,进行消息反射,可以使得控制子窗口能够自行处理与自身相关的一些消息,增强了封装性,从而提高了控制子窗口的可重用性。一般用于对 Windows API 的封装类或者类库中。这是一类消息的总称,它们的处理需要经过一种被称为“反射”的机制。这一机制的具体方式后续章节会有单独讲解。
用户自定义消息
Windwos也允许程序员定义自己的消息,使用SendMessage或PostMessage来发送消息。