1

I am trying to create a WIN32 (C++) program in which I will have to simultaneously process messages and run a while loop. In order to do that, I want to use threads.

When I moved the message loop to a separate procedure (called from the function WinMain), all things were working fine. However, when I used the code below to thread that procedure, instead of simply calling it from the main process, the window becomes unresponsive.

Do you know why that happens?

Inside WinMain, after creating the main window, I removed the message loop and the return value, adding the following piece of code:

std::thread t1(message_loop);
t1.join();
return return_val;

return_val is a global variable which I will use to receive the value WinMain should return when the message loop ends.

Also, the function message_loop is as follows:

void message_loop()
{
    MSG messages;
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }
    return_val = messages.wParam;
}
  • 2
    Generally speaking, separating UI creation code and UI event code into separate threads won't work well. Just use a single thread for all of the UI (creation, modification *and* event handling). If you need worker-threads to do some processing, they can do it as long as they are processing-only (i.e. does not use the UI in any way). – Some programmer dude Aug 27 '20 at 11:09
  • Look at https://docs.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues Essentially, windows only creates event queues in threads that have created windows, and those queues only handle events for windows created by that thread. In your case, the main thread creates a window and you create the event loop in a different thread - so it will not see events for windows created by the main thread. This is why, conventionally, windows programs create windows in the main thread, set up the event loop in the main thread, and other threads don't directly interact with the GUI. – Peter Aug 27 '20 at 11:24
  • @Peter: That misses `PostThreadMessage`. Not all messages are associated with a `HWND`. Therefore Windows also needs to create the thread message queue in threads without Windows. Relevant, because `PostThreadMessage` is a normal function to send messages to such background threads. – MSalters Aug 27 '20 at 12:06
  • Note that the initial thread in a process is a base NT thread that doesn't have a message queue, so posting a message to it with `PostThreadMessageW` fails with the error code `ERROR_INVALID_THREAD_ID`. When a process loads user32.dll, it connects to a window station and desktop (usually "WinSta0\Default"), and its kernel process and thread structures get extended to interact with the desktop environment, which includes adding a message queue to each thread in the process. – Eryk Sun Aug 27 '20 at 12:34
  • @MSalters - My previous comment was addressing the question which is about why a worker thread cannot process events sent to a window created by the main thread. But, yes, there are other methods of posting events to a thread's event queue. – Peter Aug 27 '20 at 12:42

1 Answers1

4

The fundamental reason is that Windows has the notion of a thread message queue. Each thread has its own message queue. It's OK to run GetMessage in a thread, but it will get you only the messages that belong to that thread, such as for windows which you create in that thread. Messages belonging to any other thread (no matter how that other thread was created) will not be visible in your thread.

As you state, your std::thread is created only "after creating the main window". That means you have two thread message queues; one from the main thread which created the main window, and another queue for your std::thread. The latter queue will remain empty.

You see this in the second argument to GetMessage - passing HWND=0 there means "all messages for this thread".

In theory, you can have multiple threads for multiple windows, but that quickly gets very complex. So in practice, the most common solution is to use the main thread, and the only reasonable alternative is to have a single dedicated thread.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
MSalters
  • 173,980
  • 10
  • 155
  • 350
  • "*It's OK to run `GetMessage` on an `std::thread` **if and only if** you also called `CreateWindow` on that same `std::thread`*" - not true. A window is not required. `GetMessage()` also works with *thread messages* via `PostThreadMessage()`. And calling *any* USER32 function in a thread, including `GetMessage()`, will automatically create a message queue for that thread. The *real* issue at hand is that a window has *thread affinity*, only the thread that creates the window can retrieve messages for that window. That is probably what you were trying to describe. – Remy Lebeau Aug 27 '20 at 14:55
  • @RemyLebeau: _Thread affinity_ is a bit stronger than just the message handling, IMHO. Thread affinity also means that many functions working on a HWND can only be called from the creator thread. E.g. `GetDC` is typically called in response to `WM_PAINT`, but it also has to be called from the same thread that received `WM_PAINT`. As for `PostThreadMessage`, I even noticed the same in a comment - updated that part. – MSalters Aug 27 '20 at 15:13