49086 (597427), страница 2
Текст из файла (страница 2)
BOOL PostThreadMessage(dwThreadID, uMsg, wParam, lParam);
которая посылает сообщение конкретному потоку. При этом в очередь соответствующего потока помещается сообщение, имеющее нулевой хендл окна–получателя. Идентификатор потока dwThreadID можно получить посредством функций:
DWORD GetWindowThreadProcessId(hWnd, lpdwProcessID);
DWORD GetCurrentThreadId(void);
Так как один процесс может иметь несколько потоков, каждый со своей очередью и своими окнами, и сообщения могут передаваться (SendMessage) окнам любого потока и даже любого процесса, то для обеспечения необходимого уровня защиты было принято решение, что бы сообщения, направленные окну, обрабатывались только тем потоком, который это окно создал. Если сообщение передается окну, созданному тем–же потоком, то функция SendMessage просто вызывает оконную процедуру для обработки сообщения.
А если окно создано другим потоком, то процесс передачи сообщения приводит к сложному взаимодействию потока–отправителя и потока–получателя. Передающий поток помещает сообщение в очередь принимающего потока со специальным флагом переданное сообщение (QS_SENDMESSAGE) и приостанавливается до получения ответа. Принимающий поток, закончив обработку текущего сообщения, извлекает из очереди первыми сообщения с флагом переданное, обрабатывает их, возвращает результат и после этого возобновляет работу передавшего сообщение потока.
Однако в жизни ситуация существенно усложняется: часто поток, обрабатывающий переданное сообщение, передает пославшему потоку какое–либо «встречное» сообщение. Так, например, при начале DDE–разговора DDE–клиент передает с помощью функции SendMessage широковещательное сообщение WM_DDE_INITIATE, а DDE–сервер отвечает на это сообщение передачей сообщения WM_DDE_ACK, если он поддерживает требуемый DDE–разговор. То есть обработчик сообщения WM_DDE_INITIATE сервера передает с помощью той–же функции SendMessage сообщение WM_DDE_ACK окну клиента, которое в данный момент ждет конца обработки переданного им сообщения WM_DDE_INITIATE4. При этом было бы возможным зависание обеих потоков, так как поток клиента остановлен до конца обработки переданного им WM_DDE_INITIATE, а поток сервера будет ждать, пока остановленный поток клиента не ответит на встречное сообщение WM_DDE_ACK, которое он передает в качестве подтверждения.
Что бы избежать подобных неприятностей функция SendMessage может обрабатывать сообщения, переданные данному потоку другими потоками, находясь в ожидании ответа от обработчика переданного сообщения.
Осторожно! Зависание потоков при обмене сообщениями все–таки возможно. Такая ситуация легко может случиться при использовании MDI: в Win32 для каждого открытого дочернего MDI–окна автоматически создается свой собственный поток. В этом случае при использовании функции SetFocus для передачи фокуса от одного дочернего MDI–окна другому дочернему MDI–окну (естественно принадлежащему другому потоку) происходит обмен сообщениями WM_SETFOCUS и WM_KILLFOCUS, что приводит к остановке обеих потоков. Эта особенность платформы официально описана Microsoft.
Кроме того возможно зависание потока, передавшего сообщение, если поток–получатель завис сам или занят очень продолжительными операциями. Так как продолжительная остановка передающего сообщение потока может быть нежелательна, то в Win32 API описаны специальные функции для работы с межпотоковыми сообщениями: SendMessageTimeout, SendMessageCallback, SendNotifyMessage и ReplyMessage.
Внимание! Функции SendMessageTimeout, SendMessageCallback и SendNotifyMessage не реализованы для платформы Win32s а, кроме того, в документации (правда не всегда) встречается странное упоминание о том, что в случае Windows–95 параметр wParam является 16ти битовым (!), что может существенно ограничить применение этих функций для передачи многих сообщений.
LRESULT SendMessageTimeout(
hWnd, uMsg, wParam, lParam, fuFlags, uTimeout, lpdwResult);
Дополнительные параметры этой функции: lpdwResult — указывает переменную, в которую будет помещен результат обработки сообщения, uTimeout — максимальное время, в миллисекундах, в течении которого надо ожидать результата, а fuFlags — определяет, как функция будет ожидать ответа:
флаг SMTO_ABORTIFHUNG говорит о том, что если поток–получатель завис (то есть уже более 5 секунд не обрабатывает никаких сообщений), то не ждать результата, а сразу вернуть управление.
флаг SMTO_BLOCK исключает обработку «встречных» сообщений во время ожидания ответа. С этим флагом надо быть очень осторожным!
флаг SMTO_NORMAL используется тогда, когда ни SMTO_BLOCK ни SMTO_ABOPRTIFHUNG не указаны.
Возвращаемый функцией результат равен TRUE, если сообщение обработано или FALSE, если отведенное для ожидания время истекло или поток–получатель завис.
Если функция SendMessageTimeout применяется для передачи сообщения окну, созданному тем–же самым потоком, то она работает как обычная функция SendMessage — время ожидания не ограничено и проверка на зависание не выполняется.
Вторая функция SendMessageCallback передает сообщение указанному окну и возвращает управление немедленно после этого, не дожидаясь результата обработки сообщения. Но когда сообщение будет обработано, будет вызвана указанная вами функция, обрабатывающая полученный результат.
BOOL SendMessageCallback(hWnd, uMsg, wParam, lParam, lpfnResultCallback, dwData);
void CALLBACK ResultCallback(hWnd, uMsg, dwData, lResult) {...}
Параметр lpfnResultCallback является указателем на функцию–обработчик результата, а dwData — некоторые дополнительные данные, которые вы хотите передать в вашу функцию–обработчик. Прототип этой функции приведен в следующей строке.
Уточнение: реально функция–обработчик результата будет вызвана не немедленно после обработки сообщения получателем, а результат обработки будет помещен в очередь обработанных сообщений, откуда он будет извлечен тогда, когда поток, передавший сообщение, обратиться к очереди сообщений для получения следующего (то есть при вызове GetMessage, PeekMessage, WaitMessage или одну из функций SendMessage...).
Функцию SendMessageCallback можно с успехом сочетать с широковещательными сообщениями — тогда у вас появляется возможность анализировать результаты обработки этого сообщения всеми главными окнами приложений.
Если функция SendMessageCallback используется для передачи сообщения окну, созданному тем–же потоком, то получение результата и вызов функции–обработчика результата осуществится немедленно, до возвращения из функции SendMessageCallback.
Отдельно стоит рассмотреть вопросы оптимизации обработки межпотоковых сообщений. Дело в том, что Windows NT может работать на многопроцессорных системах, реально выполняя несколько потоков в одно и то же время. В этом случае может быть целесообразно сократить время, в течении которого один поток просто ожидает результат обработки другого потока. Для этого предназначены две дополнительные функции ReplyMessage и SendNotifyMessage.
Функция ReplyMessage используется обработчиком межпотокового сообщения для того, что бы досрочно сообщить вызвавшему потоку об окончании обработки сообщения. Так, например, возможна ситуация, когда поток–получатель сообщения выясняет, что сообщение будет успешно обработано и даже известно, какой результат надо будет возвратить в самом начале обработки, но до полного завершения необходимо выполнить еще некоторые дополнительные действия. В этом случае будет удобно сообщить требуемый результат сразу после его получения с помощью функции ReplyMessage, после чего продолжить обработку сообщения. Поток, передавший сообщение сможет продолжить свою работу не дожидаясь полного завершения обработки сообщения.
Функция SendNotifyMessage появилась на свет в связи с тем, что многие сообщения являются просто информационными. То есть они сообщают окну, что с ним сделано то–то и то–то, но результат обработки этого сообщения не нужен. Часто такие сообщения должны передаваться в синхронном режиме, например сообщение WM_DESTROY должно быть обработано во время выполнения функции DestroyWindow и до обработки сообщения WM_NCDESTROY, но то как оно будет обработано и каков результат его обработки — не важно. В качестве обратного примера можно привести обработку сообщения WM_CREATE, так как результат обработки нужен для дальнейшего создания окна или отказа от его создания.
Для синхронной передачи информационных сообщений удобно применять функцию SendNotifyMessage, которая передает сообщение как обычная функция SendMessage, но не дожидается результата обработки этого сообщения. До определенной степени эффект от выполнения функции SendNotifyMessage похож на эффект от функции PostMessage, но переданное таким способом сообщение будет обработано заведомо то того, как будет обработано любое посланное обычным способом сообщение.
Подробнее о циклах обработки сообщений
Цикл обработки сообщений, в том простейшем виде, который применен в примере 1A, применяется редко. В реальных приложениях этот цикл усложняется. Обычно при этом преследуют следующие задачи:
включить дополнительные средства трансляции сообщений, обычно применяемые акселераторами и немодальными диалогами;
при выполнении каких–либо продолжительных действий дать возможность обрабатывать поступающие сообщения (например, в процессе печати документа);
включить обработку состояний простоя (когда в очереди нет сообщений) для выполнения каких–либо действий в фоновом режиме.
При рассмотрении двух последних задач следует иметь в виду, что аналогичного эффекта можно добиться применением многопотоковых приложений. Более того, во многих случаях многопотоковый вариант оказывается предпочтительным. При принятии решения необходимо учитывать специфику разрабатываемого приложения и его область применения. Если отказ от устаревшей 16ти разрядной платформы Windows 3.x не вызывает возражений, а также речь идет о разработке нового приложения, а не переносе старого на новую платформу, то стоит серьезно проработать вопрос с реализацией многопотокового приложения.
Дополнительная обработка сообщений
Первая задача — дополнительная обработка сообщений — обычно сводится к выполнению дополнительных действий после извлечения сообщения из очереди и трансляцией этого сообщения. Для применения акселераторов обычно используют функцию TranslateAccelerator или TranslateMDISysAccel, для немодальных диалогов — IsDialogMessage. Часто функции, выполнившие специфичную трансляцию сообщения, заодно передают их оконной процедуре, так что последующая трансляция и вызов DispatchMessage становятся не нужны. Например:
HWND hwndDlg = ...; // хендл окна немодального диалога
MSG msg;
while (GetMessage(&msg, NULL, NULL, NULL)) {
if (!IsWindow(hwndDlg) || !IsDialogMessage(hwndDlg, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);}}
В этом примере проверяется наличие окна немодального диалога и, если оно существует, то выполняется специальная трансляция сообщений с помощью функции IsDialogMessage. Сообщения, обработанные этой функцией, в дальнейшей трансляции не участвуют
Продолжительные операции
Вторая задача — дать возможность нормальной работы интерфейса при выполнении продолжительных операций. Эта задача обычно решается путем встраивания вспомогательных циклов обработки сообщений в процессе выполнения продолжительных операций. Единственное, что при этом требуется — что бы цикл завершался не по получению WM_QUIT, а по завершению обработки последнего сообщения в очереди, что бы приложение могло продолжить свою операцию. Для этого применяют функцию PeekMessage вместо GetMessage.
BOOL PeekMessage(lpMsg, hWnd, uFilterFirst, uFilterLast, fuRemove);
Эта функция осуществляет выборку сообщений из очереди, однако, если очередь пуста, функция PeekMessage просто возвращает FALSE, а не ждет поступления сообщения. Ее параметры аналогичны параметрам функции GetMessage, кроме дополнительного fuRemove. Этот параметр может быть комбинацией флага PM_NOYIELD и одного из флагов PM_REMOVE или PM_NOREMOVE.
Флаг PM_NOYIELD говорит функции о том, что при отсутствии сообщений в очереди нельзя передавать управление другим приложениям. Обычно PeekMessage при отсутствии сообщений в очереди передает управление другим приложениям и, только если их очереди тоже пусты, возвращает FALSE. Таким образом цикл, построенный на PeekMessage без флага PM_NOYIELD, не исключает возможность нормальной работы других приложений.
Флаг PM_NOREMOVE явно указывает, что нужно только проверить наличие сообщений в очереди. Если сообщение есть, возвращается информация об этом сообщении (в структуре MSG), а сообщение остается в очереди. Флаг PM_REMOVE указывает на необходимость извлечения сообщений из очереди.
MSG msg;
// продолжительные операции (вычисления, печать ...)
while (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);}