Sure, we have RegisterWindowMessage and RegisterClipboardFormat, but where are DeregisterWindowMessage and DeregisterClipboardFormat?

The Register­Window­Message function lets you create your own custom messages that are globally unique. But how do you free the message format when you're done, so that the number can be reused for another message? (Similarly, Register­Clipboard­Format and clipboard formats.)

You don't. There is no Deregister­Window­Message function or Deregister­Clipboard­Format function. Once allocated, a registered window message and registered clipboard format hangs around until you log off.

There is room for around 16,000 registered window messages and registered clipboard formats, and in practice exhaustion of these pools of numbers is not an issue. Even if every program registers 100 custom messages, you can run 160 unique programs before running into a problem. And most people don't even have 160 different programs installed in the first place. (And if you do, you almost certainly don't run all of them!) In practice, the number of registered window messages is well under 1000.

A customer had a problem with exhaustion of registered window messages. "We are using a component that uses the Register­Window­Message function to register a large number of unique messages which are constantly changing. Since there is no way to unregister them, the registered window message table eventually fills up and things start failing. Should we use Global­Add­Atom and Global­Delete­Atom instead of Register­Window­Message? Or can we use Global­Delete­Atom to delete the message registered by Register­Window­Message?"

No, you should not use Global­Add­Atom to create window messages. The atom that comes back from Global­Add­Atom comes from the global atom table, which is different from the registered window message table. The only way to get registered window messages is to call Register­Window­Message. Say you call Global­Add­Atom("X") and you get atom 49443 from the global atom table. Somebody else calls Register­Window­Message("Y") and they get registered window message number 49443. You then post message 49443 to a window, and it thinks that it is message Y, and bad things happen.

And you definitely should not use Global­Delete­Atom in a misguided attempt to deregister a window message. You're going to end up deleting some unrelated atom, and things will start going downhill.

What you need to do is fix the component so it does not register a lot of window messages with constantly-changing names. Instead, encode the uniqueness in some other way. For example, instead of registering a hundred messages of the form Contoso user N logged on, just register a single Contoso user logged on message and encode the user number in the wParam and lParam payloads. Most likely, one or the other parameter is already being used to carry nontrivial payload information, so you can just add the user number to that payload. (And this also means that your program won't have to keep a huge table of users and corresponding window messages.)

Bonus chatter: It is the case that properties added to a window via Set­Prop use global atoms, as indicated by the documentation. This is an implementation detail that got exposed, so now it's contractual. And it was a bad idea, as I discussed earlier.

Sometimes, people try to get clever and manually manage the atoms used for storing properties. They manually add the atom, then access the property by atom, then remove the properties, then delete the atom. This is a high-risk maneuver because there are so many things that can go wrong. For example, you might delete the atom prematurely (unaware that it was still being used by some other window), then the atom gets reused, and now you have a property conflict. Or you may have a bug that calls Global­Delete­Atom for an atom that was not obtained via Global­Add­Atom. (Maybe you got it via Global­Find­Atom or Enum­Props.)

I've even seen code that does this:

atom = GlobalAddAtom(name);

// Some apps are delete-happy and run around deleting atoms they shouldn't.
// If they happen to delete ours by accident, things go bad really fast.
// Prevent this from happening by bumping the atom refcount a few extra
// times so accidental deletes won't destroy it.

So we've come full circle. There is a way to delete an unused atom, but people end up deleting them incorrectly, so this code tries to make the atom undeletable. Le Chatelier's Principle strikes again.

Comments (23)
  1. JamesJohnston says:

    The fun part is that a certain competitor to Visual C++ would globally add atoms with unique numbers, even for the most basic "hello world" program.  One atom contained the process ID, and another atom contained the combination of HINSTANCE and thread ID.  As you can imagine, programs written that use this runtime environment eventually exhaust the global atom table if they are restarted often (e.g. automatically).

    Hopefully it is not unreasonable to link to this blog post, as the offending product isn't named in the article text……/identifying-global-atom-table-leaks.aspx

  2. DWalker says:

    Ideally, the documentation for "register anything" would say how to, or WHETHER it's necessary to, deregister the thing.  And how to query the status of the thing.  But I see Raymond's point.

    On a related note, lots and lots of documentation, for all kinds of software and systems, will tell you how to SET some option, but not tell you how to TEST or CHECK the value of that option.  I might want to know how to check the value of an option.

    The Microsoft SQL Server documentation used to be really bad about this. It would show the syntax for SET ANSI_NULLS ON and SET ANSI_NULLS OFF, for example, but not show how to query the value.  

    I have been asking for 10 years for the documentation of everything that can be SET to also describe how to find the current value.  I see that this has been done for some of the SQL documentation.  Yay!  Some of the doc teams have implemented some of my suggestions over the years, and I think the docs are better.  :-)

  3. skSdnW says:

    IIRC the SetWindowSubclass functions in comctl32 bumps (used to bump?) the SetProp atom refcounts. Not sure if it is done because of old Win9x atom bugs or as a defense against buggy apps. I'm sure you know the inside story here Raymond?

  4. Adrian says:

    Is there a way to get a list of the registered window messages?  When the resource does get depleted, it would be helpful to get a hint as to who the culprit is.  Spy++ can show them when one is sent or posted, but it doesn't seem to offer a list of registered ones.

    I've read claims that registered window messages and registered clipboard formats are in the same space, and that you therefore can use the clipboard API to figure out the text for a given registered window message.  Clearly, that's an undocumented detail that shouldn't be relied upon because it might change in a new version of Windows.  But is that how Spy++ does it?  Or is there an approved API that I'm missing in my searches?

  5. SimonRev says:

    I don't know of such a way Adrian, but if Spy++ can do it, all you have to do is send every message in the registered message space to a window you are spying on and you should get that list, if inelegantly.

  6. laonianren says:

    @JamesJohnston It's not just Visual C++'s competitors.  Every time you launch Visual C++ 2010 the main Window gets a class name containing a new GUID.

    Window class names seem to be stored in the same atom table as message and clipboard format names.

    Restarting Visual Studio (or any WPF app) will eventually exhaust the atom table.

    [Those atoms will be cleaned up when the class is unregistered. Even if the app crashes or exits without calling UnregisterClass (admit it, you don't either), the window manager will unregister on the app's behalf. -Raymond]
  7. SimonRev says:

    @Iaonianren:  Only if they fail to call UnregisterClass after destroying the main window AND RegisterClass was called from a DLL and not the main EXE.  [If Visual Studio crashes on the order of 16,000 times in a single session of Windows, you have bigger problems :)]

    Actually using a GUID is a decent way to ensure that no one is squatting on your class name.

  8. laonianren says:

    Mea culpa.  I wrote some code to try this out, and I did test to see if class names were deleted, but I used the same name I'd previously used to test if windows message names and class names were in the same atom table.  And obviously that name didn't get deleted…

  9. Paradice says:

    Whenever I see a Raymond post about "actually, this limit is never a problem in practice", I get a little hesitant about jumping into the comments section – more often than not, someone has built a terrible design and has hit whatever limit is being discussed.

    Thankfully, not the case this time!

  10. Neil says:

    In Win16, RegisterClipboardFormat and RegisterWindowMessage even have the same address.

  11. Roger Hågensen says:

    What about a system using Hybernation instead of cold starting, eventually it will use up all messages right? What happens then?

  12. Random832 says:

    @Neil Not just in Win16. I can't find any information on whether this equivalency is documented, though.

  13. Yukkuri says:

    This is my favorite thing about The Old New Thing: stories about how incredibly wrong people make their software

  14. SimonRev says:

    About the statement "Even if the app crashes or exits without calling UnregisterClass":  

    From MSDN we can see "No window classes registered by a DLL are unregistered when the DLL is unloaded. A DLL must explicitly unregister its classes when it is unloaded."

    It seems highly likely that since the examples in question are WPF windows, that RegisterClass is being called from a DLL.  The way I parse that sentence from MSDN would mean that an application crash would leak an atom.

    [The automatic unregistration happens at process termination, not DLL unload. -Raymond]
  15. Csaboka says:

    Roger Hågensen: Eventually, Windows Update will find a patch that requires a restart, and the full restart after the installation will clean up the message table, among other things. :D

  16. Sven2 says:

    Well, let's play "defend the theoretical problem": Let's say you never reboot your computer but always just hibernate for three months and there happens to be no update that requires a reboot during the two patch days.

    Let's also assume that you work e.g. as some kind of software compatibility tester (or game reviewer, etc.) and you install, test and uninstall 20 new programs every day.

    Then after 90 days (you work every weekend), you have run 1800 programs. If each program registered just 10 messages, you will run out of messages!

  17. Erik F says:

    @Sven2: If you're installing and uninstalling that many programs a day, exactly what kind of testing would you manage to complete? In an 8-hour day, you'd have 24 minutes per program to install, test and uninstall each program. Seems unlikely. Anyways, I can't think of a good reason for every program in existence to register window messages; the standard messages work for most uses.

    (Also, allowing for resource leakage you'd probably have to log out and back in every once in a while, which would release all of those registered window messages!)

  18. Gabe says:

    Roger Hågensen: The idea that Raymond was trying to get across is that any given application will (or should!) always use the same messages, no matter how many times it is executed.

    The only way to run out of messages is to run apps that combine to use over 16,000 different messages. If you have an app that has 100 messages and you run it more than 160 times, you won't run out of messages because it will still only be using the same 100 messages as the first time you ran it.

    So in order to run out of messages, either have some app that registers *new* messages every time it is executed (perhaps caused by a bug in the runtime as others have noted), run hundreds (or more likely thousands) of different apps, or have some app(s) registering thousands of different messages.

    Since 100 messages is an order of magnitude more than a typical app registers and 160 apps is an order of magnitude more than a typical user runs, it is very rare to run out of messages and hence deregistering them is unnecessary.

  19. 640k says:

    Why is the limit less that physical RAM + swap?

    [Pigeonhole principle. -Raymond]
  20. Gabe says:

    Sven2: It's hard to imagine being able to install and uninstall hundreds of different programs without a single one requiring a reboot or logout!

    Even if you did manage to find such programs, what are the odds that your system won't have a single Windows update requiring a reboot during those several months?

    Furthermore, I doubt that actual programs average anywhere close to 10 unique window messages and clipboard formats. I'm guessing the average is less than 1, even.

  21. JamesJohnston says:

    @laonianren:  Raymond already said those atoms will be cleaned up; to quote RegisterClass from MSDN:  "All window classes that an application registers are unregistered when it terminates."

    In contrast, the VC++ competitor in question calls GlobalAddAtom directly (I checked the source code).  There is a cleanup function that calls GlobalDeleteAtom if the application properly terminates.  However if the application crashes, GlobalDeleteAtom won't be called and the atoms are leaked:  "Global atoms are not deleted automatically when the application terminates."

  22. JamesJohnston says:

    @640k:  If you examine the history behind your own name, I think you will learn the reason why. ;)

  23. Joshua A. Schaeffer says:

    So you can basically maliciously lock other apps out with while(1) RegisterWindowMessage(…); ?

    [If you have desktop access, then you can maliciously lock out other apps by calling TerminateProcess on them. -Raymond]

Comments are closed.

Skip to main content