Certain Windows®
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.
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®
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.
Have a question about programming in Windows? Send it to Matt at mpietrek@tiac.com
|