Это прямое продолжение первой статьи. (И вторая часть в серии статьей по Direct2D)

Базовые действия

Сообщения окна - это информация о любых действиях в окне и вне его.

Обрабатывать сообщения мы уже научились, как минимум на базовом уровне. Остаётся понять, что из себя представляет набор сообщений, где он хранится и как добавлять в него информацию или убирать. Чтобы понять, с чего начать, нужно осознать: для любого действия с сообщением, его нужно получить или получить к нему доступ. Возникает вопрос: откуда или к чему? Ответ относительно прост: сообщения хранятся в ядре Windows и тесно связаны с win32k.sys. То есть просто так получить к ним доступ проблематично, поскольку изначально неизвестно, какой именно адрес использовать. Для таких задач требуется реверс-инжиниринг функции GetMessage, а это усложняет статью, рассчитанную всё-таки на новичков.

Итак, с тем, откуда мы получаем сообщения, немного ясно. Теперь, задавая вопрос как оттуда что-то получить, вспоминаем функцию GetMessage. Может возникнуть вопрос, как она работает. Стоит упомянуть, что всё работает благодаря win32k.sys и функциям ядра: при вызове GetMessage вызывается функция NtUserGetMessage (ссылка ведёт на ReactOS), куда передаются все те же аргументы, что и в GetMessage. На самом деле на подобном функциях, построено всё ниже упомянуто.

Как получаются данные, примерно ясно. Да, вопросов остаётся много, но без глубокого изучения с реверс-инжинирингом вряд ли можно дать полный ответ. Теперь остаётся понять, как сообщения добавляются. Базово используются функции PostMessage и SendMessage. Из названия понятно, что они добавляют сообщения в очередь, с разницей в том, что Post просто добавляет сообщение в очередь, а Send вызывает оконную процедуру окна, ожидая, пока сообщение не выполнится и не вернёт результат. Их синтаксис одинаков:

Аргумент 1: тип HWND, название hWnd.
Аргумент 2: тип UINT, название Msg.
Аргумент 3: тип WPARAM, название wParam.
Аргумент 4: тип LPARAM, название lParam.

Про каждый аргумент рассказывалось в первой статье.

Ещё действия

Можно подумать, что добавить и забрать - это всё, что можно сделать с очередью. И в целом верно, но - есть различные варианты

1.SendMessageTimeout
2.SendMessageCallback
3.SendNotifyMessage
4.PostThreadMessage
5.PeekMessage
6.WaitMessage
7.InSendMessage
8.ReplyMessage

Довольно солидный список, но не сложный:

1.SendMessageTimeout: Первые 4 аргумента - классические для SendMessage. Пятый аргумент (тип UINT, название fuFlags) - флаг поведения: SMTO_NORMAL - обычное поведение, SMTO_ABORTIFHUNG - вернуться, если поток не отвечает, SMTO_BLOCK - блокировать вызывающий поток, SMTO_ERRORONEXIT - вернуть ошибку при завершении потока, а также их комбинации. Шестой аргумент (тип UINT, название uTimeout) - время в миллисекундах, по истечении которого (если указан флаг SMTO_ABORTIFHUNG) вернётся управление. Это может быть полезно, если неизвестно, зависло окно или нет (если оно зависло и вы использовали обычный SendMessage, ваша программа тоже зависнет). Есть и седьмой аргумент - указатель на переменную для результата (тип LRESULT).

2.SendMessageCallback: Первые 4 аргумента - классические для SendMessage. Пятый аргумент - это функция, которая вызовется после обработки сообщения. Её синтаксис:

VOID CALLBACK MyMessageCallback(
    HWND hWnd,      // Окно, которое обработало сообщение
    UINT uMsg,      // Какое сообщение было отправлено
    ULONG_PTR dwData, // Наши пользовательские данные
    LRESULT lResult  // Результат обработки сообщения
) {

}

В ней можно рассмотреть dwData - это указатель на данные, который достаточно привести к любой структуре для их получения. Всё остальное понятно из названия. И последний, шестой аргумент - это результат обработки сообщения.

3.SendNotifyMessage: Аргументы такие же, как у SendMessage. Разница в поведении: для окон в том же потоке работает как SendMessage, для окон в других потоках - как PostMessage.

4.PostThreadMessage: Разница лишь в том, что первый аргумент - не ID окна (HWND), а ID потока (DWORD). То есть сообщение отправляется в определённый поток приложения, в котором вызывается функция.

5.PeekMessage: Работает как GetMessage, но не блокирует поток ожиданием. Если есть сообщение - выдаёт его, если нет - продолжает работу без ожидания.

6.WaitMessage: Наоборот, блокирует поток до появления любого сообщения в очереди, но ничего с ним не делает.

7.InSendMessage: Функция, которая вернёт true, если сообщение было отправлено через механизм SendMessage, и false для PostMessage.

8.ReplyMessage: Если сообщение было вызвано при помощи SendMessage, то можно вызвать данную функцию, указав в аргументе true или false, и это значение немедленно вернётся отправителю. Это своего рода return.

Создание своих сообщений

Стоит сказать, что в Windows есть зарезервированные диапазоны для сообщений:

1.0x0000 - 0x03FF: системные сообщения (например, WM_NULL, WM_CREATE, WM_DESTROY и т.д.)
2.0x0400 - 0x7FFF: зарезервировано для будущих системных сообщений.
3.0x8000 - 0xBFFF: используется для приложений (WM_APP).
4.0xC000 - 0xFFFF: зарезервировано для приложений (RegisterWindowMessage).
5.0x10000 и выше: зарезервировано для будущего использования.

Первое, что бросается в глаза, - это RegisterWindowMessage. Этой функции передаётся указатель на строку в кодировке Unicode, после чего она проверяет, есть ли такое сообщение в системе. Если нет - возвращает его ID в диапазоне от 0xC000 до 0xFFFF; если уже есть - просто возвращает существующий ID. Самое важное: это значение сохраняется до перезагрузки системы. По сути, достаточно где угодно вызвать эту функцию, передать уникальную строку, получить код сообщения и отправлять его окнам, заранее добавив в них обработку. Плюс такого подхода в том, что он позволяет избежать любых возможных конфликтов ID.

Конечно, с помощью SendMessage или PostMessage (и их вариантов) можно в качестве идентификатора сообщения и дополнительных данных (wParam, lParam) указать что угодно. Главное - не забыть добавить обработку этого сообщения в оконную процедуру, иначе толку от этого будет мало.

Как видите, ничего сложного.

Комментарии (2)


  1. Forgottn
    05.11.2025 19:29

    — Рыба живет в воде, шерсти у нее конечно нет, но вот если бы она у нее была, то в ней обязательно водились бы блохи…


  1. TimID
    05.11.2025 19:29

    Сообщество по прежнему остается в недоумении, какое отношение написанное в статье имеет к DirectX.
    Окна Windows, обработка сообщений...
    Как-то сильно издалека начали. Боюсь, что выйдет что-то очень напоминающее "Как выучить и понять C++ за 21 день"...