Using timers

by Kent Reisdorph

Some applications require code to be executed at a specific time interval. Timers are used for this purpose. Windows is, obviously, an event-drive operating system. For this reason you must deal with the timers provided by Windows. Put another way, you canít get directly to hardware interrupts for timing purposes. The exception to this is if you write a device driver for a specific piece of hardware, but that is beyond the scope of this article.

Windows comes with two basic types of timers: the system timer and the multimedia timer. Iíll explain the differences between these timer types, and the implications of using both.

One of the problems with an article on timers is that it is difficult to come up with a timer example that depicts a real-world programming situation. Most articles on timers create a clock and update its display every second. Creating a clock is a convenient way to illustrate the use of timers, but it isnít particularly useful. I wonít create a clock in this article. What I will do is show you the basics of using timers and leave it to you to decide when and where to use them.

 

How timers work

The concept behind timers is simple. You set an interval for the timer and activate it. The timer will fire each time the interval period elapses. You specify a callback function when you create the timer. The callback function will be called whenever the timer fires. In the case of the Windows system timer, you can opt to have a WM_TIMER message sent to your applicationís Window procedure each time the timer fires rather than using a callback function.

In the days of Windows 3.1 you had to be careful using timers. There were only so many timers available, and you never knew for sure whether you would get a timer handle from the system when you requested one. With 32-bit Windows, however, there is virtually no limit to the number of timers available. Further, todayís hardware and operating systems handle dozens or hundreds of timers simultaneously without difficulty.

 

Using the system timer

Of the two timers provided by Windows, the system timer is by far the easiest to use. It is also the lease accurate.

 

Limitations of the system timer

There are two important things that you should know about the system timer. First, this timer uses the system clock to perform timing. As such, the minimum resolution (the lowest interval you can effectively use) is about 55 milliseconds. You can certainly set the timer interval to a value less than 55, but the results will be unpredictable. This is particularly true on Windows 95 and 98. Windows NT and 2000 are less susceptible to this, but it is still not advisable to depend on the system timer for critical applications.

The second thing you need to be aware of when using the system timer is that it uses the WM_TIMER message to notify you each time the interval elapses. Of all the messages in Windows, WM_TIMER is one of the lowest priority messages. If the system is busy, WM_TIMER messages may be delayed. In some cases WM_TIMER messages may be removed from the message queue altogether. This means that a given timer event may not occur at all if the system is busy.

Considering these two factors, the system timer is potentially both inaccurate and unreliable. However, that is not to say that the system timer is unusable. Many tasks for which you will use a timer donít require a high degree of accuracy or reliability. Letís say you have an application that performs some service at 1:00 AM each day (a backup operation, for example). How do you know when itís 1:00 AM? You could use a timer that fires once per second and checks the current time. If the time is determined to be 1:00, then some processing is performed. In this example it doesnít much matter that the system timer is a bit inaccurate, nor does it matter that a timer message may be lost by Windows.

 

Putting the system timer to use

The most efficient way to access the system timer is to simply use the VCL TTimer component. TTimer is very simple to use. Just drop one on the form and set its Interval property to the timer interval you wish to use. The Interval property is specified in milliseconds. To create a timer that fires each second, for example, set the Interval property to 1000. Next create an event handler for the OnTimer event. Place code in the event handler that you wish to execute each time the timer fires. The timer can be enabled or disabled by setting the Active property to either true or false.

The other way to create and use a system timer is using the Win32 API. The SetTimer() function is used to start a timer and to set the timer interval. The KillTimer() function is used to stop the timer and free resources associated with the timer. Thereís really no need to use the API to manage timers when you have the TTimer component. TTimer is simply a wrapper around the Win32 API functions.

 

Using the multimedia timer

As I have said, the system timer is both inaccurate and potentially unreliable. Fortunately a solution is available in the multimedia timer provided by Windows. Donít let the name fool you; the multimedia timer can be used for any purpose at all. It is not limited to multimedia processes.

The multimedia timer has a resolution of 1 millisecond. Further, it is not subject to lost timer messages as the system timer is. This makes the multimedia timer a good candidate for timing needs in all but the most demanding of situations.

 

Starting and stopping the timer

The multimedia timer functions are found in the MMSYSTEM.H header. You may or may not have to explicitly include this header, depending on the version of C++Builder you have. To use the multimedia timer you first call timeBeginPeriod() to set the minimum timer resolution you need. For example:

timeBeginPeriod(1);

Next you call the timeSetEvent() as shown here:

MMRESULT timerID;
...
timerID = timeSetEvent(
  1, 0, TimeProc, 0, TIME_PERIODIC);

The first parameter is used to specify the timer interval. In this case I have specified an interval of one millisecond. The second parameter is used to set the delay. You should pass zero for this parameter, indicating that you want the greatest accuracy possible. The third parameter is used to pass the address of the callback function that will be called when the timer fires. I will explain the callback function in the next section. The third parameter is used to pass user-specified data to the callback function. You can pass anything you want for this parameter, including a pointer to an object. The last parameter is used to specify the type of timer. The possible values are TIME_ONESHOT and TIME_PERIODIC. Passing TIME_ONESHOT for this parameter indicates that the timer is a one-shot timer; the timer fires at the end of the specified interval and never fires again. The TIME_PERIODIC value is used when creating a timer that will fire repeatedly, once for each time the specified timer interval passes. timeSetEvent() returns zero if an error occurred, or a timer handle if the function succeeded. You will use this handle later to stop the timer.

To stop the timer, call timeKillEvent(), passing the handle of the timer you received when you called timeSetEvent(). For example:

timeKillEvent(timerID);

You must also call timeEndPeriod(), passing the same value you passed to timeBeginPeriod() as shown here:

timeEndPeriod(1);

Each call to timeBeginPeriod() must have a matching call to timeEndPeriod().

 

The callback function

As I said earlier, the callback function is called once for each timer event. The callback function has the following signature:

void CALLBACK TimeProc(
  UINT uID, UINT uMsg, DWORD dwUser, 
  DWORD dw1, DWORD dw2);

The first parameter is the timer ID of the timer that generated the event. This allows you to use multiple timers with a single callback function (not that you would want to). The second parameter is reserved by Windows and is not used. The third parameter is used to pass user-defined data to the callback. The data you passed in the call to timeSetEvent() will be passed to the callback function in this parameter. The final two parameters are also reserved and should not be used.

The callback function must be a stand-alone function; it cannot be a class member function.

The Windows documentation for the callback function says that you should call only the PostMessage(), timeGetSystemTime(), timeGetTime(), timeSetEvent(), timeKillEvent(), midiOutShortMsg(), midiOutLongMsg(), and OutputDebugString() functions from within the callback itself. At first glance this of approved functions looks very restrictive. However, the key is the PostMessage() function. What you typically do is create a user-defined message. Then you create a handler for that message in your applicationís main form class. From within the callback you post yourself a message by calling PostMessage(). This way the handler for the user-defined message does all the work. You can call any functions you like from the message handler for the user-defined message.

 

Lean and mean code

Regardless of the timer used, care must be taken to ensure that the code that executes as the result of a timer event is short and concise. This is not typically necessarily if your timer has a long interval. If the timer interval is short, however, you need to be sure your code his time to execute before the next timer event comes along.

With todayís machines it is surprising how much code you can execute in one millisecond. Still, if you are using a timer with a one millisecond interval you should know how much time your code takes to execute. A good profiler is indispensable in determining this. I used TurboPowerís Sleuth QA Suite 2 when writing this article. Sleuth QA Suite 2 has both method and line profilers, a memory-checking tool, a coverage analyzer, and a macro recorder for automating testing tasks. You can find out more about Sleuth QA Suite 2 at www.turbopower.com.

 

Conclusion

Timers can be used for a variety of purposes. For most applications the system timer, accessed via TTimer, works just fine. For applications that require more accuracy and dependability, the multimedia timers are the way to go.