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

类别:VC语言 点击:0 评论:0 推荐:

 

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

 

本文地址:http://com.8s8s.com/it/it2246.htm