啥是计数器?
计时器是一种输入设备,它周期性地在每经过一个指定的时间间隔后就通知应用程序一次。当你的程序将时间间隔告诉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秒。当它收到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计时器》一文
分享到:
相关推荐
第11章 程序安装 540 163. 如何使用Visual FoxPro 6.0的安装向导 541 164. 如何自动安装Visual FoxPro 6.0安装向导制作的安装程序 544 165. 如何卸载使用安装向导安装的应用程序 545 166. 如何为编译后的应用...
g003.zip 代表和平的娱乐游戏,自带EXE(12KB) 596,g002.zip 一个迷宫游戏,还有地图编辑器呢,完全源码(230KB) 597,g001.zip 模仿windows中的扫雷,自带EXE(38KB) 598,p012_prtDB.zip 打印...
g003.zip 代表和平的娱乐游戏,自带EXE(12KB) 596,g002.zip 一个迷宫游戏,还有地图编辑器呢,完全源码(230KB) 597,g001.zip 模仿windows中的扫雷,自带EXE(38KB) 598,p012_prtDB.zip 打印...
g003.zip 代表和平的娱乐游戏,自带EXE(12KB) 596,g002.zip 一个迷宫游戏,还有地图编辑器呢,完全源码(230KB) 597,g001.zip 模仿windows中的扫雷,自带EXE(38KB) 598,p012_prtDB.zip 打印...
g003.zip 代表和平的娱乐游戏,自带EXE(12KB) 596,g002.zip 一个迷宫游戏,还有地图编辑器呢,完全源码(230KB) 597,g001.zip 模仿windows中的扫雷,自带EXE(38KB) 598,p012_prtDB.zip 打印...
闭包常用于创建私有变量、模块化代码和封装函数,它在实现回调函数、事件监听器、计时器等场景中非常有用。 ### JavaScript 异步实现 JavaScript 中的异步执行通常通过回调函数、Promise、Generator 函数和 async/...
1. 计时一个小时十五分钟:使用两根绳子,第一根绳子对折后点燃,烧完是半小时;同时点燃第二根绳子的一端,当第一根绳子烧完,第二根绳子剩余一半,此时点燃第二根绳子的另一端,烧完即为一小时十五分钟。 2. 确保...
13. CSRF介绍:CSRF(跨站请求伪造)是一种攻击技术,用户在登录状态下无意识地执行了第三方网站发起的请求。为防止CSRF攻击,通常需要在服务器端验证请求是否为用户主动发起,比如使用CSRF token。 14. HTTP缓存...
第一架飞到半程,第二架给它加油后返回,第一架继续飞,第三架在出发地等待。第一架到达目的地后,第二架再去给第一架加油,最后第三架飞往目的地。 这些题目体现了微软对应聘者逻辑推理、策略制定和创新思维的重视...
现在有若干条材质相同的绳子,问如何用烧绳的方法来计时一个小时十五分钟?” - **解决方案**:可以通过组合两根绳子的方式来实现。例如,先点燃两根绳子的一端,当其中一根烧完时,立即点燃第二根的另一端,这样...
异常捕获一般通过`.then`方法的第二个回调函数或`.catch`方法来实现,以处理Promise中发生的错误。 #### 17. CSS3新增的position属性 CSS3新增的position属性值包括sticky,除此之外还有更多,如initial(将属性...