分享
 
 
 

No MFC 编程07 - 做个优化的消息泵

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

消息泵也就是消息(处理)循环 (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 <windows.h>

char Temp[77] = "Hello world";

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

// Name: WinMain() 主程序入口

// ------ ---------- ----------- ---------

int WINAPI WinMain( HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow )

{

//*

// 下面用 CreateWindowEx() 函数创建一个临时窗口 , 使用 hWnd 暂时保存句柄

HWND hWnd=CreateWindowEx( WS_EX_TOPMOST | WS_EX_TOOLWINDOW ,

"Edit","1231",

WS_OVERLAPPEDWINDOW,

120,120,

280,180,

NULL,NULL,hInstance,NULL);

ShowWindow( hWnd, SW_SHOW ); // 让这个窗口可以被看见

MessageBox( NULL, "创建了测试窗口,按确定继续测试!", Title, MB_OK | MB_TOPMOST );

// */

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() 函数里面就包含了一个消息泵,如果调用过该函数,你的消息队列就可能被清空了!

…… `海风 2002年10月17日 pm 6:01

——————————————————————————

此时此刻,走廊传来小童的嬉闹声 " 呔! 看招! 黄狗射尿,万丈穿心 ! ... "

我忍不住笑出声来 ... 现在的小孩真厉害!

现在正听的歌: 浪客剑心结尾曲 - It's gonna rain

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有