Delphi建立精确计时器

用Delphi建立精确计数器

在Windows中的很多场合下编程(例如工业控制、游戏)中需要比较精确的记时器,本文讨论的是在Delphi下实现记时器的若干方法以及它们的精度控制问题。

在Delphi中最常用的是Timer控件,它的设置和使用都非常方便,理论上它的记时精度可以达到1ms(毫秒)。但是众所周知的,实际上Timer在记时间隔小于50ms之下是精度是十分差的。它只适用于对于精度要求不太高的场合。

        这里作者要介绍的是两种利用Windows API函数实现精确记时的方法。第一中方法是利用高性能频率记数(作者本人的称呼)法。利用这种方法要使用两个API函数QueryPerformanceFrequency和QueryPerformanceCounter。QueryPerformanceFrequency函数获得高性能频率记数器的震荡频率。

调用该函数后,函数会将系统频率记数器的震荡频率(每毫秒)保存到一个LargeInteger中。不过利用该函数在几台机器上做过试验,结果都是1193180。读者朋友可以在自己的机器上试一下

QueryPerformanceCounter函数获得系统频率记数器的震荡次数,结果也保存到一个Largenteger中。

很显然,如果在计时中首先使用QueryPerformanceFrequency获得高性能频率记数器每毫秒的震荡次数,然后在计时开始时使用QueryPerformanceCounter函数获得当前系统频率记数器的震荡次数。在计时结束时再调用QueryPerformanceCounter函数获得系统频率记数器的震荡次数。将两者相减,再将结果除以频率记数器每毫秒的震荡次数,就可以获得某一事件经过的准确时间。(次数除以频率等于时间)

另外的一种精确记时器的功能是利用多媒体记时器函数(这也是作者的定义,因为这个系列的函数是在Winmm.dll中定义并且是为媒体播放服务的)。

实现多媒体记时器首先要使用timeSetEvent函数建立计时事件。该函数在Delphi中的mmsystem.pas中有定义,定义如下:

function timeSetEvent(uDelay, uResolution: UINT;

   lpFunction: TFNTimeCallBack; dwUser: DWord; uFlags: UINT): MMRESULT; stdcall

函数定义中参数uDelay定义延迟时间,以毫秒为单位,该参数相当于Timer控件的Interval属性。参数uResolution定义记时精度,如果要求尽可能高的精度,要将该参数设置为0;参数lpFunction定义了timeSetEvent函数的回调函数。该函数相当于一个定时中断处理函数,每当经过一个uDelay长度的时间间隔,该函数就会被调用,编程者可以在该函数中加入相应的处理语句。参数dwUser定义用户自定义的回调值,该值将传递给回调函数。参数uFlags定义定时类型,如果要不间断的记时,该值应设置为1。

如果函数调用成功,在系统中建立了一个多媒体记时器对象,每当经过一个uDelay时间后lpFunction指定的函数都会被调用。同时函数返回一个对象标识,如果不再需要记时器则必须要使用timeKillEvent函数删除记时器对象。

XML:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

由于Windows是一个多任务的操作系统,因此基于API调用的记时器的精度都会受到其它很多因素的干扰。到底这两中记时器的精度如何,我们来使用以下的程序进行验证:

设置三种记时器(Timer控件、高性能频率记数、多媒体记时器)。将它们的定时间隔设置为10毫秒,让它们不停工作直到达到一个比较长的时间(比如60秒),这样记时器的误差会被累计下来,然后同实际经过的时间相比较,就可以得到它们的精度。

下面是具体的检测程序。

unit Unit1;

interface

uses

   Windows, Messages, SysUtils, Classes, GraphiCS, Controls, Forms, Dialogs,

   StdCtrls, ExtCtrls,mmSystem;

type

   TForm1 = class(TForm)

     Edit1: TEdit;

     Edit2: TEdit;

     Edit3: TEdit;

     Button1: TButton;

     Button2: TButton;

     Timer1: TTimer;

     procedure FormCreate(Sender: TObject);

     procedure Button1Click(Sender: TObject);

     procedure Timer1Timer(Sender: TObject);

     procedure Button2Click(Sender: TObject);

   private

     { Private declarations }

   public

     { Public declarations }

   end;

var

   Form1: TForm1;

   actTime1,actTime2:Cardinal;

   smmCount,sTimerCount,sPCount:Single;

   hTimeID:Integer;

   iTen:Integer;

   proTimeCallBack:TFNTimeCallBack;

procedure TimeProc(uTimerID, uMessage: UINT; 

     dwUser, dw1, dw2: DWord) stdcall;

procedure proEndCount;

implementation

{$R *.DFM}

//timeSetEvent的回调函数

procedure proEndCount;

begin

   actTime2:=GetTickCount-actTime1;

   Form1.Button2.Enabled :=False;

   Form1.Button1.Enabled :=TRue;

   Form1.Timer1.Enabled :=False;

   smmCount:=60;

   sTimerCount:=60;

   spCount:=-1;

   timeKillEvent(hTimeID);

end;

procedure TimeProc(uTimerID, uMessage: UINT;

     dwUser, dw1, dw2: DWord) stdcall;

begin

   Form1.Edit2.Text:=FloatToStr(smmCount);

   smmCount:=smmCount-0.01;

end;

procedure TForm1.FormCreate(Sender: TObject);

begin

   Button1.Caption :='开始倒计时';

   Button2.Caption :='结束倒计时';

   Button2.Enabled :=False;

   Button1.Enabled :=True;

   Timer1.Enabled :=False;

   smmCount:=60;

   sTimerCount:=60;

   sPCount:=60;

end;

procedure TForm1.Button1Click(Sender: TObject);

var

   lgTick1,lgTick2,lgPer:TLargeInteger;

   fTemp:Single;

begin

   Button2.Enabled :=True;

   Button1.Enabled :=False;

   Timer1.Enabled :=True;

   Timer1.Interval :=10;

   proTimeCallback:=TimeProc;
   //timeSetEvent使用http://blog.csdn.net/xieyunc/archive/2009/04/29/4136125.aspx
   hTimeID:=timeSetEvent(10,0,proTimeCallback,1,1);

   actTime1:=GetTickCount;

   //获得系统的高性能频率计数器在一秒内的震动次数,参考http://www.cnblogs.com/del/archive/2008/02/16/1070610.html

   QueryPerformanceFrequency(lgPer);

   fTemp:=lgPer/1000;

   iTen:=Trunc(fTemp*10);

   QueryPerformanceCounter(lgTick1);

   lgTick2:=lgTick1;

   sPCount:=60;

   while sPCount>0 do begin

     QueryPerformanceCounter(lgTick2);

     //如果时钟震动次数超过10毫秒的次数则刷新Edit3的显示

     If lgTick2 - lgTick1 > iTen Then begin

             lgTick1 := lgTick2;

             sPCount := sPCount - 0.01;

             Edit3.Text := FloatToStr(sPCount);

             Application.ProcessMessages;

     end;

   end;

end;

procedure TForm1.Timer1Timer(Sender: TObject);

begin

   Edit1.Text := FloatToStr(sTimerCount);

   sTimerCount:=sTimerCount-0.01;

end;

procedure TForm1.Button2Click(Sender: TObject);

begin

   proEndCount;

   //显示从开始记数到记数实际经过的时间

   ShowMessage('实际经过时间'+IntToStr(actTime2)+'毫秒');

end;

end.

        运行程序,点击“开始倒记时”按钮,程序开始60秒倒记时,由于上面的程序只涉及了记时器程序的原理而没有将错误处理加入其中,所以不要等60秒倒记时结束。点击“结束倒记时”按钮可以结束倒记时。这时在弹出对话框中会显示实际经过的时间(单位为毫秒),将三个文本框内的时间乘以1000再加上实际经过的时间,越接近60000,则记时精度越高。

下面是在我的机器上的执行结果。

XML:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />ASPectratio="t">2000/temp/msoclip1/01/clip_image001.jpg" o:title="精确记时器">

        从上面的结果看,由Delphi的Timer控件建立的记时器的精度十分差,无法在实际中使用,而利用高性能频率记数法和多媒体计数器法的误差都在1%以下。考虑到程序中在文本框中显示时间对程序所造成的影响,这个误差在应用中是完全可以忽略的。

另外在运行程序时作者还发现一个问题,如果在倒记时时拖动窗口,文本框中的显示都会停止,而当停止窗口拖放后,多媒体记时器显示会跳过这段时间记时,而其它两种记时器显示倒记时却还是从原来的时间倒数。这说明多媒体记时器是在独立的线程中运行的,不会受到程序的影响。

综合上面的介绍和范例,我们可以看到,如果要建立高精度的记时器,使用多媒体记时器是比较好的选择。而高性能频率记数法比较适合计算某个耗时十分短的过程所消耗的时间(例如分析程序中某个被多次调用的程序段执行时间以优化程序),因为毕竟高性能频率记数的理论可以达到微秒级别。Timer控件虽然精度比上面两者差很多,但是它使用方便,在要求不高的场合它还是最佳选择。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然,我可以为您提供Delphi计时器的简单教程。在Delphi中,您可以使用TTimer组件来实现计时器功能。下面是一个基本的示例: 1. 在Delphi中创建一个新的窗体应用程序项目。 2. 在窗体上放置一个TButton和一个TLabel控件。 3. 在窗体的私有部分声明一个TTimer对象和一个整数变量用于计数器: ``` private Timer: TTimer; Counter: Integer; ``` 4. 在窗体的OnCreate事件中创建并配置计时器: ``` procedure TForm1.FormCreate(Sender: TObject); begin Timer := TTimer.Create(Self); Timer.Interval := 1000; // 设置计时器间隔为1秒 Timer.OnTimer := TimerOnTimer; // 设置计时器事件处理程序 end; ``` 5. 在窗体的OnDestroy事件中释放计时器: ``` procedure TForm1.FormDestroy(Sender: TObject); begin Timer.Free; end; ``` 6. 在TButton的OnClick事件中启动和停止计时器,并更新TLabel的显示文本: ``` procedure TForm1.Button1Click(Sender: TObject); begin if Timer.Enabled then begin Timer.Enabled := False; // 停止计时器 Button1.Caption := 'Start'; // 设置按钮文本为"Start" end else begin Timer.Enabled := True; // 启动计时器 Button1.Caption := 'Stop'; // 设置按钮文本为"Stop" end; end; ``` 7. 创建计时器事件处理程序,在每次计时器触发时更新计数器并显示在TLabel上: ``` procedure TForm1.TimerOnTimer(Sender: TObject); begin Inc(Counter); // 计数器加1 Label1.Caption := IntToStr(Counter); // 显示计数器的值 end; ``` 以上就是一个简单的Delphi计时器示例。您可以在点击按钮时启动和停止计时器,并且每秒钟计数器的值都会更新并显示在标签上。希望这个示例对您有帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值