使用delphi創(chuàng)建精確計(jì)數(shù)器
發(fā)表時(shí)間:2023-07-28 來(lái)源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]在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))