You can’t use the WM_USER message in a dialog box


Today, I'm not actually going to say anything new. I'm just going to collate information I've already written under a better title to improve search engine optimization.

A customer reported that they did the following but found that it didn't work:

#define MDM_SETITEMCOUNT WM_USER

INT_PTR CALLBACK MyDlgProc(HWND hdlg, UINT wm, WPARAM wParam, LPARAM lParam)
{
  switch (wm) {
  ...
  case MDM_SETITEMCOUNT:
    SetDlgItemInt(hwnd, IDC_ITEMCOUNT, (UINT)wParam, FALSE);
    return TRUE;
  ...
  }
  return FALSE;
}

"I send the MDM_SET­ITEM­COUNT message to my dialog, but the value doesn't stick. At random times, the value resets back to zero."

As we saw some time ago, window messages in the WM_USER range belong to the window class. In the case of a dialog box, the window class is the dialog class, and the owner of the class is the window manager itself. An application which tries to use the WM_USER message is using window messages it does not own.

It so happens that the dialog manager already defined the WM_USER message:

#define DM_GETDEFID         (WM_USER+0)

We saw this problem some time ago when we tried to find a message we could use for custom use in a dialog box.

What the customer is seeing is that whenever the dialog manager sends a DM_GET­DEF­ID message to the dialog box to get the default control ID, the MyDlgProc function mistakenly thinks that it's a MDM_SET­ITEM­COUNT message and sets the item count to whatever happens to be in the wParam (which happens to be zero). On top of that, it claims to have handled the message, which means that the current value of DWL_MSG­RESULT is returned to the sender (probably zero), so the dialog manager thinks that there is no default ID on the dialog.

The solution, as noted in that same article, is to use WM_APP instead of WM_USER. Because you don't have permission to define messages in the WM_USER range if you aren't the owner of the window class.

Comments (18)
  1. Or just use RegisterWindowMessage and never worry about a conflict…

    [That's using a global solution to a local problem. There is a limited number of registered messages (and they can never be freed once registered), so if everybody used registered messages any time they wanted a message, the system would soon run out. -Raymond]
  2. Anonymous says:

    Wow, dialog boxes really shouldn't hog the whole WM_USER range. There should be some constant (say around 0x4000) where the rest of the WM_USER range can be used by the specific dialog box.

  3. Anonymous says:

    @Joshua:  The range you seek does exist!  It's just at 0x8000 instead of 0x4000.  As Raymond would say, I can't believe I had to write that.

  4. @Raymond:  RegisterWindowMessage "return value is a message identifier in the range 0xC000 through 0xFFFF".  That's 16384 possibilities.  The applications I've done this with register a maximum of maybe 5 messages, at most.  The names are a hard-coded string with a GUID, so they'll never conflict with anyone else.  And they stay the same on every execution of the program (because I did read MSDN and learn of the low limit).  In exchange I get a guarantee that nothing else uses the message – no 3rd-party library loaded in my program, no external app doing shenanigans hooking my app and using the same ID, etc.  Let's say everyone does this – then I have to run at least 3277 unique programs before before exhausting the supply of message IDs.  I'd say in reality, if someone runs out of message identifiers, I'd look for other offenders registering dozens to hundreds of IDs before looking at my app.

    In theory you're right, but in practice I don't see the problem if you restrict yourself to only a very small handful of string IDs and always use the same IDs on every execution of your program.  There are plenty of other low global user/GDI limits that could be exhausted first, dating back to Win16 in some cases.

    In reality, the actual limit I hit first is the low size of the desktop heap.  Given my heavy usage patterns, I have to increase the size of the desktop heap on every Windows system I use for development.  Thankfully I learned about this issue on Mark Russinovich's blog, or I would still be puzzling over why programs session-wide seemed to be having trouble allocating user/GDI objects…

    [Right, I was referring more to people who use a registered window message for everything. If every app registered 500 messages, you're going to run out of space. Also, registered window messages have to share space with registered window classes and registered clipboard formats. It's not like you have 16384 values exclusively for registered messages. Maybe I'm just paranoid because I had to debug systems that had run out of registered window messages. -Raymond]
  5. Oh – it seems the developers of .NET Framework use a similar technique for marshaling from a background thread to the user interface thread (which is one of the things I was needing a special message for).  From private Control.MarshaledInvoke, which is called by Control.BeginInvoke in System.Windows.Forms (found using Reflector):

           if (threadCallbackMessage == 0)

           {

               threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");

           }

    where Application.WindowMessagesVersion is "WindowsForms12".

    I know this isn't a .NET blog, but I tend to look at their implementations because more often than not they are following "best practices."  Or at the very least, "everyone else is doing it that way" because the framework is so widely used.

  6. Anonymous says:

    Interesting comment, James.  I suspect that Winforms is using the RegisterWindowMessage technique because it's a library.  It can't possibly know what hard-coded window message numbers the host app might be using.

  7. Anonymous says:

    @ChrisR: See Dialog Box in DLL.

    Forum keeps losing my post.

    [It's not losing the post. It's blocking it because it looks like spam (too short). -Raymond]
  8. Anonymous says:

    @Joshua: I'm not sure I see the problem.  Your dialog box living in a DLL does not change the fact that you own it and hence you define the semantics of all WM_APP range messages that are sent to it.

    If you need an interface for sending messages to your dialog box from external code then it is up to you to define that interface, whether it is a chunk of WM_APP carved out or a specific WM_APP+X with WPARAM/LPARAM differentiation.

  9. Anonymous says:

    I'm pretty sure that WM_APP was intended for application-wide broadcast and for subclassing DLL-based windows.

    [WM_APP is for the code that called Create­Window. Of course, that code can in turn grant access to those messages to other components if it so wishes. -Raymond]
  10. Medinoc says:

    I've also seen WM_USER+100 used as a base in various places (and I plead guilty to using it a few times of my own).

    Also, about the .Net Framework part: That one registered message will be shared by all programs that use the same version of the Framework, so that sounds reasonable.

  11. Anonymous says:

    @Raymond:

    >>> "It's not losing the post. It's blocking it because it looks like spam (too short). -Raymond"

    That's not my experience. There's several times when I've submitted a post only for it to be silently dropped. I then paste (character-for-character) the exact same comment and it'll then be accepted (this process sometimes happens two or three times before the comment "sticks").

    I've come to accept as a fact of life that I must copy any post I make to your blog to my clipboard before clicking "Post" for fear that the comment gets dropped at random.

    [I have the two "spam blocked" messages in my inbox if you want to see them. (I'm just saying that in this case, they were blocked for spam. Maybe there were other issues at other times.) The site also filters dups (I get email for those too), so it's possible that your first message was simply in holding, and then you submitted it a second time, and the first copy cleared the holding period, and the second copy was blocked for being a dup. It looks like the first copy was blocked and the second was posted, but it was actually the other way around. Again, not saying that's what happened to you, just saying that it happens. -Raymond]
  12. Anonymous says:

    Sadly, I've programmed for Windows since 3.0.  And it was only last year that I realized that WM_USER wasn't for "the code that called createwindow".

    Hindsight 20/20 – perhaps WM_USER should have been WM_CLASS, and WM_APP -> WM_USER (I think of myself as the programmer of a dialog box as the user.  Wrong I may be, but I also intuited that WM_APP would be application-domain and WM_USER would be local-window-domain.

    I'm not sure why it took so many years to understand this distinction.  I've read piles of books and read this blog for quite some time, your book, but for whatever reason, this piece of info never got past my "intuition filter."

  13. Anonymous says:

    @MattT:  I, too, have taken up that behavior of copying my comments before posting.  The blog software will silently drop your comment if you don't post it within a certain amount of time.

  14. Anonymous says:

    In terms of this blog site working: works for me.  Just requires patience to wait for Raymond to vet each post. :)

  15. Anonymous says:

    @MattT: From my experience, comments only "stick" if you post them quickly after loading the post. So you have to write the comment, copy it to the clipboard, reload the post, and then paste and submit your comment as soon as the page loads completely. If the resulting page after the submit does not have the green confirmation bar (or red, or other colors; green is for when Raymond does not have to approve it), it did not "stick" and you will have to try again. If you got the green (or red, or other color) confirmation bar, it worked; if it was not green, just wait for Raymond to approve the comment.

    As people would say on that other site, the real WTF is this forum software…

  16. Anonymous says:

    @MattT, @John: Before I ever started reading Old New Thing, I had come to accept as a fact of the WWW that I had to copy to the clipboard the contents of any text box that I spent more than a minute composing, for fear that something would go wrong with the form submission. I think it was the trauma of spending an hour composing my performance review in some HR web app that lost it all.

  17. Anonymous says:

    @ChrisR: For dialog box in a DLL, one approach that we've used is to have the caller supply a window message (or a range of window messages) in the WM_APP space for the dialog box to use.

  18. Anonymous says:

    At times while working off-site I've noticed an inverse correlation between the length of time spent at dinner at the pub and whether one of my comments appears on this site.

Comments are closed.