Microsoft уже заявляет в документации для GetTickCount, что Вы никогда не могли сравнивать количества галочки, чтобы проверить, передал ли интервал. например:
Неправильный (псевдокод):
DWORD endTime = GetTickCount + 10000; //10 s from now
...
if (GetTickCount > endTime)
break;
Вышеупомянутый код плох, потому что это suceptable к трансформации счетчика галочки. Например, предположите, что часы около конца, он - диапазон:
endTime = 0xfffffe00 + 10000
= 0x00002510; //9,488 decimal
Затем Вы выполняете свою проверку:
if (GetTickCount > endTime)
Который удовлетворен immediatly с тех пор 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}
), вычитание двух количеств галочки генерирует исключение EIntOverflow, когда TickCount переворачивается:
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}
Как насчет такой простой функции?
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 дней.
Вы также можете использовать DSiTimeGetTime64 из DSiWin32 :
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 }
Можно использовать тип данных Int64, чтобы избежать переполнения:
var
Start, Delta : Int64;
begin
Start := GetTickCount;
...
Delta := GetTickCount - start;
if (Delta > 10000) then
...
Я перестал повсюду выполнять эти вычисления после того, как написал несколько вспомогательных функций, которые вызываются вместо них.
Чтобы использовать новую функцию GetTickCount64 ()
в Vista и более поздних версиях, существует следующий новый тип:
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 опубликовал ссылку и реализацию .]
Теперь тривиально реализовать такие функции, как
function GetTicksRemaining(...): TSystemTicks;
function GetElapsedTicks(...): TSystemTicks;
function IsTimeRunning(...): boolean;
, которые будут скрывать детали. Вызов этих функций вместо вычисления продолжительности на месте также служит документированием намерения кода, поэтому требуется меньше комментариев.
Изменить:
Вы пишете в комментарии:
Но, как вы сказали, откат GetTickCount в Windows 2000 и XP по-прежнему оставляет исходную проблему.
Вы можете легко это исправить. Во-первых, вам не требуется для возврата к GetTickCount ()
- вы можете использовать код gabr, предоставленный для расчета 64-битного счетчика тиков в старых системах , поскольку хорошо. (Вы можете заменить timeGetTime ()
на GetTickCount)
, если хотите.)
Но если вы не хотите этого делать, вы можете просто отключить проверки диапазона и переполнения во вспомогательных функциях или проверить, меньше ли minuend, чем вычитаемое, и исправить это, добавив 100000000 долларов (2 ^ 32 ) для имитации 64-битного счетчика тиков. Или реализовать функции на ассемблере, и в этом случае в коде нет проверок (не то чтобы я советовал это, но это возможно).