2

I am having trouble correctly running a message loop from within a message handler. In effect replicating how DialogBox() processes messages, minus all of the windowing.

Simply invoking GetMessage() from within a message handler nearly works except when the WM_SYSKEYDOWN event opening the system menu also triggers the entry into a sub-loop. After this weird things happen, with keys presses being swallowed and WM_MOUSEMOVE messages relative to the system menu getting sent to the main window.

For the record this happens both in Windows 8 and XP.

To give some context I am attempting a threading model where a (windowless) worker thread communicates through blocking SendMessage calls back to the main window acting as a server. These actions may require further input or depend on other I/O and thus regular messages need to be processed until the reply is ready.

I'm fairly certain that this is a basic mistake or misunderstanding on my part, just like last time I posted here, but I can't quite seem to work out what I'm doing wrong on my own.

Here is my repro case. Try navigating after pressing ALT+SPACE to open the system menu,

#include <windows.h>

BOOL update;

LRESULT WINAPI WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    MSG msg;
    char text[256];
    switch(uMsg) {
    case WM_DESTROY:
        ExitProcess(0);
    // Trigger an update on input
    case WM_SYSKEYDOWN:
        update = TRUE;
        break;
    // Display the update from the worker thread, returning once it is time to
    // ask for the next one
    case WM_USER:
        wsprintf(text, TEXT("%u"), (unsigned int) lParam);
        SetWindowText(hwnd, text);
        while(!update && GetMessage(&msg, NULL, 0, 0) > 0) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        update = FALSE;
        return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

DWORD WINAPI ThreadProc(void *hwnd) {
    // Submit updates as quickly as possible
    LONG sequence = 1;
    for(;;)
        SendMessage(hwnd, WM_USER, 0, sequence++);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCommandLine, int nCmdShow) {
    HWND hwnd;
    MSG msg;

    // Create our window
    WNDCLASS windowClass = { 0 };
    windowClass.lpfnWndProc = WindowProc;
    windowClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    windowClass.hCursor = NULL;
    windowClass.lpszClassName = TEXT("Repro");
    RegisterClass(&windowClass);
    hwnd = CreateWindow(TEXT("Repro"), TEXT("Repro"),
        WS_VISIBLE | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
        hInstance, 0);
    // Launch the worker thread
    CreateThread(NULL, 0, ThreadProc, hwnd, 0, NULL);
    // And run the primary message loop
    while(GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}
doynax
  • 4,285
  • 3
  • 23
  • 19
  • 1
    don't do that kind of things, you will only face problems one after the other. If you want thread cooperation, you could use PostMessage/PostThreadMessage (with or without an object Window in the worker thread) – manuell Oct 12 '13 at 16:53
  • Thank you, I'll try that workaround. It still rather worries me that I don't understand message queue operations after all these years though. Can you give me any hints as to why this is failing or what part of the contract I am violating? – doynax Oct 12 '13 at 17:05
  • 1
    I don't know, probably related to running a new message loop while a previous DispatchMessage() is still running. Personally, I would use a waitable event via `CreateEvent()` instead. Look at related functions like `SetEvent()`, `ResetEvent()`, `PulseEvent()`, and `WaitForSingleObject()`. – Remy Lebeau Oct 12 '13 at 17:40
  • Remy Lebeau: That's about what I figured. Still, MessageBox and other modal dialogs seem to manage the trick without apparent trouble. And, yes, events do seem more reliable than using PostThreadMessage and hoping the worker thread never displays any UI, or synchronizing the creation/teardown of a dummy window and not stalling broadcasts. – doynax Oct 12 '13 at 17:50
  • I find asynchronous programming using PostMessage much simpler. After hours of debugging weird synchronisation bugs and locks with "event", "set", and "wait", you will find PostMessage very refreshing. – manuell Oct 12 '13 at 19:50
  • Manuell: I generally agree that the message queue abstraction is preferable. Unfortunately as has been pointed out you really need two mutual windows for reliable operation, and I've found coordinating the shut-down sequence is finicky. – doynax Oct 12 '13 at 20:09
  • @doynax I don't see how it's harder to coordinate graceful shutdown of multi-thread apps if you have windowed thread. Quite the contrary, imho. – manuell Oct 13 '13 at 08:53
  • @manuell: Oh, it was probably just me managing to confuse myself last time I tried it. I kept getting into odd corner cases with messages sent to already destroyed windows and communication attempts after WM_QUIT/thread exists. Eventually I sorted it out through mutual MsgWaitForMultipleObjects on the threads, except it still caused a BSOD once in a blue moon on Win98 (don't ask..) – doynax Oct 13 '13 at 09:09

1 Answers1

1

Modal message loops are perfectly fine. Raymond Chen has a series of articles on writing modal message loops properly.

One thing I notice: Your thread should post the message, not send it; SendMessage calls directly into the window proc. Don't use PostThreadMessage, either; that's designed for threads without visible UI (and the nested DispatchMessage won't know how to dispatch the thread message, resulting in dropped messages).

Eric Brown
  • 13,774
  • 7
  • 30
  • 71
  • Thanks, I'll take a closer look at that article. I intentionally use SendMessage in this case since I want the call to block until the window has finished processing the message, something which should be fine across threads if I've understood things correctly. I do use PostMessage instead when I don't need to wait (actually SendNotifyMessage since those are delivered in order.) – doynax Oct 12 '13 at 19:29
  • @doynax - if you need to block, use an event. [Cross-thread SendMessage often leads to deadlock](http://www.developerfusion.com/article/1713/message-management/7/) and [other odd failures](http://blogs.msdn.com/b/oldnewthing/archive/2004/11/19/266664.aspx). – Eric Brown Oct 13 '13 at 04:11
  • I was hoping I'd get away with it on a windowless thread, but perhaps there are still edge cases I haven't considered. SendMessage is rather convenient as a blocking mechanism, for receiving results and being able to pass local parameter block structures. – doynax Oct 13 '13 at 06:34
  • The articles you linked to reveal that I must re-post WM_QUIT in the nested loop. Unfortunately I still haven't diagnosed the underlying problem, and have instead given up and worked around it with events as suggested (note to self: closing an event object does not cause WAIT_ABANDONED to be returned.) – doynax Oct 14 '13 at 11:24