-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathHighResTimer.pas
695 lines (646 loc) · 24.5 KB
/
HighResTimer.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
unit HighResTimer;
{******************************************************************************
* *
* THighResTimer Component *
* *
* Component for high resolution time measurement and wait routines. *
* The component can reach resolution and accuracy of a few micro seconds. *
* Because Win32 is not a real time operating system the resolution and the *
* accuracy of this component can't be garuanteed. Setting the execution thread*
* and the component thread to an approbiate priority the resolution and *
* accuracy will be with in the limits for more than 90% of the calls. *
* Remarks: *
* For high accuracy of the time stamp counter a calibration should last two *
* seconds or longer. *
* Ussage hints: *
* For pure time measurement the timer doesn't need to be enabled. At the *
* begining test wether the TSC can be used or not. *
* Befor using the Wait function enable the timer. The default calibration *
* is fast but not very accurate, so do a custom calibrate if needed. *
* After a change of the UseTSC property always a recalibration is needed for*
* high accuracy. Use TSC can be cahnged only if timer is disabled. *
* *
* If you find any bugs or improvements, please let me know. If you modify the *
* source code, please send me a copy. *
* *
* Legal issues: *
* *
* Copyright (C) 2002 by Roman Lauer <[email protected]> *
* *
* This software is provided 'as-is', without any express or implied *
* warranty. In no event will the author be held liable for any damages *
* arising from the use of this software. *
* *
* Permission is granted to anyone to use this software for any purpose, *
* including commercial applications, and to alter it and redistribute it *
* freely, subject to the following restrictions: *
* *
* 1. The origin of this software must not be misrepresented, you must not *
* claim that you wrote the original software. If you use this software *
* in a product, an acknowledgment in the product documentation would be *
* appreciated but is not required. *
* *
* 2. Altered source versions must be plainly marked as such, and must not be *
* misrepresented as being the original software. *
* *
* 3. This notice may not be removed or altered from any source distribution. *
* *
* 4. These components may not be included in any component package that is *
* distributed for profit. *
* *
* Credits go to: *
* Robert T. Palmqvist: I copy his header for these comments *
* Udo Juerss: I used his TimeStampCounter and put it into this component. *
* Autor of Component tacpuid: Used some of his assembler routines. *
* (with modifications) *
* *
* Bibliography: *
* *
* http://www.pergolesi.demon.co.uk/prog/threads/ToC.html *
* Tutorial about multi threading with delphi *
* MSDN *
* Learned about multi media timers. *
* IA-32 Intel ® Architecture Software Developer’s Manual Volume 3 : System *
* Programming Guide, 2002, Intel *
* Learned about the Pentium system architekture *
* AP-485, Intel Processor Identification and the CPUID Instruction, *
* June 1998, Intel *
* Learnd about the CPUID instruction and how to use it to identifiy the *
* used CPU ant it's features *
* *
* History: *
* Ver 1.02 Released 08/11/2002 *
* Component could crash if no time stamp counter is available. Now the *
* time stamp counter is tested. If it is not available the component *
* switches to the high performance counter of windows. There for *
* renamed some properties. Component still doesn't test if access to *
* time stamp counter is restricted. Don't know how to access the TSD *
* flag in the CR4 register. *
* *
* Ver 1.01 Released 07/31/2002 *
* Removed to assembler lines that caused problems on NT machines. *
* *
* Ver 1.00 Released 07/20/2002 *
* No bugs found yet *
* *
* *
******************************************************************************}
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
MMSystem;
type
TThreadClass = (tcIdle, tcNormal, tcHigh, tcRealTime);
TMMTimerThread = class;
TGetCounterValue = function : Int64 of object; stdcall;
THighResTimer = class(TComponent)
private
FStartMeasureTime: Int64;
FStopMeasureTime: Int64;
FMinAccuracy: Cardinal;
FMinResolution: Cardinal;
FAccuracy: Cardinal;
FResolution: Cardinal;
FResolutionTicks: Int64;
FThreadClass: TThreadClass;
FThreadPriority: TThreadPriority;
FMMTimerThread: TMMTimerThread;
FTSC: Boolean;
FUseTSC: Boolean;
FEnabled: Boolean;
FGetCounterValue: TGetCounterValue;
FCountSpeed: Double;
procedure SetAccuracy(const Value: Cardinal);
procedure SetMinAccuracy(const Value: Cardinal);
procedure SetMinResolution(const Value: Cardinal);
procedure SetHRThreadClass(const Value: TThreadClass);
procedure SetHRThreadPriority(const Value: TThreadPriority);
procedure SetResolution(const Value: Cardinal);
procedure SetResolutionTicks;
function ExecuteCPUID(const Level:Integer; var eax, ebx, ecx, edx:Integer):Integer;
function ExecuteCPUIDPtr(const Level:Integer;
var eax, ebx, ecx, edx:Pointer):Integer; stdcall;
function IsCPUIDAvailable: Boolean;
function IsRDTSCAvailable: Boolean;
procedure SetUseTSC(const Value: Boolean);
procedure SetEnabled(const Value: Boolean);
procedure SetCountSpeed(const Value: Double);
procedure SetCounter;
protected
public
constructor Create(aOwner : TComponent); override;
destructor Destroy; override;
procedure Calibrate(msCalibrateTime: Cardinal);
function GetTSCValue: Int64;stdcall;
function GetHPValue: Int64;stdcall;
function GetCountValue: Int64;
function GetCounterTicks(usTime: Cardinal): Int64;
procedure Wait(usTime: Cardinal);
procedure StartTimeMeasure;
procedure StopTimeMeasure;
function GetTimeDifference: Double;
published
property Accuracy: Cardinal read FAccuracy write SetAccuracy;
property CountSpeed: Double read FCountSpeed write SetCountSpeed stored False;
property Enabled: Boolean read FEnabled write SetEnabled default False;
property MinAccuracy: Cardinal read FMinAccuracy write SetMinAccuracy stored False;
property MinResolution: Cardinal read FMinResolution write SetMinResolution stored False;
property Resolution: Cardinal read FResolution write SetResolution;
property ThreadClass: TThreadClass read FThreadClass write SetHRThreadClass default tcNormal;
property ThreadPriority: TThreadPriority read FThreadPriority write SetHRThreadPriority default tpNormal;
property TSC: Boolean read FTSC stored False;
property UseTSC: Boolean read FUseTSC write SetUseTSC default False;
end;
TMMTimerThread = class(TThread)
private
FGetCountValue: TGetCounterValue;
FAccuracy: Cardinal;
FTimerID: Cardinal;
FEventHandle: THandle;
FTickEventHandle: THandle;
FChanged: Boolean;
FCountValue: Int64;
FResolution: Cardinal;
FReadWriteSync: TMultiReadExclusiveWriteSynchronizer;
procedure SetAccuracy(const Value: Cardinal);
function GetCountValue: Int64;
procedure SetCountValue(const Value: Int64);
procedure SetResolution(const Value: Cardinal);
protected
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy; override;
procedure WaitNextTick;
property Accuracy: Cardinal read FAccuracy write SetAccuracy;
property CountValue: Int64 read GetCountValue;
property Resolution: Cardinal read FResolution write SetResolution;
end;
procedure Register;
implementation
const
CalibrateDelayTime = 100; // Time for calibation at start up in ms
PriorityClasses: array [TThreadClass] of Integer =
(IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS,
REALTIME_PRIORITY_CLASS);
procedure Register;
begin
RegisterComponents('System', [THighResTimer]);
end;
{ THighResTimer }
procedure THighResTimer.Calibrate(msCalibrateTime: Cardinal);
// Calibration routine to compute the processor speed
var
TSCStartTime, TSCEndTime: Int64;
HPStartTime, HPEndTime: Int64;
PriorityClass,Priority: Integer;
begin
// calculate the count speed of high performance counter
QueryPerformanceFrequency(HPStartTime);
FCountSpeed := HPStartTime / 1000000.0;
if FUseTSC then
begin
// use the time stamp counter
PriorityClass:=GetPriorityClass(GetCurrentProcess);
Priority:=GetThreadPriority(GetCurrentThread);
SetPriorityClass(GetCurrentProcess,REALTIME_PRIORITY_CLASS);
SetThreadPriority(GetCurrentThread,{THREAD_PRIORITY_TIME_CRITICAL}31);
Sleep(10);
TSCStartTime := GetTSCValue;
QueryPerformanceCounter(HPStartTime);
Sleep(msCalibrateTime);
TSCEndTime:=GetTSCValue;
QueryPerformanceCounter(HPEndTime);
SetThreadPriority(GetCurrentThread,Priority);
SetPriorityClass(GetCurrentProcess,PriorityClass);
// calculate the time that passed by
FCountSpeed := (HPEndTime - HPStartTime)/FCountSpeed;
// calculate the count speed of the time stamp counter
FCountSpeed:=(TSCEndTime - TSCStartTime) / FCountSpeed;
end;
end;
constructor THighResTimer.Create(aOwner: TComponent);
var
tc: TIMECAPS;
begin
inherited;
// set measure values to 0, so the time since when the CPU run's can be calculated
FStartMeasureTime:=0;
FStopMeasureTime:=0;
// figure out the default settings for accuracy and resolution
if VER_PLATFORM_WIN32_NT = Win32Platform then
begin
// working on WINNT
// on NT systems the accuracy of the multi media timer is 1 ms in most of the cases
FMinAccuracy := 1;
end
else
begin
// working on Win9x
// on Win9x the accuracy of the multi media timer is 5 ms in most of the
// cases but it still supports higher resolutions.
FMinAccuracy := 5;
end;
FAccuracy := 0; // set highest possible accuracy
// get min resolution supported by system
TimeGetDevCaps(@tc, SizeOf(tc));
FMinResolution := tc.wPeriodMin;
FResolution := FMinAccuracy;
SetResolutionTicks; // init FResolutionTicks
// set the thread priorities and the thread it self
FThreadClass := tcNormal;
FThreadPriority := tpNormal;
FMMTimerThread := nil;
// by default the timer is disabled
FEnabled := False;
// Test for time stamp counter. By default TSC is not available
FTSC := False;
FUseTSC := False;
if IsCPUIDAvailable then
begin
// test for rdtsc instruction
if IsRDTSCAvailable then
begin
// time stamp counter available, so by default use it
FTSC := True;
FUseTSC := True;
end;
end;
// now it's clear what counter to use and whats available, so set
SetCounter; // init FGetCounterValue
Calibrate(CalibrateDelayTime); // init FCountSpeed
end;
destructor THighResTimer.Destroy;
begin
// stop timer if running
Enabled := False;
inherited;
end;
function THighResTimer.ExecuteCPUID(const Level: Integer; var eax, ebx,
ecx, edx: Integer): Integer;
begin
Result:=ExecuteCPUIDPtr(Level, Pointer(eax), Pointer(ebx), Pointer(ecx),
Pointer(edx));
end;
const _cpuType :Byte = 0; //it had to be here, do not move after funct declaration
function THighResTimer.ExecuteCPUIDPtr(const Level: Integer; var eax, ebx,
ecx, edx: Pointer): Integer;
const
_cpuTypeBit = 12;
_PSNBitMask = $200000;
asm
@@Begin:
cmp Level, 3 // Cyrix workaround:
jnz @@CyrixPass // PSN-bit mus be enabled
pushfd // no way to turn it back (off) ;)
pop EAX // if you want to do so
or EAX, _PSNBitMask // pushfd at begin and popfd at end
push EAX // beware of lost of flow-control
popfd
@@CyrixPass:
cmp Level, 2
jnz @@Synchronized
@@MPCheck: // Multi Processor Check Synchronicity
// Differentiate only primary & non-primary
mov EAX,1
dw $A20F // cpuid
shr EAX, _cpuTypeBit // extract cpuType
and AL, 3 // validate bit-0 and bit-1
cmp AL, _cpuType // compare wih previous result
mov _cpuType, AL // save current value
loopnz @@MPCheck
@@Synchronized:
mov EAX, Level
dw $A20F // cpuid
push EAX
mov EAX, [&ecx] // var argument is REALLY a pointer-
mov [EAX], ECX // load it first to register & then
mov EAX, [&edx] // you can get the value it's refers to
mov [EAX], EDX
mov EAX, [&ebx]
mov [EAX], EBX
pop EAX
push EBX
mov EBX, [&eax]
mov [EBX], EAX
pop EBX
cmp Level, 0 // is it a level 0 Query?
jnz @@End
push EAX // save eax result
shr EAX, _cpuTypeBit // extract cpuType
and AL, 3 // validate bit-0 and bit-1
mov _cpuType, AL
pop EAX
@@End:
mov @Result, EAX // done.
end;
function THighResTimer.GetCounterTicks(usTime: Cardinal): Int64;
// calculates the number of counter ticks for a given time in micro seconds
begin
Result:=Round(usTime * FCountSpeed);
end;
function THighResTimer.GetCountValue: Int64;
begin
Result := FGetCounterValue;
end;
function THighResTimer.GetHPValue: Int64;
begin
QueryPerformanceCounter(Result);
end;
function THighResTimer.GetTimeDifference: Double;
// calculates the time in micro seconds passed between start and stop
begin
Result := (FStopMeasureTime - FStartMeasureTime) / FCountSpeed;
end;
// returns the time stamp counter value
function THighResTimer.GetTSCValue: Int64;
asm
dw $310F // rdtsc
end;
function THighResTimer.IsCPUIDAvailable: Boolean;
asm
pushfd // push original EFLAGS
pop EAX // get original EFLAGS
mov ECX, EAX // save original EFLAGS
// Checks ability to set/clear ID flag (Bit 21) in EFLAGS
// (indicating the presence of cpuid instruction)
xor EAX, $200000 // flip bit-21 (ID) in EFLAGS
push EAX // save new EFLAGS value
popfd // replace current EFLAGS value
pushfd // get new EFLAGS
pop EAX // store new EFLAGS in EAX
push ECX // restore back
popfd // original flags - intel's slipped here ;-(
xor EAX, ECX // compare ID bit,
mov EAX, False // without cpuid
je @@end // cannot toggle ID bit
mov EAX, True // cpuid available
@@end: // done.
end;
function THighResTimer.IsRDTSCAvailable: Boolean;
var
eax, ebx, ecx, edx :Integer;
begin
Result := False;
ExecuteCPUID(0, eax, ebx, ecx, edx);
if eax > 0 then
begin
// CPUID can be called with parameter 1 to get the features flags
ExecuteCPUID(1, eax, ebx, ecx, edx);
// test the tsc flag
if (edx and $10) <> 0 then
begin
Result := True;
end
end;
end;
procedure THighResTimer.SetAccuracy(const Value: Cardinal);
begin
FAccuracy := Value;
if Assigned(FMMTimerThread) then
begin
FMMTimerThread.Accuracy := FAccuracy;
end;
end;
procedure THighResTimer.SetCounter;
// updates the function pointer for reading a counter value
begin
if FUseTSC then
begin
// use ime stamp counter
FGetCounterValue := GetTSCValue;
end
else
begin
// use high performance counter
FGetCounterValue := GetHPValue;
end;
end;
procedure THighResTimer.SetCountSpeed(const Value: Double);
// dumy, so the count speed in MHz appears in the object inspector
begin
end;
procedure THighResTimer.SetEnabled(const Value: Boolean);
begin
if Value <> FEnabled then
begin
FEnabled := Value;
if FEnabled then
begin
// start timer thread
if not (csDesigning in ComponentState) then
begin
// create timer thread only at runtime
FMMTimerThread := TMMTimerThread.Create(True);
FMMTimerThread.Accuracy := FAccuracy;
FMMTimerThread.Resolution := FResolution;
FMMTimerThread.FGetCountValue := FGetCounterValue; // ????
FMMTimerThread.FreeOnTerminate := False;
// start timer thread
FMMTimerThread.Resume;
end;
end
else
begin
// stop timer thread
if Assigned(FMMTimerThread) then
begin
// thread exists, so terminate it
FMMTimerThread.Terminate;
// wait until thread terminated
WaitForSingleObject(FMMTimerThread.Handle, INFINITE);
// free thread
FMMTimerThread.Free;
end;
end;
end;
end;
procedure THighResTimer.SetHRThreadClass(const Value: TThreadClass);
begin
FThreadClass := Value;
if Assigned(FMMTimerThread) then
begin
SetPriorityClass(FMMTimerThread.Handle, PriorityClasses[FThreadClass]);
end;
end;
procedure THighResTimer.SetHRThreadPriority(const Value: TThreadPriority);
begin
FThreadPriority := Value;
if Assigned(FMMTimerThread) then
begin
FMMTimerThread.Priority := FThreadPriority
end;
end;
procedure THighResTimer.SetMinAccuracy(const Value: Cardinal);
// dummy, so MinAccuracy in ms is displayed in object inspector
begin
end;
procedure THighResTimer.SetMinResolution(const Value: Cardinal);
// dummy, so MinResolution in ms is displayed in object inspector
begin
end;
procedure THighResTimer.SetResolution(const Value: Cardinal);
begin
FResolution := Value;
SetResolutionTicks;
if Assigned(FMMTimerThread) then
begin
FMMTimerThread.Resolution := FResolution;
end;
end;
procedure THighResTimer.SetResolutionTicks;
begin
// FResolution is in ms, so convert into counter ticks
FResolutionTicks := GetCounterTicks(FResolution * 1000);
end;
procedure THighResTimer.SetUseTSC(const Value: Boolean);
begin
if Value <> FUseTSC then
begin
if FTSC then
begin
// Time stamp counter available
if not FEnabled then
begin
// timer not enabled, so allowed to change
FUseTSC := Value;
SetCounter;
end;
end
else
begin
FUseTSC := False;
end;
Calibrate(CalibrateDelayTime);
end;
end;
procedure THighResTimer.StartTimeMeasure;
// starts a time measurement
begin
FStartMeasureTime := FGetCounterValue;
end;
procedure THighResTimer.StopTimeMeasure;
// stops a time measurement
begin
FStopMeasureTime := FGetCounterValue;
end;
procedure THighResTimer.Wait(usTime: Cardinal);
// waits the given time in micro seconds
var
ThreadTime: Int64;
EndTime: Int64;
TestForSuspend: Boolean;
begin
// read actual counter value
EndTime := FGetCounterValue;
if FEnabled then
begin
// calculate the counter value at end of wait time
EndTime := EndTime + GetCounterTicks(usTime);
// by defuault test wether thread can be suspended or not
TestForSuspend := True;
while EndTime > FGetCounterValue do
begin
if True = TestForSuspend then
begin
// get counter value the timer thread last was active
ThreadTime := FMMTimerThread.CountValue;
// test for next expected occurance of a timer event
if (EndTime - FResolutionTicks) < ThreadTime then
begin
// the end of the wait time is before the next timer tick will occur
// so do not suspend and count until end time reached
TestForSuspend := False;
end
else
begin
// if next timer tick occures in time, than enough time to suspend
FMMTimerThread.WaitNextTick;
end;
end;
end;
end;
end;
{ TMMTimerThread }
constructor TMMTimerThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FChanged := True;
FAccuracy := 0; // use highes possible accuracy by default
FResolution := 1; // use 1 ms resolution by default
FTimerID := 0;
FEventHandle := INVALID_HANDLE_VALUE;
// create the synchronisation object
FReadWriteSync := TMultiReadExclusiveWriteSynchronizer.Create;
FTickEventHandle := CreateEvent(nil, False, False, nil);
FCountValue := 0;
FGetCountValue := nil;
end;
destructor TMMTimerThread.Destroy;
begin
CloseHandle(FTickEventHandle);
FReadWriteSync.Free;
inherited Destroy;
end;
procedure TMMTimerThread.Execute;
var
EventCountValue: Int64;
begin
// create event
FEventHandle := CreateEvent(nil, False, False, nil);
// delete the changed flag, because all changes go into this start
FChanged := False;
// start MM timer so it periodical sets an event
FTimerID := timeSetEvent(FResolution, FAccuracy, TFNTimeCallBack(FEventHandle), 0,
TIME_PERIODIC or TIME_CALLBACK_EVENT_SET);
while not Terminated do
begin
if FChanged then
begin
// stop timer
timeKillEvent(FTimerID);
// restart MM timer with new settings
FTimerID := timeSetEvent(FResolution, FAccuracy, TFNTimeCallBack(FEventHandle), 0,
TIME_PERIODIC or TIME_CALLBACK_EVENT_SET);
end;
// suspend on event
WaitForSingleObject(FEventHandle, INFINITE);
// log count value
EventCountValue := FGetCountValue;
// store count value for component to use
SetCountValue(EventCountValue);
// release a waiting timer
SetEvent(FTickEventHandle);
end;
// stop timer
timeKillEvent(FTimerID);
// release event
CloseHandle(FEventHandle);
end;
function TMMTimerThread.GetCountValue: Int64;
begin
FReadWriteSync.BeginRead;
Result := FCountValue;
FReadWriteSync.EndRead;
end;
procedure TMMTimerThread.SetAccuracy(const Value: Cardinal);
begin
FAccuracy := Value;
FChanged := True;
end;
procedure TMMTimerThread.SetResolution(const Value: Cardinal);
begin
FResolution := Value;
FChanged := True;
end;
procedure TMMTimerThread.SetCountValue(const Value: Int64);
begin
FReadWriteSync.BeginWrite;
FCountValue := Value;
FReadWriteSync.EndWrite;
end;
procedure TMMTimerThread.WaitNextTick;
begin
WaitForSingleObject(FTickEventHandle, FResolution*2);
end;
end.