Windows编程第十一回 三问计时器

啥是计数器?

计时器是一种输入设备,它周期性地在每经过一个指定的时间间隔后就通知应用程序一次。当你的程序将时间间隔告诉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, DWORD dwTime)

       

{

       

           //处理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   (HWND, UINT, WPARAM, LPARAM) ;

  7         

  8 VOID    CALLBACK   TimerProc (HWND, UINT, UINT,   DWORD ) ;

  9         

 10 

 11 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

 12         

 13                                                          PSTR szCmdLine, int iCmdShow)

 14         

 15 {

 16         

 17    static char   szAppName[]           = " Timer Demo " ;

 18         

 19            HWND                                 hwnd ;

 20         

 21            MSG                                  msg ;

 22         

 23            WNDCLASS                      wndclass ;

 24         

 25    

 26         

 27            wndclass.style                                       = CS_HREDRAW | CS_VREDRAW ;

 28         

 29            wndclass.lpfnWndProc                                 = WndProc ;

 30         

 31           wndclass.cbClsExtra                                  = 0 ;

 32         

 33            wndclass.cbWndExtra                                  = 0 ;

 34         

 35            wndclass.hInstance                                   = hInstance ;

 36         

 37            wndclass.hIcon                                       = LoadIcon (NULL, IDI_APPLICATION) ;

 38         

 39            wndclass.hCursor                                     = LoadCursor (NULL, IDC_ARROW) ;

 40         

 41           wndclass.hbrBackground                       = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

 42         

 43            wndclass.lpszMenuName                        = NULL ;

 44         

 45            wndclass.lpszClassName                       = szAppName ;

 46         

 47    

 48         

 49            if (!RegisterClass (&wndclass))

 50         

 51            {

 52         

 53                   MessageBox (  NULL, TEXT ("Program requires Windows NT!"),

 54         

 55                                                                         szAppName, MB_ICONERROR) ;

 56         

 57                   return 0 ;

 58         

 59            }

 60         

 61    

 62         

 63            hwnd = CreateWindow ( szAppName, "Timer Demo",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,

 70         

 71                         NULL, NULL, hInstance, NULL) ;

 72         

 73    

 74         

 75            ShowWindow (hwnd, iCmdShow) ;

 76         

 77            UpdateWindow (hwnd) ;

 78         

 79         

 80         

 81            while (GetMessage (&msg, NULL, 0, 0))

 82         

 83            {

 84         

 85                   TranslateMessage (&msg) ;

 86         

 87                   DispatchMessage (&msg) ;

 88         

 89            }

 90         

 91            return msg.wParam ;

 92         

 93 }

 94         

 95 

 96 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

 97         

 98 {

 99         

100    switch (message)

101         

102     {

103         

104            case   WM_CREATE:

105         

106                   SetTimer (hwnd, ID_TIMER, 1000, TimerProc) ;

107         

108                  return 0 ;

109         

110        

111         

112            case   WM_DESTROY:

113         

114                   KillTimer (hwnd, ID_TIMER) ;

115         

116                   PostQuitMessage (0) ;

117         

118                   return 0 ;

119         

120            }

121         

122            return DefWindowProc (hwnd, message, wParam, lParam) ;

123         

124 }

125         

126 

127 VOID CALLBACK TimerProc (HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)

128         

129 {

130         

131            static BOOL fFlipFlop = FALSE ;

132         

133            HBRUSH                        hBrush ;

134         

135            HDC                                  hdc ;

136         

137            RECT                                 rc ;

138        

139            fFlipFlop = !fFlipFlop ;

140         

141    

142         

143            GetClientRect (hwnd, &rc) ;

144         

145           hdc = GetDC (hwnd) ;

146         

147     hBrush = CreateSolidBrush (fFlipFlop ? RGB(255,0,0) : RGB(0,0,255)) ;

148                               //括号里的这种判断语句我估计大家都懂,就不解释了

149    

150         

151     FillRect (hdc, &rc, hBrush) ;

152         

153    ReleaseDC (hwnd, hdc) ;

154         

155    DeleteObject (hBrush) ;

156         

157 }

这里计时器的时间间隔设定为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毫秒的分辨率,在Microsoft Windows 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计时器》一文

你可能感兴趣的:(windows)