Psychic debugging: Why messages aren’t getting processed by your message pump

The second parameter to the Get­Message is an optional window handle that is used to tell the Get­Message function to retrieve only messages that belong to the specified window.

A filtered Get­Message is nearly always a bad idea, because your program will not respond to messages that don't meet the filter. Unlike a filtered Peek­Message (which simply returns "no messages satisfy the filter"), Get­Message blocks your thread and does not return until a satisfactory message arrives. Instead, they just pile up like newspapers on your doorstep.

A common mistake I encounter is using a filtered Get­Message as the main message pump:

hwnd = CreateWindow(...);
if (hwnd == NULL) { return error }
while (GetMessage(&msg, hwnd, 0, 0)) {

I don't know for sure, but I'm guessing that the author said, "Well, I created only one window, so clearly that is the only window that can receive messages, and therefore that is the only window I care about."

That may be the only window you explicitly created in that function, but there are still plenty of opportunities for other windows to get created. For example, there may be child windows of your main window. Or there may be hidden windows created by other components such as OLE which are used for cross-thread communication. Filtering your message pump's Get­Message prevents those other windows from receiving queued messages, and consequently prevents those windows from getting done whatever it was you asked them to do.

When a support request comes in for a program that hangs or acts erratically, you don't think to look at the message pump, because that is nearly always just boilerplate code. Only when you glance at it and notice that the boilerplate code has been tweaked do you realize that the tweaking is the source of the problem. (And when I point out the mistake, I may get a "Thank you" and possibly even a "I didn't realize that", but never a "This is what I was thinking when I wrote that in the first place," so I never figure out why they went to the extra effort of adding a Get­Message filter.)

Armed with this new psychic power, you can help this customer out:

I can't get combo boxes to work outside of a dialog box. When used as a standalone window, the combo box doesn't work correctly. It doesn't respond to mouse hover, sometimes it ignores clicks, sometimes it makes my app hang when I select an item with the mouse. But if I put the combo box inside a dialog, then it works perfectly. As you can see in the attached project, the exact same function (Create­Combo) works if called from a dialog box, but not from a regular window. Is there something special about combo boxes that prevent them from being used outside of a dialog box?

void CreateCombo(HWND hwndParent)
 HWND hwndCombo = CreateWindow(TEXT("combobox"), 0,
  10, 10, 200, 200, hwndParent, NULL, g_hinst);

 ComboBox_AddString(hwndCombo, TEXT("Item 0"));
 ComboBox_AddString(hwndCombo, TEXT("Item 1"));
 ComboBox_AddString(hwndCombo, TEXT("Item 2"));
 ComboBox_AddString(hwndCombo, TEXT("Item 3"));
 ComboBox_AddString(hwndCombo, TEXT("Item 4"));
 ComboBox_AddString(hwndCombo, TEXT("Item 5"));
 ComboBox_AddString(hwndCombo, TEXT("Item 6"));
 ComboBox_AddString(hwndCombo, TEXT("Item 7"));
 ComboBox_AddString(hwndCombo, TEXT("Item 8"));
 ComboBox_AddString(hwndCombo, TEXT("Item 9"));

// Dialog box version
    HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
 switch (uMsg) {
  return TRUE;
 case WM_CLOSE:
  EndDialog(hdlg, 0);
  return TRUE;
 return FALSE;

void TestDialog()
           NULL, DialogProc);

// Plain window version
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 switch (uMsg) {
 case WM_CREATE:
  return 0;
  return 0;
 return DefWindowProc(hwnd, uMsg, wParam, lParam);

void TestWindow()
 WNDCLASS wc = { 0, WndProc, 0, 0, g_hinst, NULL, NULL,
                 (HBRUSH)(COLOR_WINDOW+1), NULL, TEXT("Test"));
 RegisterClassEx(&wc); // succeeds
 HWND hwnd = CreateWindow(TEXT("Test"), TEXT("Test"),
                  NULL, NULL, g_hinst, NULL);
 MSG msg;
 while (GetMessage(&msg, hwnd, 0, 0)) {
 UnregisterClass(TEXT("Test"), g_hinst);
Comments (21)
  1. Henke37 says:

    The fun part isn't pointing out the error. It is understanding why it worked despite the error.

    The answer is that the DialogBox function runs its own message pump that doesn't do the incorrect filtering.

  2. Nicholas says:

    OK, I seriously missing something here in this article.  I get that applying a filter is dangerous.  What I don't get is where it is happening?  I see this everywhere:

    GetMessage(&msg, hwnd, 0, 0)

    And according to MSDN the zeros mean that there is no filtering and all messages will be retrieved.

  3. laonianren says:

    @Nicholas: The hwnd parameter is a filter.

  4. Anon says:

    This is why you never provide a solution until someone explains how and why they created the problem!

  5. Joshua says:

    Is there a good reason to use the hwnd argument anymore or is it a holdover from Win16?

    [I can't think of a good reason even in Win16. It probably exists merely to align with with PeekMessage. -Raymond]
  6. Jason says:

    @Nicholas: I think those are different types of filtering.  hWnd filters on the window handle, wMsgFilterMin/Max filter on the message value.

  7. pmbAustin says:

    It seems to me this would be a good target for code analysis… flagging that as a warning if a parameter is ever specified there.  If it's a constant source of misunderstanding, and thus bugs, and there's no obvious "good" reason to ever supply that parameter, then it would seem flagging it would be a Very Good Thing to do.

  8. smf says:

    You'll get this problem all the time 99% of software developers belong to a cargo cult. It's only possible because software development has been made "easier", if it was harder they wouldn't have found their way out of writing hello world.

  9. Anonymous Coward says:

    1) I assume the filter parameters for GetMessage have to remain in the binary interface for compatibility reasons, but there's no reason why the header files couldn't include a macro that disables their use.

    2) I've seen countless message loops, and all the ones that weren't flawed were essentially the same. Why then, isn't this standard message loop part of the Win32 API? I realise that most GUI toolkits provide it, but more people than you'd think use raw C++ and screw it up.

  10. Joshua says:

    If I recall correctly (it's been a looong time), my documentation stated GetMessage was available on Windows 2.x but PeekMessage required 3.0.

    I'd guess the filter is to remove a message you know you posted there by some action.

  11. Nicholas says:

    Thanks laonianren and Jason.  Stupid of me since the very first line of the article is:

    "The second parameter to the Get­Message…"

    Apparently I had severe memory loss by the time I got to the end of the article.  My bad.

  12. Matt says:

    What happens if you (i.e. someone at Microsoft) edits GetMessage to ignore the filter parameter, and then run all of your app-compat regression tests. That would show you (A) if there are any legitimate uses of this functionality and (B) give you a cheap fix for this if not. You can maintain the binary compatibility by making hwndFilter a reserved (and ignored) parameter.

    [I'm sure there are apps that rely on filtering. That doesn't mean that their use is legitimate. A lot of compatibility involves getting existing apps to work in spite of their bugs. -Raymond]
  13. Antonio 'Grijan' says:

    TRWTF is adding your own message pump to a test window. After reading the series of articles on the dialog manager, I wouldn't dare to have more than one message pump for each thread (apart from the dialog manager itself, that is). If you ever need to make something fancy with some window's messages, you can always call a filter function in the main message loop, just before the call to DispatchMessage().

    A filtered GetMessage() *could* be useful to remove messages left by PeekMessage() when hWnd != NULL and wRemoveMsg == PM_NOREMOVE (if you ever need to do that, which is dubious), but that's a corner case.

  14. The next really common problem is returning TRUE for every handled message in your custom WindowProc without calling DefWindowProc and/or putting return DefWindowProc only in the default clause.

    Te cause for this might be the default Win32 project template. At least in VS 2008 it does exactly that. While it works for that specific sample it is a really bad start if the code gets expanded. Sooner or later a message is handled, not passed on to DefWindowProc and everything breaks apart.

  15. Kyle S. says:

    > [I can't think of a good reason even in Win16. It probably exists merely to align with with PeekMessage. -Raymond]

    On the Mac, NSWindow has -nextEventMatchingMask:, which behaves identically to GetMessage. So apparently at some point in the time of Win16 and NEXTSTEP this was considered a good idea.

  16. Neil says:

    Doesn't the fact that a dialog has its own message loop cause a problem if you have more than one top-level window? Unless you create them in different threads, then as soon as you open a dialog the accelerators in the other window will stop working.

    [You can read the modality series for extended discussion on this topic. -Raymond]
  17. Azarien says:

    @Georg_Rottensteiner: what would break apart? I don't see the point in passing most of handled messages to DefWindowProc.

  18. @Azarien: The sample code from VS does return 0 if the message is handled without calling DefWindowProc. Try that with WM_CREATE for example, or WM_NC_CREATE. Basically every message should be passed on unless the explicitely say so.

  19. Master says:

    Creating hidden windows for cross-thread communication: fail.

  20. alegr1 says:


    Having more than one top-level window opens a big can of worms. Having any top-level window enabled while showing a modal dialog (or running any modal loop without mouse capture bail-out) will give you headaches.

  21. Medinoc says:

    The combo box fails becauses it uses a non-child window, doesn't it? (and IIRC, it's also unowned, and I'm not sure it should be)

Comments are closed.