The following code was written to provide a repeating process at a given rate while a button is held down with the mouse.
I thought I could just set a timer to the appropriate interval to get the rate I wanted. What I found out is that the timer created with SetTimer has a granularity of 32 milli seconds. If I set the interval to 1 millisec to get the fastest rate, I get 63 repetitions per second. Not quite as fast as I had hoped. If I set the interval to 2 or 3 or anthing up to 15, I get the same 63 repetitions/second. If I set the interval from 17 to 31, I get 30 reps/second. If I set the timer interval from 33 to 46, I get 20 reps/second.
What's going on here? And what do you suggest as a replacement for the built in timer?
.686p
.model flat, stdcall
option casemap :none ; case sensitive
.nolist
include windows.inc
inv equ invoke
; some miscellaneous macros
soff Macro QuotedText:Vararg ; returns offset to a string
Local LocalText
.data
LocalText db QuotedText,0
.code
EXITM <offset LocalText>
Endm
msg Macro qtext:VARARG
pusha
invoke MessageBox,0,soff(qtext),0,0
popa
endm
GetHandle Macro MyId:Req,MyHandle:Req
invoke GetDlgItem,hWin,MyId
mov MyHandle,eax ; save handle to control
endm
uselib MACRO libname
include libname.inc
includelib libname.lib
ENDM
uselib user32
uselib kernel32
uselib comctl32
uselib shell32
;uselib comdlg32
;uselib gdi32
uselib masm32 ; for debug
uselib debug ; for debug
.listall
DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data?
buff db 100 dup (?) ; scratch
hWin dd ?
hlab dd ? ; handle to label for test print
hbut dd ? ; handle to button being subclassed
hEd dd ? ; handle to edit box
OldButtonWnd dd ?
TimerVal dd ? ; number of tics to use for timer
TimerCount dd ? ; count timer ticks while pressing button
ButtonHandle dd ? ; used as flag for timer, etc.
ET dd ? ; elapsed time in millisecs
.data
ALIGN 8
freq dq 0
count1 dq 0
count2 dq 0
count3 dd 0
.code
Program:
invoke InitCommonControls
invoke GetModuleHandle, NULL
invoke DialogBoxParam, eax, 101, 0, ADDR DlgProc, 0
invoke ExitProcess, eax
DlgProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.if uMsg==WM_COMMAND
mov eax,wParam ; identifier of the control, or accelerator
mov edx,eax ; check hiword (notification code)
shr edx,16
.if ax == 1004
.if dx == EN_CHANGE
inv GetDlgItemInt,hWin,1004,0,TRUE
mov TimerVal,eax
.endif
.endif
.elseif uMsg== WM_INITDIALOG
mov eax,hWnd ; save handle for everyone
mov hWin,eax
GetHandle 1002,hlab
GetHandle 1001,hbut
GetHandle 1004,hEd
ButtonWndProc proto :dword,:dword,:dword,:dword
inv SetWindowLong,hbut,GWL_WNDPROC,ButtonWndProc
mov OldButtonWnd,eax
mov ButtonHandle,0 ; timer not running
inv GetDlgItemInt,hWin,1004,0,TRUE
mov TimerVal,eax
inv SetFocus,hlab ; get the focus off the button
.elseif uMsg==WM_TIMER
.if ButtonHandle!=0
; do something here each tick
inc TimerCount
.else
invoke QueryPerformanceCounter, ADDR count2
invoke QueryPerformanceFrequency, ADDR freq
mov ecx,DWORD PTR count1
sub DWORD PTR count2, ecx
mov ecx, DWORD PTR count1 + 4
sbb DWORD PTR count2 + 4, ecx
finit
fild count2
fild freq
fdiv
mov count3,1000
fild count3
fmul
fistp count3
mov eax, count3
mov ET,eax
mov edx,wParam ; handle of the button pushed
inv KillTimer,hWin,edx
mov eax,TimerCount
mov ecx,1000
mul ecx
div ET
inv wsprintf,addr buff,soff("counts=%li millisec=%li counts/sec=%li"),TimerCount,ET,eax
inv SendMessage,hlab, WM_SETTEXT, 0,addr buff
.endif
.elseif uMsg == WM_CLOSE
invoke EndDialog,hWin,0
.endif
xor eax,eax
ret
DlgProc endp
ButtonWndProc PROC hEdit:DWord,uMsg:DWord,wParam:DWord,lParam:DWord
mov edx,hEdit
.if uMsg==WM_LBUTTONDOWN || uMsg==WM_LBUTTONDBLCLK ; was a button clicked?
mov ButtonHandle,edx ; pressed
mov TimerCount,0
inv SendMessage,hlab,WM_SETTEXT,0,soff("Testing")
invoke QueryPerformanceCounter, ADDR count1
inv SetTimer,hWin,edx,TimerVal,0 ; use buttonhandle as timer id, 1000 times/second
.elseif uMsg==WM_LBUTTONUP
mov ButtonHandle,0
call MakeButtonLookNormal
.else
inv CallWindowProc,OldButtonWnd,hEdit,uMsg,wParam,lParam
.endif
ret
ButtonWndProc EndP
MakeButtonLookNormal proc ; call with handle of control in edx
inv SendMessage, edx, BM_SETSTYLE, BS_PUSHBUTTON, TRUE ; make button look normal
inv SetFocus,hlab ; get the focus off the button
ret
MakeButtonLookNormal EndP
end Program
[attachment deleted by admin]
On my system the timer created by SetTimer appears to have a nominal granularity of 10ms.
[attachment deleted by admin]
Wierd. Using your program, I get a granularity of 16 ms using xp sp2, athlon cpu. I'm suprised. I guess I expected better performance out of a cpu running at 2 ghz. Thanks Michael.
Its a brave man who tries to use a normal timer for very fine resolution. Usually you use a multimedia timer if you need anything like accuracy for fast intervals.
Jimg,
Timers in Windows change granularity according to circumstances so one application using a fine resolution timer can cause other timers in other applications to behave differently.
Also, the WM_TIMER message is a very low priority and doesn't get sent to your application if anything else is going on so it's not good for timing important events.
You're better off using the multimedia timers, see TimeSetEvent and TimeKillEvent at msdn.
Paul.
Thank you Paul, I never heard of TimeSetEvent, that's one more new thing I learned today!
Jimg,
Another technique you should be looking at is to start a thread and give it a High Priority for the most attention from Windows. Another wise, depending on what is happenning in Windows, your process/thread could suffer from lack of focus.
Regards, P1 :8)