明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

使用delphi創(chuàng)建精確計(jì)數(shù)器

[摘要]在windows中的很多場(chǎng)合下編程(例如工業(yè)控制、游戲)中需要比較精確的記時(shí)器,本文討論的是在delphi下實(shí)現(xiàn)記時(shí)器的若干方法以及它們的精度控制問(wèn)題。 在delphi中最常用的是timer控件,它...
在windows中的很多場(chǎng)合下編程(例如工業(yè)控制、游戲)中需要比較精確的記時(shí)器,本文討論的是在delphi下實(shí)現(xiàn)記時(shí)器的若干方法以及它們的精度控制問(wèn)題。

在delphi中最常用的是timer控件,它的設(shè)置和使用都非常方便,理論上它的記時(shí)精度可以達(dá)到1ms(毫秒)。但是眾所周知的,實(shí)際上timer在記時(shí)間隔小于50ms之下是精度是十分差的。它只適用于對(duì)于精度要求不太高的場(chǎng)合。

       這里作者要介紹的是兩種利用windows api函數(shù)實(shí)現(xiàn)精確記時(shí)的方法。第一中方法是利用高性能頻率記數(shù)(作者本人的稱呼)法。利用這種方法要使用兩個(gè)api函數(shù)queryperformancefrequency和queryperformancecounter。queryperformancefrequency函數(shù)獲得高性能頻率記數(shù)器的震蕩頻率。

調(diào)用該函數(shù)后,函數(shù)會(huì)將系統(tǒng)頻率記數(shù)器的震蕩頻率(每毫秒)保存到一個(gè)largeinteger中。不過(guò)利用該函數(shù)在幾臺(tái)機(jī)器上做過(guò)試驗(yàn),結(jié)果都是1193180。讀者朋友可以在自己的機(jī)器上試一下

queryperformancecounter函數(shù)獲得系統(tǒng)頻率記數(shù)器的震蕩次數(shù),結(jié)果也保存到一個(gè)largenteger中。  

很顯然,如果在計(jì)時(shí)中首先使用queryperformancefrequency獲得高性能頻率記數(shù)器每毫秒的震蕩次數(shù),然后在計(jì)時(shí)開(kāi)始時(shí)使用queryperformancecounter函數(shù)獲得當(dāng)前系統(tǒng)頻率記數(shù)器的震蕩次數(shù)。在計(jì)時(shí)結(jié)束時(shí)再調(diào)用queryperformancecounter函數(shù)獲得系統(tǒng)頻率記數(shù)器的震蕩次數(shù)。將兩者相減,再將結(jié)果除以頻率記數(shù)器每毫秒的震蕩次數(shù),就可以獲得某一事件經(jīng)過(guò)的準(zhǔn)確時(shí)間。(次數(shù)除以頻率等于時(shí)間)

另外的一種精確記時(shí)器的功能是利用多媒體記時(shí)器函數(shù)(這也是作者的定義,因?yàn)檫@個(gè)系列的函數(shù)是在winmm.dll中定義并且是為媒體播放服務(wù)的)。

實(shí)現(xiàn)多媒體記時(shí)器首先要使用timesetevent函數(shù)建立計(jì)時(shí)事件。該函數(shù)在delphi中的mmsystem.pas中有定義,定義如下:

function timesetevent(udelay, uresolution: uint;

  lpfunction: tfntimecallback; dwuser: dword; uflags: uint): mmresult; stdcall

函數(shù)定義中參數(shù)udelay定義延遲時(shí)間,以毫秒為單位,該參數(shù)相當(dāng)于timer控件的interval屬性。參數(shù)uresolution定義記時(shí)精度,如果要求盡可能高的精度,要將該參數(shù)設(shè)置為0;參數(shù)lpfunction定義了timesetevent函數(shù)的回調(diào)函數(shù)。該函數(shù)相當(dāng)于一個(gè)定時(shí)中斷處理函數(shù),每當(dāng)經(jīng)過(guò)一個(gè)udelay長(zhǎng)度的時(shí)間間隔,該函數(shù)就會(huì)被調(diào)用,編程者可以在該函數(shù)中加入相應(yīng)的處理語(yǔ)句。參數(shù)dwuser定義用戶自定義的回調(diào)值,該值將傳遞給回調(diào)函數(shù)。參數(shù)uflags定義定時(shí)類型,如果要不間斷的記時(shí),該值應(yīng)設(shè)置為1。

如果函數(shù)調(diào)用成功,在系統(tǒng)中建立了一個(gè)多媒體記時(shí)器對(duì)象,每當(dāng)經(jīng)過(guò)一個(gè)udelay時(shí)間后lpfunction指定的函數(shù)都會(huì)被調(diào)用。同時(shí)函數(shù)返回一個(gè)對(duì)象標(biāo)識(shí),如果不再需要記時(shí)器則必須要使用timekillevent函數(shù)刪除記時(shí)器對(duì)象。

由于windows是一個(gè)多任務(wù)的操作系統(tǒng),因此基于api調(diào)用的記時(shí)器的精度都會(huì)受到其它很多因素的干擾。到底這兩中記時(shí)器的精度如何,我們來(lái)使用以下的程序進(jìn)行驗(yàn)證:

設(shè)置三種記時(shí)器(timer控件、高性能頻率記數(shù)、多媒體記時(shí)器)。將它們的定時(shí)間隔設(shè)置為10毫秒,讓它們不停工作直到達(dá)到一個(gè)比較長(zhǎng)的時(shí)間(比如60秒),這樣記時(shí)器的誤差會(huì)被累計(jì)下來(lái),然后同實(shí)際經(jīng)過(guò)的時(shí)間相比較,就可以得到它們的精度。
下面是具體的檢測(cè)程序。

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的回調(diào)函數(shù)

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 :='開(kāi)始倒計(jì)時(shí)';
  button2.caption :='結(jié)束倒計(jì)時(shí)';
  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;
  htimeid:=timesetevent(10,0,protimecallback,1,1);
  acttime1:=gettickcount;
  //獲得系統(tǒng)的高性能頻率計(jì)數(shù)器在一毫秒內(nèi)的震動(dòng)次數(shù)
  queryperformancefrequency(lgper);
  ftemp:=lgper/1000;
  iten:=trunc(ftemp*10);
  queryperformancecounter(lgtick1);
  lgtick2:=lgtick1;
  spcount:=60;
  while spcount>0 do begin
   queryperformancecounter(lgtick2);
    //如果時(shí)鐘震動(dòng)次數(shù)超過(guò)10毫秒的次數(shù)則刷新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;
  //顯示從開(kāi)始記數(shù)到記數(shù)實(shí)際經(jīng)過(guò)的時(shí)間
  showmessage('實(shí)際經(jīng)過(guò)時(shí)間'+inttostr(acttime2)+'毫秒');
end;

end.

       運(yùn)行程序,點(diǎn)擊“開(kāi)始倒記時(shí)”按鈕,程序開(kāi)始60秒倒記時(shí),由于上面的程序只涉及了記時(shí)器程序的原理而沒(méi)有將錯(cuò)誤處理加入其中,所以不要等60秒倒記時(shí)結(jié)束。點(diǎn)擊“結(jié)束倒記時(shí)”按鈕可以結(jié)束倒記時(shí)。這時(shí)在彈出對(duì)話框中會(huì)顯示實(shí)際經(jīng)過(guò)的時(shí)間(單位為毫秒),將三個(gè)文本框內(nèi)的時(shí)間乘以1000再加上實(shí)際經(jīng)過(guò)的時(shí)間,越接近60000,則記時(shí)精度越高。

下面是在我的機(jī)器上的執(zhí)行結(jié)果。
       從上面的結(jié)果看,由delphi的timer控件建立的記時(shí)器的精度十分差,無(wú)法在實(shí)際中使用,而利用高性能頻率記數(shù)法和多媒體計(jì)數(shù)器法的誤差都在1%以下?紤]到程序中在文本框中顯示時(shí)間對(duì)程序所造成的影響,這個(gè)誤差在應(yīng)用中是完全可以忽略的。
另外在運(yùn)行程序時(shí)作者還發(fā)現(xiàn)一個(gè)問(wèn)題,如果在倒記時(shí)時(shí)拖動(dòng)窗口,文本框中的顯示都會(huì)停止,而當(dāng)停止窗口拖放后,多媒體記時(shí)器顯示會(huì)跳過(guò)這段時(shí)間記時(shí),而其它兩種記時(shí)器顯示倒記時(shí)卻還是從原來(lái)的時(shí)間倒數(shù)。這說(shuō)明多媒體記時(shí)器是在獨(dú)立的線程中運(yùn)行的,不會(huì)受到程序的影響。

綜合上面的介紹和范例,我們可以看到,如果要建立高精度的記時(shí)器,使用多媒體記時(shí)器是比較好的選擇。而高性能頻率記數(shù)法比較適合計(jì)算某個(gè)耗時(shí)十分短的過(guò)程所消耗的時(shí)間(例如分析程序中某個(gè)被多次調(diào)用的程序段執(zhí)行時(shí)間以優(yōu)化程序),因?yàn)楫吘垢咝阅茴l率記數(shù)的理論可以達(dá)到微秒級(jí)別。timer控件雖然精度比上面兩者差很多,但是它使用方便,在要求不高的場(chǎng)合它還是最佳選擇。

(最后要說(shuō)的是,以上的結(jié)果都是在windows 9x下獲得的,作者在windows 2000下運(yùn)行該程序時(shí)發(fā)現(xiàn),timer控件的精度比在windows 9x下要高出很多,一般誤差在5%以下,這說(shuō)明windows 2000是一個(gè)真正的多任務(wù)操作系統(tǒng)。再加上windows nt\2000的穩(wěn)定性和易用性,在工業(yè)控制或?qū)崟r(shí)檢測(cè)等領(lǐng)域是一個(gè)比較完美的平臺(tái))