|Copyright © Microsoft Corporation. This document is an archived reproduction of a version originally published by Microsoft. It may have slight formatting modifications for consistency and to improve readability.|
programming topics are timeless. Questions about them
keep coming up, despite the ever growing list of knowledge bases and
publications. One such topic is timers and DispatchMessage. I've seen
this particular subject trip up even grizzled Windows veterans because
they first encountered timers in those prehistoric days when 16-bit
Windows programs roamed the land. Improvements and extensions to the
Win32® architecture have
made DispatchMessage and timers behave differently (or at least appear
that way). With this in mind, a fresh 32-bit expedition into the subject
is in order. |
Since you're probably not reading this column while sitting in front of the Win32 documentation, I'll do a quick review of the SetTimer function. The SetTimer function is the traditional method of initiating a timer in Windows (both 16 and 32-bit). As parameters, it takes an HWND, a timer interval, a timer ID (which you supply), and the address of a callback function. The callback function is of type TIMERPROC.
If you pass a non-null HWND after the specified interval has passed, a WM_TIMER message is posted to the window and the TIMERPROC parameter is ignored. The other option is passing a null HWND and a valid TIMERPROC address. In this case, after the specified time has elapsed, the HWND is ignored and the system calls your TIMERPROC function. In either case, you shouldn't rely on the timer to be extremely accurate, as you're subject to the unpredictability of Win32 thread scheduling.
With this quick review out of the way, consider a ques-tion I've heard several times recently: "I've called SetTimer in a program that doesn't have any windows. Therefore, I set the timer to call a function, rather than post a message to a window. However, my timer callback (see Figure 1) is never called."
My immediate response is to ask if their program has a DispatchMessage loop. They'll reply that they shouldn't need one. After all, they told SetTimer to call their TIMERPROC rather than post a message. The problem is that calling DispatchMessage isn't optional if you're using timers. As you'll see later, DispatchMessage is needed for both varieties of timer notifications (that is, window messages and callback functions).
To prove that DispatchMessage really is necessary, run the code in Figure 1. The program sits at a prompt, and you'll never see the message that's printed inside the MyTimerProc function. Next, run CONSOLE_WM_TIMER2 (see Figure 2). The code is identical to the first program, except that it adds a call to GetMessage followed by a call to DispatchMessage. Yes, it may seem a little strange for a console program to retrieve and dispatch window messages, but hey, it works, and the TIMERPROC function gets called.
How is this behavior different between 16 and 32-bit programs? It isn't really, but remembering to call DispatchMessage usually isn't an issue for 16-bit programs. Strange as it might seem now, 16-bit Windows doesn't support console applications. Therefore, nearly every 16-bit Windows program creates windows, and hence, has a message loop. The message loop might be explicit or hidden. As an example of a hidden message loop, think about a DialogBox-based program. The DialogBox API spends most of its time in a message loop inside USER.
Because 16-bit programs have message loops, the 16-bit Windows documentation doesn't bother to tell you that DispatchMessage is a necessary complement to timers. In reading the Remarks section of the 32-bit SetTimer documentation, you'll come across this: "When you specify a TimerProc callback function, the DispatchMessage function simply calls the callback function instead of the window procedure. Therefore, you need to dispatch messages in the calling thread, even when you use TimerProc instead of processing WM_TIMER."
If there's a lesson here, that lesson is that it's a good idea to occasionally review the API documentation. A lot of what you learned in the days of 16-bit Windows may be incomplete or inaccurate. I'm certainly guilty of skimming the Win32 documentation because the prototype of the API at the top looks familiar.
While the easy answer here is to process window messages, it may not be practical. There are other ways to get periodic callbacks under Win32. If your needs are simple and you want to keep things light, consider this option: start a second thread to act as a timer thread. The second thread enters into a loop, first calling the Sleep API for the desired interval, and then calling your callback function.
There're two potential pitfalls with this that you should be aware of. First, the true period of the callbacks will be (at a minimum) the time spent in the Sleep function as well as the time spent in the callback function. You can theoretically prevent some of the vagaries of scheduling by making the timer thread a higher priority thread than normal. However, there's still no guarantee about scheduling, and some people will argue that high-priority threads, if used excessively, can degrade overall system performance and scheduling smoothness.
The other problem with creating a second timer thread with a Sleep loop is that the callback function will be called from a different thread than your primary thread. If you're not relying on anything that's thread-sensitive (such as global data), this shouldn't be a problem. If you're using thread-sensitive resources, your code needs to use the appropriate synchronization mechanisms. A regular SetTimer-style timer doesn't have this problem, as the callback function is invoked in the same thread that called SetTimer.
Beyond this simple homegrown approach, there are a couple of other solutions to consider. First, there are the multimedia timers. I won't rehash the SDK documentation on them here. The key thing is they're not dependent on DispatchMessage to invoke the callback function. Like the homegrown solution, they also run in a separate thread. To learn more about them, see the timeSetEvent API function (and friends).
The second timer solution is the Waitable Timer objects, which are new in Windows NT® 4.0. While they're oriented toward threads that need to block for a specified period of time, they also support a completion function that's called after the specified time elapses. In the Win32 documentation, see the CreateWaitableTimer and SetWaitableTimer APIs.
Here's another question that highlights a difference between 16 and 32-bit Windows timers: "I've recently been rewriting a 16-bit Windows application to run under Windows NT. The original program uses the PostMessage(WM_TIMER) trick to force code in my task to execute in the context of another task. This trick doesn't seem to work under Windows NT (or Windows 95 for that matter)."
When I saw this question, it made me smile because it momentarily transported me back to the days when gross system-level hacks were often relatively easy to pull off. This was due to the fact that 16-bit Windows was relatively unsecure and didn't do much to prevent devious or malicious programs from doing things that most other operating systems won't allow.
If you're not familiar with the trick, it's pretty simple. Let's say you wanted some code you wrote to execute in the context of another application. By posting a particular WM_TIMER message to a window owned by the desired task, you could coerce Windows into executing your code in that task's context. The trick was to create and post a WM_TIMER message with the LPARAM holding a pointer to a TIMERPROC function of your own devising. When the other task retrieved and dispatched this WM_TIMER message, your TIMERPROC would be called in that task's context. Clever, eh?
The unspoken assumption that lets this hack-o-rama succeed is that your TIMERPROC code is visible within the context of the desired task. This isn't an issue in 16-bit Windows because there is just one address space shared by all tasks. Any task can see all the memory of any other task.
In contrast to this brazen cohabitation, Windows NT and Windows 95 have separate address spaces for each process. Therefore, the odds are reasonably high that your code isn't loaded in the address space of some other process that you'd like to execute in the context of. Sure, you can still post a WM_TIMER message with an LPARAM containing a pointer to your code. What will Windows NT and Windows 95 do when they try to dispatch a WM_TIMER message with an LPARAM pointing to code that's not loaded in the receiving process's address space? This question provides a good segue into an examination of the DispatchMessage code.
What Does DispatchMessage Do?
In my book, Windows Internals
(Addison-Wesley, 1993), I created pseudocode for the DispatchMessage
function as implemented in Windows 3.1. Since then, I haven't paid much
attention to this API. While researching this column, I decided it was
time to examine how DispatchMessage has evolved to handle the much more
complex environment of Windows NT 4.0. The result of my work is the
pseudocode for Windows NT 4.0 DispatchMessage, which is given in Figure 3.
Have a question about programming in Windows? Send it to Matt at firstname.lastname@example.org