How to filter Timer ID for WM_TIMER event messages

Windows specific questions.
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

Using a callback when hWnd = Null

Code: Select all

FUNCTION WndProc(ByVal hWnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT
Static timerID0 As UINT_PTR
Static timerID1 As UINT_PTR
 
Select Case uMsg
 
  Case WM_CREATE
    timerID0 = SetTimer( hWnd, 1, 2000, NULL )
    timerID1 = SetTimer( Null, 0, 4000, Cast(TIMERPROC, @MyFunc) ) ' The Cast is not required
    Print timerID0, timerID1
    Exit Function
 
  Case WM_TIMER
    If wParam = timerID0 Then ' Not needed as only one hWnd timer
      Print "timerID0 2 seconds"
    End IF
 
blah, blah

Just before WndProc I added

Code: Select all

Function MyFunc(ByVal hWnd As HWND, BYVAL uMsg AS UINT, ByVal idEvent AS Uint_Ptr, ByVal dwTime AS DWORD) As LRESULT
  Print "timerID1 4 seconds"
  Function = DefWindowProcW(hwnd, uMsg, idEvent, dwTime)
End Function

On execution I got:

Code: Select all

1             12001
timerID0 2 seconds
timerID1 4 seconds
timerID0 2 seconds
timerID0 2 seconds
timerID1 4 seconds
timerID0 2 seconds
timerID0 2 seconds
timerID1 4 seconds
timerID0 2 seconds
timerID0 2 seconds
timerID1 4 seconds
timerID0 2 seconds
timerID0 2 seconds
timerID1 4 seconds
timerID0 2 seconds
timerID0 2 seconds
timerID1 4 seconds
timerID0 2 seconds

and so on until I hit the Close button.

The timers were killed in WM_DESTROY before PostQuitMessage(0).

Code: Select all

KillTimer hWnd, timerID0
KillTimer Null, timerID1

I should add that I bashed my head against a brick wall quite a few times before the above worked. :D
Last edited by deltarho[1859] on Oct 27, 2023 20:45, edited 1 time in total.
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

I added Timer to the output and changed the global timer to five seconds to prove it was working, and got this:

Code: Select all

1             23058
timerID0 2 seconds 61671.5632601
timerID0 2 seconds 61673.5608426
timerID1 5 seconds 61674.5715545
timerID0 2 seconds 61675.5897405
timerID0 2 seconds 61677.5798921
timerID1 5 seconds 61679.5705259
timerID0 2 seconds 61679.5858167
timerID0 2 seconds 61681.5765028
timerID0 2 seconds 61683.5827571
timerID1 5 seconds 61684.5694191
timerID0 2 seconds 61685.58832269999
timerID0 2 seconds 61687.5787313
timerID1 5 seconds 61689.5847956
timerID0 2 seconds 61689.58601409999
timerID0 2 seconds 61691.5910719
timerID0 2 seconds 61693.5968316
timerID1 5 seconds 61694.5772326
timerID0 2 seconds 61695.6024611
:D

I got a hint on the internet, but no code to back up the hint. It was then down to old-fashioned trial and error. What an admission. :)
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

In MyFunc() I figured 'Function = DefWindowProcW(hwnd, uMsg, idEvent, dwTime)' was required. It isn't – I commented it and changed it to a Sub. Nothing untoward happened.

Added: Cast(TIMERPROC, @MyFunc) is used above. The Cast is not required.
Last edited by deltarho[1859] on Oct 27, 2023 20:44, edited 1 time in total.
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

SetTimer in a console application.

Code adapted from Dave Biggs at the PowerBASIC forum in 2010!

Code: Select all

#Include Once "windows.bi"
Dim Shared timerID As UINT_PTR
Dim Msg As TAGMSG
 
Sub MyFunc(ByVal HWND As hwnd, BYVAL uMsg AS UINT, ByVal idEvent AS Uint_Ptr, ByVal dwTime AS Dword)
  Select Case uMsg
    Case WM_TIMER
      Print "timerID 1 second";Timer
    Case WM_DESTROY
      KillTimer Null, TimerID
      PostThreadMessage GetCurrentThreadID, WM_QUIT, 0, 0
  End Select
End Sub

SetConsoleTitle("SetTimer example")
 
timerID = SetTimer( Null, 0, 1000, @MyFunc )
WHILE GetMessage(@Msg, NULL, NULL, NULL) <> 0
  DispatchMessage  @Msg
WEND
I get:

Code: Select all

timerID 1 second 98598.002169
timerID 1 second 98599.0015421
timerID 1 second 98599.9981752
timerID 1 second 98601.0043031
timerID 1 second 98601.9994594
timerID 1 second 98603.00261289999
timerID 1 second 98604.005643
timerID 1 second 98605.008655
timerID 1 second 98606.0116655
timerID 1 second 98606.99864429999
timerID 1 second 98608.0018496
timerID 1 second 98609.00511849999
timerID 1 second 98610.0081029
timerID 1 second 98611.0105131
timerID 1 second 98611.9984975
timerID 1 second 98613.0018097
timerID 1 second 98614.0039388
and so on until I hit the Close button.
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

How about two global timers using just one callback function?

Code: Select all

Dim Shared timerID0 As UINT_PTR
Dim Shared timerID1 As UINT_PTR
 
Sub MyFunc(ByVal HWND As hwnd, BYVAL uMsg AS UINT, ByVal idEvent AS Uint_Ptr, ByVal dwTime AS Dword)
  If idEvent = timerID0 Then
    Print "timerID0 2 seconds";Timer
  Elseif idEvent = timerID1 Then
    Print "timerID1 4 seconds";Timer
  End If
End Sub
and

Code: Select all

CASE WM_CREATE
  timerID0 = SetTimer( Null, 0, 2000, @MyFunc )
  timerID1 = SetTimer( Null, 0, 4000, @MyFunc )
  Print timerID0, timerID1
  Exit Function
I get:

Code: Select all

3593          3592
timerID0 2 seconds 121207.6596702
timerID1 4 seconds 121209.6655596
timerID0 2 seconds 121209.6663963
timerID0 2 seconds 121211.6713442
timerID1 4 seconds 121213.6614777
timerID0 2 seconds 121213.6772927
timerID0 2 seconds 121215.6834189
timerID1 4 seconds 121217.6581533
timerID0 2 seconds 121217.6738037
timerID0 2 seconds 121219.6804558
timerID1 4 seconds 121221.6548305
timerID0 2 seconds 121221.6701243
timerID0 2 seconds 121223.6762772
timerID1 4 seconds 121225.6662928
timerID0 2 seconds 121225.6819809
timerID0 2 seconds 121227.6882493
timerID1 4 seconds 121229.6567216
timerID0 2 seconds 121229.709695
timerID0 2 seconds 121231.7156316
timerID1 4 seconds 121233.6904283
timerID0 2 seconds 121233.7062657
and so on until I hit the Close button.

The timers were killed in WM_DESTROY before PostQuitMessage(0).

Code: Select all

KillTimer Null, timerID0
KIllTimer Null, timerID1
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

I cannot confirm this but it seems to me that dwTime that comes in with MyFunc is the uptime in milliseconds. I have no idea why this should be of any interest to SetTimer. :o Being a Dword (Ulong) it will roll over after 49.7 days.
adeyblue
Posts: 301
Joined: Nov 07, 2019 20:08

Re: How to filter Timer ID for WM_TIMER event messages

Post by adeyblue »

I'd say 'The docs tell you that' but uh, they seem to have fudged them up which is weird because the source is fine - https://github.com/MicrosoftDocs/sdk-ap ... merproc.md
deltarho[1859] wrote: Oct 27, 2023 19:41 In MyFunc() I figured 'Function = DefWindowProcW(hwnd, uMsg, idEvent, dwTime)' was required. It isn't – I commented it and changed it to a Sub. Nothing untoward happened.
Nothing untoward can happen in there

Code: Select all

#Include Once "windows.bi"
Dim Shared timerID As UINT_PTR
Dim Msg As TAGMSG
 
Sub MyFunc(ByVal HWND As hwnd, BYVAL uMsg AS UINT, ByVal idEvent AS Uint_Ptr, ByVal dwTime AS Dword)
    Print "Start WM_TIMER"
     Dim as Byte Ptr pNull
    *pNull = 7 '' crash the app
      Print "timerID 1 second";Timer
End Sub

SetConsoleTitle("SetTimer example")
 
timerID = SetTimer( Null, 0, 1000, @MyFunc )
WHILE GetMessage(@Msg, NULL, NULL, NULL) <> 0
  DispatchMessage  @Msg
WEND
Start WM_TIMER
Start WM_TIMER
Start WM_TIMER
Start WM_TIMER
Start WM_TIMER
You can turn it off and Microsoft encourage you to turn it off. So much so that when they added it, they waited until some kind reader thought it might be a good idea to put it on the page of the function it applies to rather than just the one that turns it off.
Before using SetTimer or other timer-related functions, it is recommended to set the UOI_TIMERPROC_EXCEPTION_SUPPRESSION flag to false through the SetUserObjectInformationW function, otherwise the application could behave unpredictably and could be vulnerable to security exploits. For more info, see SetUserObjectInformationW.
Some version of Windows 10 is the minimum for that.
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

I saw the note about UOI_TIMERPROC_EXCEPTION_SUPPRESSION flag. What a 'dog's dinner' that is. :)
Tonigau
Posts: 36
Joined: Feb 25, 2021 20:19

Re: How to filter Timer ID for WM_TIMER event messages

Post by Tonigau »

Sorry deltarho[1859], I didn't know there were new messages after my reply last year.
The msg.wParam = timerID did the trick. I had 2 timers & there was no interference.

The unwanted timer event I had was from the editbox scrollbars(even if they were not visible)

I couldn't try with timeSetEvent, too may fiddly bits to get something working.

My issue with timer events now is overTime of the Set ms Value. I suspect a windows message priority.
1000, 500 or even 50 ms are mostly fairly accurate, but some times seems consistently now 10 or 16ms longer.
It seems pointless to set timer for 5 or 1 ms (on my system anyway, W7x64 offline)
I tested setTimer with your console code but is still the same so its not to do with
messages in the app thread ? (no WM). I ran the exe on another machine = same.
I might try on winxp to see wotif.

I post a new message for some help viewtopic.php?p=301900#p301900
Post Reply