textpub.neocities.org . [ список статей ]

Мой перевод англоязычной статьи «Window Messages» с сайта компании Microsoft (ссылка на оригинал: новая, старая), автор статьи там не указан, дата публикации оригинала: 05.10.2010 г.

Сообщения окну от операционной системы

Приложение с GUI (графическим интерфейсом пользователя) должно реагировать на события, порождаемые действиями пользователя или операционной системы:

Эти события могут возникнуть в любой момент времени, пока программа работает, и почти в любом порядке. Как выстроить программу, схему исполнения которой невозможно предсказать заранее?

Для решения этой проблемы Windows использует модель обмена сообщениями. Операционная система передает информацию окну нашего приложения, посылая ему сообщения. Сообщение представляет собой просто числовой код, который описывает конкретное событие. Например, когда пользователь нажимает левую кнопку мыши, окно получает от операционной системы сообщение со следующим кодом:

#define WM_LBUTTONDOWN    0x0201

Для некоторых сообщений имеются данные, связанные с ними. Например, сообщение WM_LBUTTONDOWN включает координату по горизонтали и координату по вертикали курсора мыши на клиентской области окна приложения.

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

Цикл обмена сообщениями

Приложение, пока оно запущено, будет получать тысячи сообщений. (Учитывая то, что каждое нажатие на клавиатуре и нажатие кнопки мыши порождает новое сообщение.) Кроме того, приложение может иметь несколько окон, у каждого из которых есть своя собственная процедура окна. Каким образом программа справляется с получением всех этих сообщений и выбором правильного получателя этих сообщений среди процедур окна, если окон несколько? В приложении должен быть цикл для получения и распределения сообщений правильным получателям-процедурам окон.

Для каждого потока, создающего окно, операционная система создает очередь сообщений окну. Эта очередь содержит сообщения для всех окон, созданных в этом потоке. Сама очередь скрыта от нашей программы. Мы не можем манипулировать очередью прямо, но мы можем получить сообщение из очереди с помощью вызова функции GetMessage.

MSG msg;
GetMessage(&msg, NULL, 0, 0);

Эта функция получает первое сообщение из начала очереди. Если очередь пуста, функция перестает работать до того момента, пока очередное сообщение не поступит в очередь. Тот факт, что функция GetMessage перестанет работать, не заставит нашу программу зависнуть. Ведь, пока нет сообщений, программе нечего исполнять. Если потребуется выполнить какую-либо задачу в фоновом режиме, можно создать дополнительные потоки, которые продолжат работу, пока функция GetMessage ожидает очередное сообщение. (Обратите внимание на раздел «Как избежать узких мест в процедуре окна» в статье «Написание процедуры окна».)

Первый параметр функции GetMessage является адресом структуры типа MSG. Если функция отработала без ошибок, она заполнит структуру типа MSG информацией о сообщении, включая информацию об окне-получателе и код сообщения. Остальные три параметра дают нам возможность фильтровать сообщения, получаемые из очереди. Почти во всех случаях мы будем устанавливать эти параметры в ноль.

Хотя структура типа MSG содержит информацию о сообщении, мы почти никогда не будем работать с этой структурой напрямую. Вместо этого мы будем передавать ее двум следующим функциям:

TranslateMessage(&msg); 
DispatchMessage(&msg);

Функция TranslateMessage занимается вводом с клавиатуры; она переводит фрагменты нажатия на кнопку (движение кнопки вниз, движение кнопки вверх) в символы. В контексте этой статьи нам не нужно подробно изучать то, как эта функция работает; нужно лишь запомнить, что ее необходимо вызывать перед функцией DispatchMessage. Если эта функция вам всё же интересна, поднимайте документацию MSDN, в которой дается больше информации по этому вопросу.

Функция DispatchMessage дает команду операционной системе вызвать процедуру окна, относящуюся к тому окну, которое является получателем сообщения. Другими словами, операционная система ищет дескриптор окна в списке окон, находит указатель на функцию, связанный с найденным окном, и вызывает эту функцию.

Например, допустим, что пользователь нажал на левую кнопку мыши. Это запустит следующую цепь событий:

  1. Операционная система поместит сообщение WM_LBUTTONDOWN в очередь сообщений;
  2. Наша программа вызовет функцию GetMessage;
  3. Функция GetMessage получит сообщение WM_LBUTTONDOWN из начала очереди и заполнит структуру типа MSG;
  4. Наша программа вызовет функции TranslateMessage и DispatchMessage;
  5. Функция DispatchMessage обратится к операционной системе, которая вызовет нашу процедуру окна;
  6. Наша процедура окна может либо как-то среагировать на полученное сообщение, либо проигнорировать его.

После окончания работы процедуры окна программа возвратится к функции DispatchMessage, после завершения которой программа вернется в цикл обмена сообщениями за следующим сообщением из очереди. Пока наша программа запущена, сообщения будут продолжать прибывать через очередь. Именно поэтому нам нужен цикл, который постоянно вынимает сообщения из очереди и отправляет их окнам-получателям. Можно мысленно представлять себе этот цикл следующим образом:

// ВНИМАНИЕ: Не пользуйтесь этим кодом в программах,
// он нужен лишь для иллюстрации

while (1)
{
    GetMessage(&msg, NULL, 0, 0);
    TranslateMessage(&msg); 
    DispatchMessage(&msg);
}

В таком виде, естественно, этот цикл никогда не закончится. И здесь в дело вступает значение, возвращаемое функцией GetMessage. Пока программа работает, функция GetMessage возвращает ненулевое значение. Когда нужно будет выйти из приложения и прервать цикл обмена сообщениями, просто вызовем функцию PostQuitMessage.

    PostQuitMessage(0);

Функция PostQuitMessage поставит сообщение WM_QUIT в очередь сообщений. WM_QUIT — это особое сообщение: оно служит причиной того, что функция GetMessage возвращает нулевое значение, сигнализируя о том, что цикл обмена сообщениями должен закончиться. Вот как примерно должен выглядеть цикл обмена сообщениями:

// Правильная версия

MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

Пока функция GetMessage возвращает ненулевое значение, выражение, результат вычисления которого является условием продолжения цикла, разрешается в значение «Истина». После того, как будет вызвана функция PostQuitMessage, выражение-условие продолжения цикла разрешится в значение «Ложь» и программа выйдет из этого цикла. (Интересным последствием этого является то, что наша процедура окна никогда не получит сообщение WM_QUIT, а поэтому нет нужды в этой процедуре описывать порядок действий при получении этого сообщения.)

Следующим очевидным вопросом является: «Когда мы вызовем функцию PostQuitMessage?» Мы вернемся к этому вопросу в статье «Закрытие окна», но перед этим нам нужно написать нашу процедуру окна.

Размещение сообщений и прямая передача сообщений

В предыдущем разделе статьи речь шла о том, что сообщения проходят через очередь. В некоторых ситуациях операционная система вызывает процедуру окна прямо, минуя очередь.

Терминология при обсуждении этих двух разных случаев может запутать:

В контексте нашей простой программы это различие не является слишком важным. Процедура окна обрабатывает все сообщения, но некоторые сообщения пропускают очередь и попадают напрямую в нашу процедуру окна. Однако, это различие может стать важным, если наше приложение начнет передавать информацию от одного окна другому. Подробный разбор этого вопроса можно найти в цикле статей «О сообщениях и очередях сообщений».

textpub.neocities.org . [ список статей ]