Windows消息
简介
一个GUI程序必须对来自用户和操作系统的事件(events)做出响应。事件来源可以分为:
- 来自用户:鼠标点击、键盘敲击、屏幕触碰等。
- 来自操作系统:本GUI程序之外的操作引起的事件,比如插入一个新的硬件设备,或者Windows进入了低功耗模式。
为了解决以上这些不知道什么时间会到来的事件,Windows系统使用了一个消息传递模型(message-passing model)来进行此类事件的处理。一个消息用一个整数来进行表示,指定这是某一个特定的事件。比如当用户按下鼠标左键,GUI程序的某个窗口对象会接收到如下的消息码:
#define WM_LBUTTONDOWN 0x0201
一些类型的消息会带有额外的数据。比如上面列举的WM_BUTTONDOWN
消息,该消息就带有鼠标按下时的x坐标值和y坐标值。
为了将消息传递给一个窗口对象,操作系统必须调用为该窗口对象注册的窗口过程(window procedure)。
消息循环(Message Loop)
一个应用程序在其运行时会接收成千上万的消息。另外,一个应用程序会有很多的窗口,每一个窗口都有自己的窗口过程。为了接收如此多的消息并且将它们发送给对应的窗口过程,应用程序需要一个循环来接收消息并且将它们发送(传递)给正确的窗口对象。
当某个线程开始创建第一个窗口对象的时候,操作系统会伴随着创建一个队列来存储窗口消息。该线程可以创建多个窗口对象,但是所有由该线程创建的窗口对象共享这个消息队列。队列对于开发人员是隐藏的,不可以直接对其进行操作,但是,可以通过GetMessage
函数来从队列中拉取一条消息。如下:
MSG msg;
GetMessage(&msg,NULL,0,0);
该函数会将队列头部的第一条消息从队列中移除。如果队列为空,该函数会锁住直到另外一条消息被加入到队列中。GetMessage
锁的这个行为不会使应用程序失去响应,是非阻塞式的。如果没有消息,那么程序就什么也不需要处理。如果有必须要处理某类事情的行为,那么可以创建另外的线程来处理这些事情,而GetMessage
函数仍然等待下一条消息。
对于以上提供的代码片段,如果函数执行成功,那么msg
结构会被接收到的该消息的信息填充满,包含了目标窗口和消息码。上述代码片段中的其他三个参数可以让开发人员对队列中的消息进行过滤以获得想要的某一类消息。在绝大数案例中,可以将这三个参数设置为零。
虽然MSG
结构包含了关于某条消息的详细信息,但是几乎不会去直接地检查这个结构,而是像以下的代码片段一样进行处理:
TranslateMessage(&msg);
DispatchMessage(&msg);
TranslateMessage
函数对应于键盘输入,不需要知道其内部工作原理,只需要知道它在DispatchMessage
之前调用即可。
DispatchMessage
函数告诉操作系统去调用消息将被发往的目的窗口的窗口过程。换句话说,操作系统根据消息中的hwnd在自己的窗口表中寻找该窗口句柄,找到与该窗口句柄关联的函数指针(该窗口的窗口过程的函数首地址),然后去调用这个函数。
基于上面的阐述,以下以一个实例(用户点击了坐标左键)进行相关说明:
- 操作系统会将
WM_LBUTTONDOWN
消息放在消息队列中 - 应用程序会调用
GetMessage
函数 GetMessage
函数会从消息队列中拉取WM_LBUTTONDOWN
消息并将其信息填充在MSG
结构中- 应用程序会调用
TranslateMessage
和DispatchMessage
函数 - 在
DispatchMessage
函数内部,操作系统会根据MSG
数据调用对应的窗口过程 - 该被调用的窗口过程可以选择处理这个消息或者忽略它
当窗口过程返回时,仍然会返回到DispatchMessage
函数。只要应用程序一直在运行,消息就会连续不断地到达消息队列中,因此必须有一个循环来连续不断地从队列中拉取消息并且对消息进行派送。消息循环的过程大致类似于以下的行为:
MSG msg={};
while(GetMessage(&msg,NULL,0,0);)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
如果想要退出应用程序并从消息循环中退出,调用PostQuitMessage
函数。
PostQuitMessage(0);
PostQuitMessage
函数会将WM_QUIT
消息放到消息队列中,该消息会导致GetMessage
返回0,这样就会终止消息循环。
调用这个函数后,就不会再执行DispatchMessage
函数了,因此不需要在开发人员自己设计的窗口的窗口过程中去处理WM_QUIT
消息。
Post方式发送消息与Sent方式发送消息
Post和Sent发送消息的方式是有区别的:
- Post方式:将一条消息放置到消息队列中,通过消息循环进行派送
- Sent方式:会跳过消息队列和消息循环进行派送的这个流程,操作系统会直接调用窗口过程。