
                     浅谈定时器组件TTimer和高精度定时
                     ================================
                          CnPack开发组 周劲羽
                   (https://www.cnpack.org  zjy@cnpack.org)
                               2002.04.29

    用Delphi写程序的朋友对TTimer组件应该是比较熟悉的了，但也许很少有朋友对它有过深入的了解，这份文档打算简单地介绍一下Windows下定时器的一些有趣的东西，同时也作为CnPack中TCnTimer高精度定时器组件的介绍。

    TTimer的实现代码在VCL源码的ExtCtrls单元中。从组件的源码中，我们知道它内部使用SetTimer这个API函数来调用操作系统提供的定时器功能。
    SetTimer需要提供四个参数：窗口句柄、定时器ID、定时间隔和回调函数入口。操作系统定时向该窗口消息队列中发送WM_TIMER消息，并将定时器ID和回调函数入口做为消息参数传递。
    TTimer在构造器中使用Forms单元提供的AllocateHWnd过程创建了一个隐藏的窗口并在析构方法中用DeallocateHWnd释放。创建窗口时它提供了一个方法指针WndProc用于处理窗口消息，该方法只是简单地响应WM_TIMER消息并产生OnTimer事件。当定时间隔大于0、Enabled为真、定义了OnTimer事件时，TTimer调用SetTimer进行定时。

    了解了TTimer的工作机制后，我们来看看它的一些不足：
    1、首先，每一个TTimer实例都会创建一个窗口，我们知道窗口是会消耗系统资源的，特别是在Win9X下，当用户资源或GDI资源用完时，死机就是不可避免的了。其实只需要一个窗口，根据不同的定时器ID就可区分了。Rxlib中有一个组件叫RxTimerList，它就是只使用了一个窗口句柄和一个定时器ID，通过特别的算法实现多个定时器的功能，不过它的定时精度较低。
    2、从表面上看，TTimer的定时分辨率是1ms，Win32 SDK中也没有提及内部定时器精度问题。事实上，很多朋友都知道，Win9X下它的实际分辨率是55ms，而NT/2000下是10ms左右。对TTimer来说，将Interval属性设置为1和55效果是一样的，写一个很简单的程序就可证明（Win9X下）。

    很多书上提到Win9X下定时器的实际精度是55ms，事实真的就是这样吗？
    我在实测中发现一些有趣的现象（以下描述都在Win98SE下）：
    1、将定时间隔设为1ms，用一段代码在窗体上显示实际的定时间隔。
    2、运行程序，我发现定时间隔在55ms左右，每秒约18次。
    3、而当我快速移动鼠标或持续按住某个键时，实际间隔最短变化到20ms左右，每秒可达数十次。
    4、当我在程序中加入一段串口通讯代码，并通过外部硬件向串口连续发数时，定时器速度居然达到了数百次。

    55ms的定时间隔，让我很容易地联想到Dos下的外部定时中断INT08，再看看键盘中断INT09、串口中断INT0C，很自然地得出这样的结论：
    Win9X是在响应外部中断时处理定时器的，正常情况下INT08定时中断的产生频繁最高，所以表现出的实际定时精度为55ms。
    为了证明这一想法，我写了一个简单的程序：
    * 在窗体上放两个TTimer，定时间隔为55ms，Enabled为假，再放一按钮，四个标签。
    * 在定时事件中将GetTickCount（从开机到当前经过的毫秒数）的值显示出来，再将定时器关掉。
    * 在按钮事件中先将第一个定时器打开，显示当前时间，用Sleep延时25ms，再将第二个定时器打开，显示当前时间。
    执行结果可看到，虽然两个定时器开启的时间相差了27ms，但两个定时事件发生在同一时间段内（显示值完全相同）。

    附: 时间有限，先写到这，下半部分将接着叙述：
    * Win32的抢占式多任务系统决定了定时器的最终精度。
    * 线程方式定时的实现和优劣。
    * MIDI序列定时器的介绍。
    * 其它的定时方式。
    * CnPack中TCnTimer定时器的介绍。