Sending a window a WM_DESTROY message is like prank calling somebody pretending to be the police

A customer was trying to track down a memory leak in their program. Their leak tracking tool produced the stacks which allocated memory that was never freed, and they all seemed to come from uxtheme.dll, which is a DLL that comes with Windows. The customer naturally contacted Microsoft to report what appeared to be a memory leak in Windows.

I was one of the people who investigated this case, and the customer was able to narrow down the scenario which was triggering the leak. Eventually, I tracked it down. First, here's the thread that caused the leak:

DWORD CALLBACK ThreadProc(void *lpParameter)
 // This CreateWindow caused uxtheme to allocate some memory
 HWND hwnd = CreateWindow(...);
 MSG msg;
 while (GetMessage(&msg, NULL, 0, 0)) {
 return 0;

This thread creates an invisible window whose job is to do something until it is destroyed, at which point the thread is no longer needed. The window procedure for the window looks like this:

                         WPARAM wParam, LPARAM lParam)
 switch (uMsg) {
 ... business logic deleted ...

 return DefWindowProc(hwnd, uMsg, wParam, lParam);

Sinec this is the main window on the thread, its destruction posts a quit message to signal the message loop to exit.

There's nothing obviously wrong here that would cause uxtheme to leak memory. And yet it does. The memory is allocated when the window is created, and it's supposed to be freed when the window is destroyed. And the only time we exit the message loop is when the window is destroyed. So how is it that this thread manages to exit without destroying the window?

The key is how the program signals this window that it should go away.

void MakeWorkerGoAway()
 // Find the worker window if it is registered
 HWND hwnd = GetWorkerWindow();
 // If we have one, destroy it
 if (hwnd) {
  // DestroyWindow doesn't work for windows that belong
  // to other threads.
  // DestroyWindow(hwnd);
  SendMessage(hwnd, WM_DESTROY, 0, 0);

The authors of this code first tried destroying the window with DestroyWindow but ran into the problem that you cannot destroy a window that belongs to a different thread. "But aha, since the DestroyWindow function sends the WM_DESTROY message, we can just cut out the middle man and send the message directly."

Well, yeah, you can do that, but that doesn't actually destroy the window. It just pretends to destroy the window by prank-calling the window procedure and saying "Ahem, um, yeah, this is the, um, window manager? (stifled laughter) And, um, like, we're just calling you to tell you, um, you're being destroyed. (giggle) So, um, you should like pack up your bags and (snort) sell all your furniture! (raucous laughter)"

The window manager sends the WM_DESTROY message to a window as part of the window destruction process. If you send the message yourself, then you're making the window think that it's being destroyed, even though it isn't. (Because it's DestroyWindow that destroys windows.)

The victim window procedure goes through its "Oh dear, I'm being destroyed, I guess I'd better clean up my stuff" logic, and in this case, it unregisters the worker window and posts a quit message to the message loop. The message loop picks up the WM_QUIT and exits the thread.

And that's the memory leak: The thread exited before all its windows were destroyed. That worker window is still there, because it never got DestroyWindow'd. Since the window wasn't actually destroyed, the internal memory used to keep track of the window didn't get freed, and there you have your leak.

"You just got punk'd!"

The correct solution is for the MakeWorkerGoAway function to send a message to the worker window to tell it, "Hey, I'd like you to go away. Please call DestroyWindow on yourself." You can invent a private message for this, or you can take advantage of the fact that the default behavior of the WM_CLOSE message is to destroy the window. Since our window procedure doesn't override WM_CLOSE, the message will fall through to DefWindowProc which will convert the WM_CLOSE into a DestroyWindow.

Now that you understand the difference between destroying a window and prank-calling a window telling it is being destroyed, you might be able to help Arno with his problem.

Comments (27)
  1. Cesar says:

    So, the problem is that WM_DESTROY is a notification, while WM_CLOSE is a command, and they were sending the notification as if it were a command?

    Perhaps this was not obvious to them and they were thinking that WM_DESTROY was a command. That is, they probably were thinking that all DestroyWindow does is send or post or whatever is the correct term a WM_DESTROY message and that all the window deletion code was within DefWindowProc when called with WM_DESTROY.

    This is why I like looking at the source code whenever I use a function I am not used to (or, when programming for Windows, look at the Wine source code, which should act similar enough).

  2. send a message to the worker window to tell it, "Hey, I'd like you to go away. Please call DestroyWindow on yourself."

    I would call it UWM_GTFO or UWM_ESAD

  3. Roger Lipscombe says:

    I've long thought that Windows should have made more of a distinction between those messages that are commands and those that are notifications (WM_DESTROY vs WM_DESTROYING vs WM_DESTROYED). Obviously, that would involve inventing a time machine — it's way too late to fix it now. It just means that you can't assume that this is obvious from the message name.

  4. Wyatt says:

    The last link goes to the top of the suggestion page rather than the specific comment.  Looks like the redirect is dropping the target.

    [It goes to the comment if the page is already cached. You have "Web 2.0 technology" to thank. -Raymond]
  5. Rick C says:

    Fortunately you can search for "Arno" on the suggestion box:  An app with a window and a modal dialog box.  If another app sends WM_DESTROY while the dialog is active, the app crashes, and the question is "I mean, what is the standard way for an application to close wheter it has a modal box or not ? "

    I would assume the answer is an extension of the same–have both the parent window and the dialog box accept WM_CLOSE, or a user-defined message.  If the dialog gets it, it would have to communicate to the parent window that it was told to shut down externally, probably by something like a certain value from nResult in EndDialog().

  6. Medinoc says:

    That error is very easy to make. I remember making it as a beginner, until I read the documentation saying "WM_DESTROY tells the window that it's being destroyed".

  7. Frederik Slijkerman says:

    "This is why I like looking at the source code whenever I use a function I am not used to (or, when programming for Windows, look at the Wine source code, which should act similar enough)."

    This is why I like looking at the documentation before using a function or message.

  8. Billy O'Neal says:

    @Cesar: The problem with doing that is that you don't know what's supposed to be a contract (maintained between versions) or an implementation detail (changes between versions). You need documentation to tell the difference.

  9. Cesar says:

    @Frederik Slijkerman, @Billy O'Neal: I thought looking at the documentation went without saying. But if you missed it when reading the documentation, looking at the source code would make it clear.

  10. djeidot says:

    Shouldn't it be called WM_ONDESTROY instead of WM_DESTROY?

  11. Jim says:

    This is a good topic. Cause we involved with developers with big argument about the memory leak which cause the embedded system crash. We can see leak in the testing tool. Then we told the developer, he attacked our testing tool rather than fix the problem. Although the leak was getting better, but it was not resolved at all. But the system got crashed a less frequently then everybody was happy?

  12. henke37 says:

    Why not just make a rule that only the owner can send events from a window and prevent the error from being made in the first place? We seriously need to write a note about thinking about the long long term effects of the design for when the timemachine is completed.

    [How do you know that the code sending the message is the "owner"? From a kernel standpoint, objects don't send messages; threads do. (You might semantically attribute the action to an object, but that's a conceptual thing not encoded anywhere in the CPU state.) -Raymond]
  13. Ben Voigt says:

    @henke37: I'm pretty sure that the completion of the time machine will herald the end of thinking about design.  After all, if our design is sub-optimal, we can always use the time-machine later, to fix it retroactively.

    At least, that's the argument all managers will use to justify firing all architects, designers, and in fact any programmer who actually understands their trade and expects to be compensated correspondingly.

  14. Joshua says:

     // DestroyWindow doesn't work for windows that belong

     // to other threads.

    Hmmm. Last time I tried it it worked just fine, but that was in a 16 bit program on Win95.

    I'm sure Raymond would agree that calling DestroyWindow on a window owned by another process is a bad idea.

    It's the kind of tool I'd only reach for these days if I had to blast a virus out of the system.

  15. Jeff says:

    I notice that (on Vista at least) you can't send WM_CREATE from one thread to another, even within the same process. Odd that WM_DESTROY doesn't have the same restriction.

  16. Rick C says:

    "[It goes to the comment if the page is already cached. You have "Web 2.0 technology" to thank. -Raymond]"

    Actually I couldn't get it to work right in Chrome 14 *or* IE9.  IE redirected the page and lost the anchor; Chrome redirected and kept the anchor in the address bar but wouldn't navigate to the link.  In both browsers, I tried hitting the link several times to see the "from cached" behavior.

    [I'll tweak the link to at least take the redirect out of the mix. Maybe that'll help. -Raymond]
  17. Cheong says:

    Sinec this is the main window on the thread,…


  18. Cheong says:

    You don't perform UI update from the other threads, so it's only logical to NOT perform UI destruction from the other threads.

  19. Crescens2k says:


    Were you checking for memory leaks? While sending WM_DESTROY does work fine in that it provokes the window destroy action, it doesn't do the rest of the actions that calling DestroyWindow does.

    So yes, sending the message even in Windows 7 "works fine" in the sense that the window is destroyed, it doesn't work fine in that none of the associated cleanup takes place.

  20. Crescens2k says:

    -_-; oops ignore that, I posted it by accident after reading, typing it out and realising that I misunderstood the post. Sorry. One of those cases where you want the bug of it not posting your message to happen but it doesn't.

  21. @Joshua:

    "Last time I tried it it worked just fine, but that was in a 16 bit program on Win95."

    For Win16 app in Windows 9x, there is only one thread (effectively) in the whole system. One can say that even Win32 apps in Win9x might not have had this DestroyWindow restriction, because the whole USER system was pretty much serialized.

  22. @Jeff:

    If you're ever sending WM_CREATE, you're doing things wrong. WM_CREATE can only be sent by Windows, and never cross-thread.

  23. TC says:

    @Cesar: "if you missed [something] when reading the documentation, looking at the source code would make it clear."

    That doesn't make it clear at all – for the reason Billy stated: you don't know what is maintained between versions, and what is just an implementation detail that might change between versions. "You need documentation to tell the difference."

  24. Neil says:

    Caching the page doesn't help because the page doesn't actually have the comments on it. (Try visiting the blog with a browser with scripting disabled.) The target doesn't even exist until the (presumed) XHR has completed and the page populated. At this point you can then click Go again and the browser should just scroll to the link. (I'm sure that this step could be automated.)

    Earlier versions of the blogging software would output a number of comments (with a more link) and then XHR the rest (deleting the link) so you could at least link safely to a comment that made the cut.

  25. Rick C says:

    It looks like the workaround Neil mentioned, clicking Go a second time after the page has loaded completely, works for IE9, but NOT for Chrome.  Yay, Community Server.

  26. Andy Feldstein says:

    Thank you for the smile–your anthropomorphization of the wrong code hit just the right note.

Comments are closed.