消息泵

来源:互联网 发布:php 招聘 北京 编辑:程序博客网 时间:2024/04/27 06:41

希望一下对消息泵的理解能个您带来帮助!

消息泵也就是消息(处理)循环 (Message Loop),每个基于事件驱动编写出来的 Windows 程序都应该有一个。 消息循环(Message Loop)是程序的心脏,保证程序的正常运行,它的形状大概如下面的结构。

while (true)
{
// 内部处理
}

可见,它应该是不断循环的一段代码, 打破它的循环可以有条件的使用 break 。

消息(处理)循环的首要任务当然就是检测消息队列中的消息了,你有两个选择,就是使用 PeekMessage() 或 GetMessage() 函数。不过,这两个函数是有区别的,下面讲解一下。

记得 Sleep() 函数吗?上一篇教程里面用来让程序暂停执行特定的一段时间,在你沉睡的时候不会给 CPU 带来任何运行的负担,这比用空循环延时更科学。

GetMessage() 就是这样的原理,你执行 GetMessage() 的时候,如果有消息在消息队列里,它取得 MSG 并返回真。但如果没有消息呢?它就先小睡一会儿。什么时候被唤醒?当然是系统向你的消息队列发送消息的时候,所以什么时候被返回,作为程序设计者的你根本无法预知!

那么说来,它应该不会返回 0 的了吧?不对,一个特殊情况,如果它取得的消息的代号是 WM_QUIT (WM_QUIT == 18),它返回的就是 0 ,那时候你就可以使用 break 了。

PeekMessage() 就不同了,它从来不偷懒! 队列中有消息, 它返回真;没有消息,它返回假。它的逻辑就那么简单!

WaitMessage() 是特意为 PeekMessage() 准备的,一般来说,当消息循环里面为空的时候就会调用 WaitMessage() 。它有什么功用?就像它的名字一样,等待消息。嗯,睡着等!(这样可以减轻 CPU 运算负担)

当你拿回来的消息代号等于 WM_QUIT (WM_QUIT == 18),你一样可以使用 break 。

以下两个例子功能其实是一样的,你会选择哪一个做你的消息循环呢?

MSG msg; // 定义消息载体
PostQuitMessage(0); // 发送 WM_QUIT 使打断循环

// 使用 GetMessage() 的例子
while (true)
{
if ( ! GetMessage( &msg, NULL, 0, 0 ) ) break;
// 其它处理
}


PostQuitMessage(0); // 发送 WM_QUIT 使打断循环

// 使用 PeekMessage() 的例子
while (true)
{
if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
if ( msg.message == WM_QUIT ) break;
// 其它处理
}
else WaitMessage();
}


我肯定会选择第二个,因为我可以更加自主地开发。 ( 一个提示:游戏程序程序通常会把它的核心运算部分做成函数,用来代替 WaitMessage() 函数 )

以上例子描述的是一个消息循环的基本框架,不过,相信你也知道,不会就那么简单的。不过,也不会太复杂,只要加多两个函数就行了,那就是 TranslateMessage() 和 DispatchMessage() 。

Translate 是转化的意思,整个函数有什么用呢?对了,是把 虚拟键码 转化为 字符码 ( MSDN 原文:translates virtual-key messages into character messages )。虚拟键码 (virtual-key) 是什么?其实在之前检测全局键盘里面就用过,那是对键盘上不同键钮的编码,用来区分每一个键钮。那么字符码 (character) 实际上是 ASCii 码的一个子集。实现方式是,当认定消息需要转化为 字符码 消息,该函数就会向自己的消息队列里 Post 一个 WM_CHAR 的消息 (WM_CHAR == 258),因为是使用 Post 方式,所发送的消息会插队到最前面。 要转化的 虚拟键码 从原消息的 wParam 中取得,转化后的 字符码 则放在 WM_CHAR 消息的 wParam 中。

举个例子,当 A 键被按下的时候会发出 WM_KEYDOWN 消息,并且附带一个虚拟键码帮助我们知道被按下的是哪一个键。但是,A 被按下的情况不止一种,根据不同的其他因素,可能是 大写 的 "A" ,也可能是 小写 的 "a",完全视乎当时是否打开大写输入 (Caps Lock) 和有没有同时按下 Shift 键。 TranslateMessage() 会自动识别这些情况,保证在 WM_CHAR 消息的字符码 是希望得到的效果。

再举个例子: 如果 键 "7" 被按下,同时还有 Shift 键,那么转化后的 WM_CHAR 消息的 wParam 中就不是 "7" 的 ASCii 码 而是 "&" 的 ASCii 码了。

没有对应 ASCii 码的 虚拟键码 (比如 VK_LSHIFT) 只是被直接复制到 WM_CHAR 消息的 wParam 中去而已。

( 想不到一个简单的函数也要用这么多篇幅讲解,不多说了,查查 MSDN 就知道了 )

为了验证以上说的,我修改了上一篇中关于检测系统消息的循环部分。


long i = 19, k = 0 ; // i 为循环系数初始值, k 用来纪录接受了多少条消息

while (i) // 循环系数 i 为 0 则退出
{
if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{

wsprintf( Temp , "检测到 第 %ld 号消息 \n "
" 第一参数 wParam = %ld ;"
" 第二参数 lParam = %ld ;\n "
" (系统时间) msg.time = %ld ;"
" (涉及的句柄) msg.hwnd = %ld \n\n",
msg.message, msg.wParam, msg.lParam, msg.time, msg.hwnd );
lstrcat( Result, Temp ); k ; // 追加到结果字符串后面


if ( TranslateMessage( &msg ) ) // 新加入 TranslateMessage() 函数
{
wsprintf( Temp , "\n注意! 第 %ld 号 消息被 Translate 或和"
" WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN、WM_SYSKEYUP 相关."
" \n\n\n", msg.message );
lstrcat( Result, Temp ); // 追加到结果字符串
}

} // End of if ( PeekMessage(...

i--; // 循环系数减少 1,为 0 则退出
}


通过感性认识,我认为 TranslateMessage() 实际上不过在适当的时候重新发送 WM_CHAR 而已!不过,如果没有它,很多窗口都不工作了。我也不知道是什么原因,姑且留着它吧。

那么接下来就是 DispatchMessage() 函数了,查看 MSDN 的解释说,它是分派消息到窗口的消息处理函数中去进行处理的。

这个不难解释,不过要先说明一下窗口的消息处理函数 ( window procedure ),一个程序可以同时创建多个窗口,这些窗口对消息有着不同的处理方法,所以就要给窗口定义自己的消息处理的函数 (window procedure)。

但是,系统发送给这些窗口的消息都统一发送到同一个消息队列 中,幸亏消息结构中有 msg.hwnd 指出该条消息与哪一个窗口相关, DispatchMessage() 函数就是依照这个保证消息分派处理自动化而且不会出错!

在本例中创建的 "EDIT" 类的窗口,它的消息处理函数已经被预先定义了,以后会介绍自己定义的方法,是要调用 RegisterClassEx() 函数来实现的。暂时卖个关子,不想再搞混乱你了。

下面给出优化的消息泵的源程序:


// File Name: WinMain.cpp


#define WIN32_LEAN_AND_MEAN // Say No to MFC !!

#include


char Temp[77] = "Hello world";

char Title[] = "Sample __CopyRight - `海风 ";


// Name: WinMain() 主程序入口
// ------ ---------- ----------- ---------
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow )
{


/


MSG msg; // 定义了一个装载消息的结构


// 优化的消息循环
while (true)
{
if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
if ( msg.message == WM_QUIT ) break;

// 一旦按 Shift 键就发送退出消息
if ( msg.message == WM_KEYDOWN && msg.wParam == VK_SHIFT ) PostQuitMessage( true );

TranslateMessage(&msg);
DispatchMessage(&msg);
}
else WaitMessage();
} // End of while (true)

DestroyWindow( hWnd ); // 退出程序前要销毁窗口

MessageBox( NULL, "按 确定 结束测试", Title, MB_OK | MB_TOPMOST );


ExitProcess(0);
return NULL;
}


这个程序不太长,内容是最常用到的东西!

也许你还不知道,其实 MessageBox() 函数里面就包含了一个消息泵,如果调用过该函数,你的消息队列就可能被清空了!

0 0