微软已经在GetTickCount的文档中说过,你永远无法比较滴答计数以检查是否已经过了一段时间。 e.g:
不正确(伪代码):
DWORD endTime = GetTickCount + 10000; //10 s from now
...
if (GetTickCount > endTime)
break;
上面的代码很糟糕,因为它可以对tick计数器进行翻转。例如,假设时钟接近其范围的结尾:
endTime = 0xfffffe00 + 10000
= 0x00002510; //9,488 decimal
然后你执行检查:
if (GetTickCount > endTime)
立即满意,因为GetTickCount
大于endTime
:
if (0xfffffe01 > 0x00002510)
相反,您应该总是减去两个时间间隔:
DWORD startTime = GetTickCount;
...
if (GetTickCount - startTime) > 10000 //if it's been 10 seconds
break;
看同样的数学:
if (GetTickCount - startTime) > 10000
if (0xfffffe01 - 0xfffffe00) > 10000
if (1 > 10000)
在C / C ++中哪一方都很好,编译器在某种程度上表现得很好。
但是当我在Delphi中执行相同的数学运算时,通过溢出检查({Q+}
,{$OVERFLOWCHECKS ON}
),减去两个滴答计数会在 TickCount >时生成EIntOverflow异常em>翻身:
if (0x00000100 - 0xffffff00) > 10000
0x00000100 - 0xffffff00 = 0x00000200
此问题的预期解决方案是什么?
编辑:我试图暂时关闭OVERFLOWCHECKS
:
{$OVERFLOWCHECKS OFF}]
delta = GetTickCount - startTime;
{$OVERFLOWCHECKS ON}
但是减法仍然会引发EIntOverflow
异常。
是否有更好的解决方案,包括演员表和更大的中间变量类型?
我问的另一个问题解释了为什么{$OVERFLOWCHECKS}
不起作用。它显然只适用于功能级别,而不是行级别。因此,以下不工作:
{$OVERFLOWCHECKS OFF}]
delta = GetTickCount - startTime;
{$OVERFLOWCHECKS ON}
以下 工作:
delta := Subtract(GetTickCount, startTime);
{$OVERFLOWCHECKS OFF}]
function Subtract(const B, A: DWORD): DWORD;
begin
Result := (B - A);
end;
{$OVERFLOWCHECKS ON}
答案 0 :(得分:6)
像这样的简单函数怎么样?
function GetElapsedTime(LastTick : Cardinal) : Cardinal;
var CurrentTick : Cardinal;
begin
CurrentTick := GetTickCount;
if CurrentTick >= LastTick then
Result := CurrentTick - LastTick
else
Result := (High(Cardinal) - LastTick) + CurrentTick;
end;
所以你有
StartTime := GetTickCount
...
if GetElapsedTime(StartTime) > 10000 then
...
只要StartTime和当前GetTickCount之间的时间小于GetTickCount的臭名昭着的49.7天范围,它就会起作用。
答案 1 :(得分:5)
在编写了一些被调用的辅助函数之后,我已经停止在任何地方进行这些计算。
要在Vista上使用新的GetTickCount64()
功能,以后会有以下新类型:
type
TSystemTicks = type int64;
用于所有这些计算。永远不会直接调用GetTickCount()
,而是使用辅助函数GetSystemTicks()
:
type
TGetTickCount64 = function: int64; stdcall;
var
pGetTickCount64: TGetTickCount64;
procedure LoadGetTickCount64;
var
DllHandle: HMODULE;
begin
DllHandle := LoadLibrary('kernel32.dll');
if DllHandle <> 0 then
pGetTickCount64 := GetProcAddress(DllHandle, 'GetTickCount64');
end;
function GetSystemTicks: TSystemTicks;
begin
if Assigned(pGetTickCount64) then
Result := pGetTickCount64
else
Result := GetTickCount;
end;
// ...
initialization
LoadGetTickCount64;
end.
您甚至可以手动跟踪GetTickCount()
返回值的环绕,并在早期系统上返回真正的64位系统节拍计数,如果您调用GetSystemTicks()
函数,这应该可以正常工作至少每隔几天。 [我似乎记得某个地方的实现,但不记得它在哪里。 gabr发布了a link and the implementation。]
现在实现像
这样的功能是微不足道的function GetTicksRemaining(...): TSystemTicks;
function GetElapsedTicks(...): TSystemTicks;
function IsTimeRunning(...): boolean;
将隐藏细节。调用这些函数而不是就地计算持续时间也可以作为代码意图的文档,因此需要的注释较少。
修改强>
你写评论:
但是就像你说的那样,Windows 2000和XP对GetTickCount的回退仍然存在原始问题。
您可以轻松解决此问题。首先,您不需要 回到GetTickCount()
- 您也可以使用提供给calculate a 64 bit tick count on older systems的代码gabr。 (如果需要,可以将timeGetTime()
替换为GetTickCount)
。)
但是如果你不想这样做,你也可以在辅助函数中禁用范围和溢出检查,或检查minuend是否小于减数并通过添加$ 100000000(2 ^ 32)来纠正模拟64位滴答计数。或者在汇编程序中实现这些函数,在这种情况下代码没有检查(不是我会建议的,但这是可能的)。
答案 2 :(得分:3)
您还可以使用DSiWin32中的DSiTimeGetTime64:
threadvar
GLastTimeGetTime: DWORD;
GTimeGetTimeBase: int64;
function DSiTimeGetTime64: int64;
begin
Result := timeGetTime;
if Result < GLastTimeGetTime then
GTimeGetTimeBase := GTimeGetTimeBase + $100000000;
GLastTimeGetTime := Result;
Result := Result + GTimeGetTimeBase;
end; { DSiTimeGetTime64 }
答案 3 :(得分:1)
您可以使用Int64数据类型来避免溢出:
var
Start, Delta : Int64;
begin
Start := GetTickCount;
...
Delta := GetTickCount - start;
if (Delta > 10000) then
...