Code for this article: Hood0397.exe (9KB)
Matt Pietrek is the author of Windows 95 System Programming Secrets (IDG Books, 1995). He works at NuMega Technologies Inc., and can be reached at firstname.lastname@example.org.|
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.® documentation doesn't really let on that timers are special in the eyes of DispatchMessage and the messaging system. In both of the questions I addressed, a rote knowledge of how to do things and how things work in 16-bit Windows proved to be insufficient in 32-bit Windows.
You can see that the DispatchMessageA function is just a wrapper around an internal USER32 function called DispatchMessageWorker. Besides a pointer to the message to dispatch, DispatchMessageWorker takes a parameter that specifies whether it was called in its ANSI flavor (DispatchMessageA) or Unicode (DispatchMessageW). DispatchMessageWorker needs to know this so that it can convert characters passed in the WPARAM to the ANSI or Unicode form that the target window expects.
The first significant thing DispatchMessageWorker does is extract the HWND from the MSG structure and pass it to the @ValidateHwnd function. This serves two purposes. Obviously, it ensures that the message will be dispatched to a valid window. More interestingly, @ValidateHwnd returns a pointer to an internal data structure that represents a window (which I've called a WND structure in the pseudocode).
The fact that @ValidateHwnd returns a WND pointer surprised me at first. The window data structure contains very sensitive internal system information. Normally, ring 3 code (including ring 3 system DLLs like USER32.DLL) isn't supposed to have access to this privileged information. Further investigation lead to an interesting discovery. In Windows NT, WND data structures are kept in memory above 2GB, which is owned by WIN32K.SYS (the Windows NT 4.0 ring 0 portion of USER32). As you may know, addresses above 2GB in Windows NT are off-limits to ring 3 code. Specifically, these addresses have the supervisor attribute (in the Intel architecture), and ring 3 code will fault if it tries to access these addresses. So how does the ring 3 USER32 get a pointer to a WND structure? It turns out that Windows NT uses the processor's page tables to create a read-only range of addresses below 2GB that map to the same physical memory as the ring 0 WND structures above 2GB. Thus, the ring 3 USER32.DLL can read, but not write, the WND structures maintained by WIN32K.SYS.
Returning to the ring 3 DispatchMessageWorker code, the remainder of the function splits into two major parts. The latter part executes if the messages is a WM_TIMER or WM_SYSTIMER message. The former part is for handling all other messages. This is further evidence that timer messages are handled specially by the system. Incidentally, WM_SYSTIMER is a well-known yet still undocumented message. Windows uses WM_SYSTIMER for internal actions like scrolling.
In the timer section of the DispatchMessageWorker function, if the timer was set up to post a window message, the code simply jumps to where normal messages are dispatched. In other words, if you receive your timer notifications via a WM_TIMER message, DispatchMessageWorker treats the timer message like any other message. Special processing is only required if the timer is set up to call a TIMERPROC function. In the case of the WM_TIMER message, DispatchMessageWorker extracts the TIMERPROC address from the message's LPARAM and just calls it. If the timer is a WM_SYSTIMER type, DispatchMessageWorker lets another internal function, NtUserDispatchMessage, handle it.
What does DispatchMessageWorker do with regular, non-timer window messages? The dispatching of these messages depends on what type of window (16 or 32-bit) the dispatchee window is, as well as whether the message is a WM_PAINT message. In the simplest case, DispatchMessageWorker simply reaches into the WND procedure, grabs out the WNDPROC address, and calls it with the appropriate parameters. Since some messages contain character values in their WPARAM, DispatchMessageWorker makes the appropriate conversions between ANSI and Unicode characters as necessary.
If the message being dispatched is for a 16-bit window, a special function, pfnWowWndProcEx, handles the message. Why would a 32-bit app be retrieving and dispatching messages for a 16-bit window? The answer is WOW (yep, that horrible acronym for Windows on Windows). In Windows NT, the NTVDM process is a 32-bit process that encapsulates and runs 16-bit Windows-based applications within the otherwise entirely 32-bit system.
If the message isn't handled via one of the simple scenarios that I've just described, DispatchMessageWorker pushes the message off to the ring 0 NtUserDispatchMessage I alluded to earlier. When this happens the message is usually a WM_PAINT. Like the timer messages, WM_
PAINT has extra significance to the windowing system. It's best to let the ring 0 USER component, where the real brains lie, do the work.
To summarize, DispatchMessageWorker handles the simple messages that don't need much system knowledge and that can be dispatched with a minimum of fuss. Anything more complicated gets shuttled off to the NtUserDispatchMessage function in WIN32K.SYS at ring 0. While writing this column, I did look into the NtUserDispatchMessage function and found it rather tricky and complicated, so I won't attempt to describe it in this limited space.
Now let's return to what happens if you use the "posting a phony WM_TIMER message" trick that I described earlier. Looking at the pseudocode for DispatchMessageWorker and mentally executing the code, you'll see that execution very quickly gets to the else clause that handles WM_TIMER messages. Since the LPARAM is nonzero when using this trick, execution quickly gets to the line that calls through pfnTimerCallback, where pfnTimerCallback is whatever value is in the LPARAM.
What's really interesting here is not what the code does, but what it doesn't do. DispatchMessageWorker makes no attempt to validate the LPARAM of the WM_TIMER message to see if it's a valid code address. Instead, DispatchMessageWorker blindly assumes that the LPARAM is OK and calls it. You might be tempted to think that the system should be smart and load in the appropriate code that the LPARAM points to. The problem is, how? There's nothing in the MSG structure that indicates which EXE or DLL the LPARAM code address refers to.
I personally find it very surprising that DispatchMessage doesn't do better checking on the LPARAM for WM_TIMER messages. If there's truly no validity checking, you could post a lethal WM_TIMER message with a bogus LPARAM to any window in the system and kill the process that owns the window. To test this out, I wrote a small program that kills Windows Explorer. The program is called WM_
TIMER_TOAST, and is shown in Figure 4. The code simply locates the taskbar window on Windows NT 4.0 or Windows 95 and posts a WM_TIMER message with a bogus LPARAM value to it. Luckily, the Windows Explorer process automatically restarts if a fault occurs in it, so you can run WM_
TIMER_TOAST without having to reboot your system afterwards. It goes without saying that a malicious program could pick some other window besides the Explorer and wreak potential havoc.
What could be done to prevent this WM_TIMER loophole from being exploited? While digging around in WIN32K.SYS, I found that it uses a neat trick for WM_SYSTIMER messages. When Windows creates a new system timer, it remembers the callback address. Before dispatching a WM_SYSTIMER message, the system checks the LPARAM address against the list of registered timer callback functions. If there's no match, the message is tossed. This same technique could probably be applied to WM_TIMER message in the ring 3 DispatchMessage code, although it would add overhead to WM_TIMER dispatching.
If I were to apply a moral to all this, it would have to be that once again there's really no substitute for understanding what goes on under the hood of the system. In this case, the Win32
Have a question about programming in Windows? Send it to Matt at email@example.com
© 1997 Microsoft Corporation. All rights reserved. Legal Notices.