How To Speed Timers in Windows

The Timerand Xojo.Core.Timerclasses gives us a resolution that is more than enough for most of the cases where we need to use them. In fact, under macOS we don’t find any kind of limitation when using the classes provided by the Xojo Framework: we can achieve a maximum resolution of 1 ms on any of the most recent computers.

But when working with Windows OS this is not so straightforward . It doesn’t matter if we try to set the Timer Periodproperty to a minimum value of 1 ms; Windows imposes a minimum resolution of 16 ms, insufficient when we demand the maximum precision for a critical task (period interval between Timer firings).

Of course Xojo has a solution for this, you can resort to a third-party plug-in that works extremely well … or you might find the following technique useful for all kinds of projects.

Let’s start with the foundation. The Timer period is determined by the hardware internal clock and, specifically, by the OS driver. Under Windows, all modern computers are capable of providing a resolution above the 16 ms set by Windows by default.

In fact, when we use the NtQueryTimerResolutionfunction (from the NTDLL.DLLlibrary) we can get minimum, maximum and current timer values for the hardware it is running on, similar to these (based on my own computer running Windows 10 under a VMWare Fusion virtual machine):

  • Minimum Resolution: 15.62 ms
  • Maximum Resolution: 0.5 ms
  • Current Resolution: 0.99 ms

As you can see, the first value matches the current Timer behavior under Windows, but the hardware tell us that we can reach even a higher resolution … even of 0.5 ms between timer firings.

In order to make this change, Windows offers several options (all of them available under Xojo via the use of Declaresor, preferably, using External Methods).

The first of these options, the one that would be the optimal approach, uses the group of MultimediaTimer functions (or high resolution Timers); specially these:

  • CreateTimerQueueTimer: Allows creating a timer with a delegate (or Callback) that will fire just one time, or regularly at the set period interval. This would be a behavior similar to the current Xojo Timer class.
  • ChangeTimerQueueTimer: Using this function, we can modify the firing period for an already created timer; like the one we got when using the previous function.

The main problem we will face when using these functions is that Windows creates these Timers on a different thread than the one used by the Xojo app. During my tests, this is a showstopper due to the fact that, currently, Xojo doesn’t support the use of pre-emptive threading. Thus, it can’t coordinate the execution of our Timer Callback with the tasks in place by the Xojo framework. As result, at some random point our app will be pointing to Nil objects and will silently stall.

In addition, these kind of Timers are reentrant. That means that the timer will execute the Callback again even if the previous call has not finished the job yet.

During my tests, I found a way to solve this kind of reentrant problem using one-time firing Timers. This way, the callback method would be the responsible for deleting the previously created Timer (this is needed to not overflow the maximum limit of 500 timers), and create a new one-time firing timer.

But this approach doesn’t solve the main problem: at a random point, the app unexpectly quits due to the fact of the non preemptive multithreading coordination between the one created by Windows for the Timer and the main, single thread, used by the Xojo apps.

Same Thread Timers

Once you have discarded the previous (optimal) approach, the technique you can use is based on the use of another kind of Timer provided by Windows: WaitableTimer. This kind of timer is created and, most important, run under the same thread they were created on. Thus, there is no collisionwith the Xojo framework during the application execution. In adition, as it goes with the commented High Definition Timers, we can achieve the same maximum resolution of 1 ms.

To start creating and using this kind of Timers in your Xojo app, first add an External Methodso we can set the function definition from the C or C++ Windows Library.

In a new Xojo project, add a Moduleand our first External Method in it using the following values:

  • Method NameCreateWaitableTimerW
  • Parameters: att as ptr, resetMode as Boolean, timerName as CString
  • Return Type: Uint32
  • Lib: Kernel32.dll
  • Soft: Enabled

As you can see, this function returns a UInt32value that will be non zero if it has succeeded creating the Timer. In fact, the returned value is a Handlerpointing to the Timer structure of data. Thus, it is recommended to store this value in a property you can access later when you need it for calling other functions to delete (i.e: cancel), and for deleting the handler in order to free the used memory.

From the Xojo code point of view, later we can use this external function in this way:

Dim name As CString = "name"
handler = CreateWaitableTimerW(Nil, False,  name)

In this example, the handlervariable is a property set in a class that works as a wrapper(see the example project).

Once we get the Handler, we will be able to set the parameters of the Timer functionality, using the SetWaitableTimerfunction. As in the previous case, we need to add a new External Method using the following values:

  • Method NameSetWaitableTimer
  • Parameters: hTimer as uint32, byref lpDueTime as LARGE_INTEGER, period as uint32, CompletionRoutine as ptr, parameter as uint32, resume as Boolean
  • Return Type: Boolean
  • Lib: Kernel32.dll
  • Soft: Enabled

As you can see in the method signature, we are using a data type that is not available in the Xojo Framework: LARGE_INTEGER. This is a very simple data structure needed when calling the function (in fact, it needs a pointerto this structure, so this is why we employ the ByRefkeyword here. This is the way to get a pointer to the structure).

So, we need to add a new Structure definition into our Xojo project, using these values:

  • Structure Name: LARGE_INTEGER

And setting the following value in the Declarationeditor for the Structure declaration:

  • quadPart As Int64.

Setting the Timer Values

Now we can write the Xojo code to call the added External Method:

pLarge.quadPart = -5
b = SetWaitableTimer(handler, pLarge, TimerPeriod, AddressOf callback, 0, False)

The important arguments passed to the function are these:

  • Handler: This is the (non zero) value we got calling the previous function.
  • pLarge: This is a property added to the project, and whose data type matches the defined structure. As you can see, we have previously assigned to it the negative value -5. It is very importantto assign here a value, or starting point from the past time, in this case, five milliseconds.
  • TimerPeriod: Here we set the firing period for the Timer (for example, 1 ms.).
  • AddressOf callback: Here we set the memory address of the Xojo method we want to execute when the Timer fires. It is very importantto add this method into the project as a Shared Method. In this example, the Xojo Method name is callback.

The function call will return a Boolean value: Trueif it had success, or Falseotherwise.

Signaling the Timer Firing

However, this kind of timer need to be signaledfor them to execute. That means that we need to add other External Method to our Module from the Windows Kernel32.DLLlibrary: SleepEx:

  • Method NameSleepEx
  • Parameters: miliseconds as uint32, bAlertable as Boolean
  • Return Type: Uint32
  • Lib: Kernel32.DLL
  • Soft: Enabled

Once it has been set, we can create an endlees loop in our Xojo Code, so the Timer keeps signaling:

Do 
  result  = SleepEx(INFINITE, True)
  app.DoEvents
Loop Until CancelTimer = True

I’m using here the Boolean CancelTimerproperty in order to have a way to exit the loop (and interrupting the Timer execution) when required from the app.

Another important parameter is the one used by the call to the SleepExfunction: INFINITE. This is a Constantadded to the project, whose value in hexadecimal is &hFFFFFFFF.

Timer Cleaning

Unlike the Windows High Definition or Multimedia Timers, we can’t modify the firing period for the WaitableTimers once they have been set. The only way to do it is to delete the one previously created, and create a new one; something you need to do when quitting the app or when you just need the Timer to fire once.

For this, we need to use the CancelWaitableTimerfunction. Add a new Exterrnal Method with this values for the method signature:

  • Method NameCancelWaitableTimer
  • Parameters: thandler as uint32
  • Return Type: Boolean
  • Lib: Kernel32.DLL
  • Soft: Enabled

In addition, and as part of the cleaning process, we need also to call the function in charge of freeing the used Handler. Add the last External Method with this signature:

  • Method NameCloseHandle
  • Parameters: tHandler as Uint32
  • Return Type: Boolean
  • Lib: Kernel32.dll
  • Soft: Enabled

Now, you can use the following Xojo code to cancel the timer and delete the handler:

If handler <> 0 Then
  Dim b As Boolean
  b = CancelWaitableTimer( handler )
  b = CloseHandle( handler )
End If

To Summarize

As you can see, the main disadvantage of this technique is that you’ll be interrupting the usual workflow of the app: it will not return to the calling method or event once it enters into the method in charge of creating and firing the Timer (until the exit the Loop, of course). The way to make the app responsive and execute the code associated with the UI controls is through the App.DoEventsline of code.

Additional Reading:

Example Project

You can download the example project, available from this link, to see and test the following:

  • Exposed technique in action (Timers with a maximum resolution of 1 ms.)
  • How to modify the period value with the cancelation of a previously created timer
  • How to modify on the fly the Callback code executed by the Timer
  • How to get the Current, Minimum and Maximum clock resolutions from the Windows computer you’re using to run the app

Leave a Reply

Your email address will not be published. Required fields are marked *