Посылка сообщений
Мы это сделаем на примере нажатия на клавишу. Когда вы нажимаете на клавишу, генерируется аппаратное прерывание. Клавиатурный драйвер Windows обрабатывает это прерывание и помещает соответствующее сообщение в общую очередь сообщений Windows.
При этом указывается, какое окно должно получить это сообщение. Затем Windows извлекает из своей очереди это сообщение и помещает его в очередь сообщений приложения, содержащего окно–адресат. Вслед за этим уже само приложение выбирает из очереди поступившее сообщение и передает его соответствующей оконной функции.
Этот процесс называется посылкой (post) сообщений, так как посылка сообщения напоминает посылку письма: посылающий сообщение указывает адресата, отправляет сообщение и больше о нем не беспокоится. Отправитель не знает, когда точно его сообщение получит адресат. Процесс посылки может показаться излишне сложным, однако для этого существует несколько причин:
Во–первых, аппаратные прерывания надо обрабатывать со всей возможной скоростью. Поэтому при приеме аппаратного прерывания драйвер не тратит время на передачу сообщения в очередь приложения, а ставит его в очередь сообщений Windows. Аппаратные прерывания являются асинхронными по отношению к выполняющемуся приложению, а обработка сообщений обязательно должна быть синхронной. Поэтому механизм посылки сообщений нельзя смешивать с аппаратными прерываниями.
А во–вторых, накопление событий в очереди приложения помогает уменьшить количество переключений между приложениями, так как Windows обычно дает приложению полностью обработать события из его очереди и лишь после этого переключается на другие приложения. Кроме того, некоторые события могут группироваться в одно во время нахождения в очереди.
Рисунок 1 Маршрутизация сообщений в Windows 3.x
Извлечение сообщений из очереди приложения и направление их соответствующим окнам осуществляет функция WinMain. Этот процесс выполняется в несколько приемов:
сообщение выбирается из очереди с помощью функции GetMessage или PeekMessage
затем сообщение транслируется с помощью функции TranslateMessage[0] (одно сообщение может порождать последовательность других или заменяться, как, например, происходит для сообщений клавиатуры WM_KEYDOWN). Часто трансляция состоит из вызова более чем одной функции, сюда могут добавляться специальные средства трансляции акселераторов и немодальных диалогов (об этом позже).
И только после этого оно направляется окну с помощью функции DispatchMessage (это называется диспетчеризацией)
Для выполнения этих операций существуют специальные функции. Эти функции образуют цикл обработки сообщений, так как после завершения обработки одного сообщения приложение должно приготовиться к обработке следующего. Цикл заканчивается только при завершении работы приложения.
MSG msg;
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);}
Это самый простой вид цикла обработки сообщений. В реальных приложениях он более сложный. Все три функции, вызываемые здесь, принадлежат Windows. Назначение их должно быть понятно. Требуется добавить несколько замечаний о функции GetMessage. Эта функция имеет следующие аргументы:
BOOL GetMessage(lpMsg, hWnd, uMsgFilterMin, uMsgFilterMax);
lpMsg указывает на структуру MSG, в которую будет записано полученное сообщение. Если очередь сообщений пуста, то GetMessage передает управление оболочке, так что та может начать обработку сообщений другого приложения.
Какие же данные передаются сообщением?
typedef struct tagMSG {
HWND hwnd; // хендл окна-получателя
UINT message; // номер сообщения WM_...
WPARAM wParam; // параметр сообщения
LPARAM lParam; // параметр сообщения
DWORD time; // время поступления сообщения
POINT pt; // координаты сообщения (для сообщений мыши)
} MSG;
Поле message структуры MSG задает номер сообщения, посланного системой. Интерпретация параметров сообщения wParam и lParam зависит от самого сообщения. Для этого надо смотреть описание конкретного сообщения и обрабатывать параметры соответствующим образом. Так как в системе определено огромное количество разных сообщений, то для простоты использования применяются символические имена сообщений, задаваемыми с помощью #define в заголовочном файле. В качестве примера можно привести сообщения WM_CREATE, WM_PAINT, WM_QUIT.
hWnd указывает хендл окна, сообщения для которого будут выбираться из очереди. Если hWnd равен NULL, то будут выбираться сообщения для всех окон данного приложения, а если hWnd указывает реальное окно, то из очереди будут выбираться все сообщения, направленные этому окну или его потомкам (дочерним или используемым окнами, или их потомкам, в том числе отдаленным).
uMsgFilterMin и uMsgFilterMax обычно установлены в NULL. Вообще они задают фильтр для сообщений. GetMessage выбирает из очереди сообщения, номера (имена) которых лежат в интервале от uMsgFilterMin до uMsgFilterMax. Нулевые значения исключают фильтрацию.
Функция GetMessage возвращает во всех случаях, кроме одного, ненулевое значение, указывающее, что цикл надо продолжать. Только в одном случае эта функция возвратит 0 — если она извлечет из очереди сообщение WM_QUIT. Это сообщение посылается только при окончании работы приложения.
После завершения цикла надо сделать совсем немногое — освободить память от тех объектов, которые создавались во время работы приложения (если они еще существуют). Некоторые объекты, которые уничтожаются автоматически, можно не освобождать — это сделает Windows. Таков, например, зарегистрированный нами класс окон.
И остается еще одно дело: так как WinMain возвращает результат, то мы должны вернуть какое–либо значение. В Windows принято, что возвращаемое значение является параметром wParam сообщения WM_QUIT, завершившего цикл обработки сообщений. Таким образом мы пишем:
return msg.wParam;
Посылка и передача сообщений
Ранее, в разделе “Ошибка! Источник ссылки не найден.”, мы рассматривали метод передачи сообщений, называемый посылкой сообщений (post message), и их обработки — извлечения из очереди в цикле обработки сообщений, трансляции и последующей передачи оконной процедуре. Источниками таких сообщений могут быть как компоненты системы, например, клавиатурный драйвер, так и само приложение. Для посылки сообщения в API предусмотрена функция
BOOL PostMessage(hWnd, uMsg, wParam, lParam);
Эта функция ставит сообщение в очередь. Возвращаемое значение TRUE указывает, что сообщение поставлено в очередь, FALSE — возникла ошибка (например, ошибочно указанный адресат или очередь сообщений переполнилась). Позже сообщение будет извлечено из очереди вызовом функции GetMessage или PeekMessage в цикле обработки сообщений.
Однако механизм посылки сообщений не всегда удобен, так как не позволяет получить результат обработки сообщения, или дождаться его завершения. Точнее, позволяет, но очень громоздким способом — надо вводить специальные ответные сообщения и дожидаться их получения.
Вообще говоря, процесс посылки и обработки посланных сообщений часто называют асинхронным способом обработки сообщений, так как сама посылка сообщения и его обработка никак между собой не связаны по времени.
Для решения этих задач вводится альтернативный механизм, называемый передачей сообщений (send message). При этом сообщение в очередь не попадает, а направляется непосредственно оконной функции[1]. Приблизительно его можно рассматривать как непосредственный вызов процедуры обработки сообщений. Для передачи сообщения используется функция
LONG SendMessage(hWnd, uMsg, wParam, lParam);
Она вызывает оконную процедуру указанного окна, получает результат обработки сообщения и возвращает управление в вызвавшую процедуру после обработки указанного сообщения. Возвращаемое этой функцией значение совпадает с результатом, выполнения оконной функции. Вы можете воспользоваться ею для передачи тех или иных сообщений даже до организации цикла обработки сообщений, так как очередь сообщений при этом не используется (кроме особых случаев в Win32 API — см. ниже).
Многие функции API используют SendMessage для передачи сообщений окну. Например, функция CreateWindow, создающая окно, в процессе создания передает ему сообщение WM_CREATE (и не только одно это сообщение), а результат, возвращенный обработчиком сообщения, используется функцией CreateWindow для продолжения создания окна.
Процесс передачи сообщений с помощью функции SendMessage называется синхронной обработкой сообщений, так как процесс передачи и обработки сообщений жестко упорядочен во времени.
То, что сообщения, переданные с помощью функции SendMessage, минуют цикл обработки сообщений, накладывает ограничения на применение этой функции. Дело в том, что цикл обработки сообщений выполняет над некоторыми сообщениями определенные операции. Так, например, нельзя передавать сообщение WM_QUIT — оно обязательно должно пройти через очередь сообщений, так как используется для завершения цикла обработки сообщений. Другие сообщения (например, клавиатуры) транслируются в цикле и их тоже надо посылать, а не передавать.
Иногда сообщения передаются не какому–либо конкретному окну и не какому–либо приложению (потоку), а всем приложениям, запущенным в системе. Для этого используются широковещательные сообщения (broadcast message), которые передаются всем главным окнам всех приложений. Для передачи такого сообщения необходимо указать в качестве хендла окна–получателя специальный символ HWND_BROADCAST (равный -1), например:
SendMessage(HWND_BROADCAST, WM_DDE_INITIATE, (WPARAM)hwndDDEClient, 0L);
В этом случае сообщение WM_DDE_INITIATE будет передано главным окнам всех приложений, так что все работающие DDE–сервера смогут на него ответить.
Существенная особенность широковещательных сообщений — то, что они будут обрабатываться сразу большим количеством окон. То есть получить результат от обработчика этого сообщения обычным путем нельзя, так как остается неопределенность, результат какого обработчика возвращать. В частном случае (Win32 API) можно получить ответы от всех обработчиков с помощью функции SendMessageCallback. В общем случае при необходимости получения ответа от обработчика тот должен передать ответное сообщение (именно так сделано при установлении DDE–разговора).