啥是计数器?

计时器是一种输入设备,它周期性地在每经过一个指定的时间间隔后就通知应用程序一次。当你的程序将时间间隔告诉Windows,例如“每10秒钟通知我一声”,然后Windows给你的程序发送周期性发生的WM_TIMER消息以表示时间到了。

我们可以通过调用SetTimer函数为的Windows程序分配一个定时器。SetTimer有一个时间间隔范围为1毫秒到 4,294,967,295毫秒(将近50天)的整型参数,这个值指示Windows每隔多久时间给程序发送WM_TIMER消息。例如,如果间隔为 1000毫秒,那么Windows将每秒给程序发送一个WM_TIMER消息。

当你的程序用完定时器时,它调用KillTimer函数来停止计时器消息。在处理WM_TIMER消息时,你可以通过调用KillTimer函数来 编写一个“瞬间”的定时器。KillTimer调用除了会销毁以前调用SetTimer创建的定时器事件,还会清除消息队列中尚未被处理的WM_TIMER消息,从而使程序在调用KillTimer之后就不会再 接收到WM_TIMER消息。

下面就介绍一下计时器的使用方法吧。

计时器怎么用?

如果你需要在整个程序执行期间都使用计时器,那么你将得从WinMain函数中或者在处理WM_CREATE消息时调用SetTimer,并在退出 WinMain或响应WM_DESTROY消息时调用KillTimer。根据调用SetTimer时使用的参数,可以选择以下两种方法之一来使用计时 器。

 

方法一

这是最方便的一种方法,它让Windows把WM_TIMER消息发送到应用程序的正常窗口过程中,SetTimer调用如下所示:

SetTimer (hwnd, 1, uiMsecInterval, NULL) ;

第一个参数是其窗口过程将接收WM_TIMER消息的窗口句柄。第二个参数是定时器ID,它是一个非0数值,在整个例子中假定为1。第三个参数是一 个32位无符号整数,以毫秒为单位指定一个时间间隔,一个60,000的值将使Windows每分钟发送一次WM_TIMER消息。

你可以通过调用

KillTimer (hwnd, 1) ;

在任何时刻停止WM_TIMER消息(即使正在处理WM_TIMER消息)。此函数的第二个参数是SetTimer调用中所用的同一个定时器ID。在终止程序之前,你应该在响应WM_DESTROY消息中停止任何活动的定时器。

当你的窗口过程收到一个WM_TIMER消息①时,wParam参数等于定时器的ID值(上述情形为1),lParam参数为0。为了使程序更具有可读性,您可以使用#define叙述定义不同的定时器ID:

#define TIMER_SEC 1

#define TIMER_MIN 2

然后你可以使用两个SetTimer调用来设定两个定时器:

SetTimer (hwnd, TIMER_SEC, 1000, NULL) ;

SetTimer (hwnd, TIMER_MIN, 60000, NULL) ;

WM_TIMER的处理如下所示:

caseWM_TIMER:

       

   switch (wParam)

       

    {

       

   case TIMER_SEC:

       

           //每秒一次的处理

       

                  break ;

       

   case TIMER_MIN:

       

           //每分钟一次的处理

       

           break ;

       

    }

       

return 0 ;

       

如果你想将一个已经存在的定时器设定为不同的时间间隔,您可以简单地用不同的时间值再次调用SetTimer。

 

方法二

设定计时器的第一种方法是把WM_TIMER消息发送到通常的窗口过程,而第二种方法是让Windows直接将计时器消息发送给你程序的另一个函数。

接收这些计时器消息的函数被称为回调函数,这是一个在你的程序之中但是由Windows调用的函数(在第四回曾提到)。 你先告诉Windows此函数的地址,然后Windows调用此函数。这看起来也很熟悉,因为程序的窗口过程实际上也是一种回调函数。当注册窗口类时,要 将函数的地址告诉Windows,当发送消息给程序时,Windows会调用此函数。

像窗口过程一样,回调函数也必须定义为CALLBACK,因为它是由Windows从程序的程序代码段调用的。 callback函数的参数和callback函数的返回值取决于callback函数的目的。跟计时器有关的callback函数中,输入参数与窗口过 程的输入参数一样。计时器callback函数不向Windows返回值。

我们把以下的callback函数称为TimerProc(你能够选择与其它一些用语不会发生冲突的任何名称),它只处理WM_TIMER消息:

VOID CALLBACK TimerProc (  HWND hwnd, UINT message, UINT iTimerID, DWORDdwTime)

       

{

       

           //处理WM_TIMER消息

       

}

       

TimerProc的参数hwnd是在调用SetTimer时指定的窗口句柄。Windows只把WM_TIMER消息 送给TimerProc,因此消息参数message总是等于WM_TIMER。iTimerID值是计时器ID,dwTimer值是与从 GetTickCount函数的返回值相容的值。这是自Windows启动后所经过的毫秒数。

用第一种方法设定计时器时要求下面格式的SetTimer调用:

SetTimer (hwnd, iTimerID, iMsecInterval, NULL) ;

你使用回调函数处理WM_TIMER消息时,SetTimer的第四个参数由回调函数的地址取代,如下所示:

SetTimer (hwnd, iTimerID, iMsecInterval, TimerProc) ;

看个例子吧。

  1. #include <windows.h>  
  2.           
  3. #define ID_TIMER    1  
  4.           
  5.   
  6. LRESULT     CALLBACK       WndProc   (HWNDUINTWPARAMLPARAM) ;  
  7.           
  8. VOID    CALLBACK   TimerProc (HWNDUINTUINT,   DWORD ) ;  
  9.           
  10.   
  11. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)  
  12.           
  13. {  
  14.           
  15.    static char   szAppName[]           = " Timer Demo " ;  
  16.           
  17.            HWND                                 hwnd ;  
  18.           
  19.            MSG                                  msg ;  
  20.           
  21.            WNDCLASS                      wndclass ;  
  22.           
  23.      
  24.           
  25.            wndclass.style                                       = CS_HREDRAW | CS_VREDRAW ;  
  26.           
  27.            wndclass.lpfnWndProc                                 = WndProc ;  
  28.           
  29.           wndclass.cbClsExtra                                  = 0 ;  
  30.           
  31.            wndclass.cbWndExtra                                  = 0 ;  
  32.           
  33.            wndclass.hInstance                                   = hInstance ;  
  34.           
  35.            wndclass.hIcon                                       = LoadIcon (NULL, IDI_APPLICATION) ;  
  36.           
  37.            wndclass.hCursor                                     = LoadCursor (NULL, IDC_ARROW) ;  
  38.           
  39.           wndclass.hbrBackground                       = (HBRUSH) GetStockObject (WHITE_BRUSH) ;  
  40.           
  41.            wndclass.lpszMenuName                        = NULL ;  
  42.           
  43.            wndclass.lpszClassName                       = szAppName ;  
  44.           
  45.      
  46.           
  47.            if (!RegisterClass (&wndclass))  
  48.           
  49.            {  
  50.           
  51.                   MessageBox (  NULL, TEXT ("Program requires Windows NT!"),szAppName, MB_ICONERROR) ;  
  52.           
  53.                   return 0 ;  
  54.           
  55.            }  
  56.           
  57.      
  58.           
  59.            hwnd = CreateWindow ( szAppName, "Timer Demo",  
  60.           
  61.                                                          WS_OVERLAPPEDWINDOW,  
  62.           
  63.                                   CW_USEDEFAULT, CW_USEDEFAULT,  
  64.           
  65.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  66.           
  67.                         NULL, NULL, hInstance, NULL) ;  
  68.           
  69.      
  70.           
  71.            ShowWindow (hwnd, iCmdShow) ;  
  72.           
  73.            UpdateWindow (hwnd) ;  
  74.           
  75.           
  76.           
  77.            while (GetMessage (&msg, NULL, 0, 0))  
  78.           
  79.            {  
  80.           
  81.                   TranslateMessage (&msg) ;  
  82.           
  83.                   DispatchMessage (&msg) ;  
  84.           
  85.            }  
  86.           
  87.            return msg.wParam ;  
  88.           
  89. }  
  90.           
  91.   
  92. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
  93.           
  94. {  
  95.           
  96.    switch (message)  
  97.           
  98.     {  
  99.           
  100.            case   WM_CREATE:  
  101.           
  102.                   SetTimer (hwnd, ID_TIMER, 1000, TimerProc) ;  
  103.           
  104.                  return 0 ;  
  105.           
  106.          
  107.           
  108.            case   WM_DESTROY:  
  109.           
  110.                   KillTimer (hwnd, ID_TIMER) ;  
  111.           
  112.                   PostQuitMessage (0) ;  
  113.           
  114.                   return 0 ;  
  115.           
  116.            }  
  117.           
  118.            return DefWindowProc (hwnd, message, wParam, lParam) ;  
  119.           
  120. }  
  121.           
  122.   
  123. VOID CALLBACK TimerProc (HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)  
  124.           
  125. {  
  126.           
  127.            static BOOL fFlipFlop = FALSE ;  
  128.           
  129.            HBRUSH                        hBrush ;  
  130.           
  131.            HDC                                  hdc ;  
  132.           
  133.            RECT                                 rc ;  
  134.          
  135.            fFlipFlop = !fFlipFlop ;  
  136.           
  137.      
  138.           
  139.            GetClientRect (hwnd, &rc) ;  
  140.           
  141.           hdc = GetDC (hwnd) ;  
  142.           
  143.     hBrush = CreateSolidBrush (fFlipFlop ? RGB(255,0,0) : RGB(0,0,255)) ;  
  144.                               //括号里的这种判断语句我估计大家都懂,就不解释了  
  145.      
  146.           
  147.     FillRect (hdc, &rc, hBrush) ;  
  148.           
  149.    ReleaseDC (hwnd, hdc) ;  
  150.           
  151.    DeleteObject (hBrush) ;  
  152.           
  153. }  

这里计时器的时间间隔设定为1秒。当它收到WM_TIMER消息时,它将显示区域的颜色由蓝色变为红色或由红色变为蓝色。

程序在窗口过程处理WM_CREATE消息时设定计时器。在处理WM_TIMER消息处理期间,翻转bFlipFlop的值并使窗口无效以产生 WM_PAINT消息。在处理WM_PAINT消息处理期间,通过调用GetClientRect获得窗口大小的RECT结构,并通过调用 FillRect改变窗口的颜色。

计时器精确吗?

很可惜它不精确,原因如下。

原因一:Windows计时器是PC硬件和ROM BIOS构造的计时器逻辑的一种相对简单的扩展。回到Windows以前的MS-DOS编程,应用程序能够通过捕获称为timer tick的BIOS中断来实现时钟或计时器。这些中断每54.915毫秒产生一 次,或者大约每秒18.2次。一些为MS-DOS编写的程序自己捕获这个硬件中断以实现时钟和计时器。这是原始的IBM PC的微处理器频率值4.772720 MHz被262144所除而得出的结果。Windows应用程序不拦截BIOS中断,相反地,Windows本身处理硬件中断,这样应用程序就不必进行处 理。在Windows 98中,计时器与其下的PC计时器一样具有55毫秒的分辨率,在MicrosoftWindows NT中,计时器的分辨率为10毫秒。即Windows应用程序不能以高于这些分辨率的频率(在Windows 98下,每秒18.2次,在Windows NT下,每秒大约100次)接收WM_TIMER消息。在SetTimer中指定的时间间隔总是截尾后tick数的整数倍。例如,1000毫秒的间隔除以 54.925毫秒,得到18.207个tick,截尾后是18个tick,它实际上是989毫秒。对每个小于55毫秒的间隔,每个tick都会产生一个 WM_TIMER消息。 

  可见,计时器并不能严格按照指定的时间间隔发送WM_TIMER消息,它总要相差那么几毫秒。 

 

  即使忽略这几个毫秒的差别,计时器仍然不精确。请看原因二: 

   WM_TIMER消息放在正常的消息队列之中,和其他消息排列在一起,因此,如果在SetTimer中指定间隔为1000毫秒,那么不能保证程序每 1000毫秒或者989毫秒就会收到一个WM_TIMER消息。如果其他程序的运行时间超过一秒,在此期间内,你的程序将收不到任何WM_TIMER消 息。事实上, Windows对WM_TIMER消息的处理非常类似于对WM_PAINT消息的处理,这两个消息都是低优先级的,程序只有在消息队列中没有其他消息时才 接收它们。 

  WM_TIMER还在另一方面和WM_PAINT相似:Windows不能持续向消息队列中放入多个 WM_TIMER消息,而是将多余的WM_TIMER消息组合成一个消息。因此,应用程序不会一次收到多个这样的消息,尽管可能在短时间内得到两个 WM_TIMER消息。应用程序不能确定这种处理方式所导致的WM_TIMER消息“丢失”的数目。 

  可见,WM_TIMER消息并不能及时被应用程序所处理,WM_TIMER在消息队列中的延误可能就不能用毫秒来计算了。 

 

   由以上两点,你不能通过在处理WM_TIMER时一秒一秒计数的方法来计时。如果要实现一个时钟程序,可以使用系统的时间函数如 GetLocalTime ,而在时钟程序中,计时器的作用是定时调用GetLocalTime获得新的时间并刷新时钟画面,当然这个刷新的间隔要等于或小于1秒。 

①WM_TIMER消息。

    wParam为计数器的ID:如果需要设定多个计时器,那么对每个计时器都使用不同的计时器ID。wParam的值将随传递到窗口过程的WM_TIMER消息的不同而不同。

    lParam为指向TimerProc的指针,如果调用SetTimer时没有指定TimerProc(其参数值为NULL,即第一种用法),则lParam为0,显然在第二种用法中此值就不为0了。

 注:部分内容参考《Windows计时器》一文