Почему цикл сообщений win32 перестает работать при потоковой передаче?

#c #windows #multithreading #winapi #message-loop

#c #Windows #многопоточность #winapi #цикл сообщений

Вопрос:

Я пытаюсь создать программу WIN32 (C ), в которой мне придется одновременно обрабатывать сообщения и запускать цикл while. Чтобы сделать это, я хочу использовать потоки.

Когда я переместил цикл сообщений в отдельную процедуру (вызываемую из функции WinMain), все работало нормально. Однако, когда я использовал приведенный ниже код для потоковой передачи этой процедуры, вместо простого вызова ее из основного процесса, окно перестает отвечать.

Вы знаете, почему это происходит?

Внутри WinMain, после создания главного окна, я удалил цикл сообщений и возвращаемое значение, добавив следующий фрагмент кода:

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

return_val это глобальная переменная, которую я буду использовать для получения значения, которое WinMain должен вернуть по завершении цикла сообщений.

Кроме того, функция message_loop выглядит следующим образом:

 void message_loop()
{
    MSG messages;
    while (GetMessage (amp;messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(amp;messages);
        /* Send message to WindowProcedure */
        DispatchMessage(amp;messages);
    }
    return_val = messages.wParam;
}
  

Комментарии:

1. Вообще говоря, разделение кода создания пользовательского интерфейса и кода события пользовательского интерфейса на отдельные потоки не будет работать должным образом. Просто используйте один поток для всего пользовательского интерфейса (создание, модификация и обработка событий). Если вам нужны рабочие потоки для выполнения некоторой обработки, они могут делать это, пока они обрабатываются только (т. Е. Никак не используют пользовательский интерфейс).

2. Посмотрите на docs.microsoft.com/en-us/windows/win32/winmsg/… По сути, Windows создает очереди событий только в потоках, которые создали Windows, и эти очереди обрабатывают только события для Windows, созданные этим потоком. В вашем случае основной поток создает окно, а вы создаете цикл событий в другом потоке, поэтому он не будет видеть события для окон, созданные основным потоком. Вот почему обычно программы Windows создают окна в главном потоке, настраивают цикл событий в главном потоке, а другие потоки напрямую не взаимодействуют с графическим интерфейсом.

3. @Peter: Этого не хватает PostThreadMessage . Не все сообщения связаны с HWND . Поэтому Windows также необходимо создать очередь сообщений потока в потоках без Windows. Актуально, потому что PostThreadMessage это обычная функция для отправки сообщений в такие фоновые потоки.

4. Обратите внимание, что начальный поток в процессе — это базовый поток NT, у которого нет очереди сообщений, поэтому отправка сообщения в него с помощью PostThreadMessageW завершается ошибкой с кодом ошибки ERROR_INVALID_THREAD_ID . Когда процесс загружается user32.dll он подключается к оконной станции и рабочему столу (обычно «WinSta0 Default»), а его структуры процессов ядра и потоков расширяются для взаимодействия со средой рабочего стола, что включает добавление очереди сообщений к каждому потоку в процессе.

5. @MSalters — Мой предыдущий комментарий касался вопроса о том, почему рабочий поток не может обрабатывать события, отправленные в окно, созданное основным потоком. Но, да, существуют другие методы публикации событий в очередь событий потока.

Ответ №1:

Основная причина заключается в том, что Windows имеет понятие очереди сообщений потока. У каждого потока есть своя очередь сообщений. Можно запускать GetMessage в потоке, но при этом будут получены только сообщения, принадлежащие этому потоку, например, для Windows, которые вы создаете в этом потоке. Сообщения, принадлежащие любому другому потоку (независимо от того, как был создан этот другой поток), не будут видны в вашем потоке.

Как вы заявляете, ваш std::thread создается только «после создания главного окна». Это означает, что у вас есть две очереди сообщений потока; одна из основного потока, который создал главное окно, и другая очередь для вашего std::thread . Последняя очередь останется пустой.

Вы видите это во втором аргументе для GetMessage — передача HWND=0 там означает «все сообщения для этого потока».

Теоретически у вас может быть несколько потоков для нескольких окон, но это быстро становится очень сложным. Итак, на практике наиболее распространенным решением является использование main потока, и единственной разумной альтернативой является наличие одного выделенного потока.

Комментарии:

1. » Это нормально для запуска GetMessage на std::thread тогда и только тогда, когда вы также вызывали CreateWindow то же самое std::thread » — неверно. Окно не требуется. GetMessage() также работает с сообщениями потока через PostThreadMessage() . И вызов любой функции USER32 в потоке, включая GetMessage() , автоматически создаст очередь сообщений для этого потока. Реальная проблема заключается в том, что окно имеет привязку к потоку , только поток, который создает окно, может извлекать сообщения для этого окна. Вероятно, это то, что вы пытались описать.

2. @RemyLebeau: Привязка к потоку немного сильнее, чем просто обработка сообщений, ИМХО. Сходство с потоком также означает, что многие функции, работающие с HWND, могут быть вызваны только из потока-создателя. Например, GetDC обычно вызывается в ответ на WM_PAINT , но он также должен быть вызван из того же потока, который получил WM_PAINT . Что касается PostThreadMessage , я даже заметил то же самое в комментарии — обновил эту часть.