Windows

Посылка синхронных сообщений окну


Оконное сообщение можно отправить непосредственно оконной процедуре вызовом SendMessage:

LRESULT SendMessage( HWNO hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

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

Вот как работает SendMessage Если поток вызывает SendMessage для посылки со общения окну, созданному им же, то функция просто обращается к оконной проце дуре соответствующего окна как к подпрограмме. Закончив обработку, оконная про цедура передает функции SendMessage некое значение, а та возвращает его вызвавше му потоку.

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

Во-первых, переданное сообщение присоединяется к очереди сообщений пото ка-приемника, в результате чего для зтого потока устанавливается флаг QSSEND MESSAGE. Во-вторых, если поток-приемник в данный момент выполняет какой-то код и не ожидает сообщений (через вызов GetMessage, PeekMessage или WaitMessage), пе реданное сообщение обработать не удастся — система нс прсрвст работу потока для немедленной обработки сообщения. Но когда поток-приемпик ждет сообщений, си стема сначала проверяет, установлен ли флаг пробуждения QS_SENDMESSAGE, и, если да, просматривает очередь синхронных сообщений, отыскивая первое из них. В оче реди может находиться более одного сообщения Скажем, несколько потоков одно временно послали сообщение одному и тому же окну. Тогда система просто ставит эти сообщения в очередь синхронных сообщений потока.




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

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

Поскольку Windows обрабатывает межпоточные сообщения описанным выше образом, Ваш поток может зависнуть Допустим, в потоке, обрабатывающем синхрон ное сообщение, имеется "жучок", из-за которого поток входит в бесконечный цикл Что же произойдет с потоком, вызвавшим SendMessage? Возобновится ли когда-нибудь его выполнение? Значит ли это, что ошибка в одном приложении «подвесит» другое? Ответ — да!

Это верно даже в том случае, если оба потока принадлежит одному процессу

Избегать подобных ситуаций позволяют четыре функции, и первая из них — SendMessageTimeout



LRESULT SendMessageTimeout( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT fuFlags, UINT uTimeout, PDWORD_PTR pdwResult);

Она позволяет задавать отрезок времени, в течение которого Вы готовы ждать ответа от другого потока на Ваше сообщение Ее первые четыре параметра идентич ны параметрам функции SendMessage. В пэраметре fuFlags можно передавать флаги SMTO_NORMAL (0), SMTO_ABORTIFHUNG, SMTO_BLOCK, SMTO_NOTIMEOUTIFNO THUNG или комбинацию этих флагов.



Флаг SMTO_ABORTIFHUNG заставляет SendMessageTimeout проверить, не завис ли ноток приемник^, и, если да, немедленно вернуть управление Флаг SMTO_NOTIME OUTIFNOTHUNG сообщает функции, что она должна игнорировать ограничение по времени, если поток-приемник не завис. Флаг SMTO_BLOCK предотвращает обработ ку вызывающим потоком любых других синхронных сообщений до возврата из Send MessageTimeout, Флаг SMTO_NORMAL определен в файле WinUser.h как 0, он исполь зуется в том случае, если Вы нс указали другие флаги

Я уже говорил, что ожидание потоком окончания обработки синхронного сооб щения может быть прервано для обработки другого синхронного сообщения. Флаг SMTO_BLOCK предотвращает такое прерывание Он применяется, только если поток, ожидая окончания обработки своего сообщения, не в состоянии обрабатывать про чие синхронные сообщения. Этот флаг иногда приводит к взаимной блокировке по токов до конца таймаута Так, если Ваш поток отправит сообщение другому, а тому нужно послать сообщение Вашему, ни один из них не сможет продолжить обработку, и оба зависнут

Параметр uTimeout определяет таймаут — время (в миллисекундах), в течение которого Вы готовы ждать ответного сообщения. При успешном выполнении функ ция возвращает TRUE, а результат обработки сообщения копируется no адресу, ука занному в параметре pdwResult,

Кстати, прототип этой функции в заголовочном файле WinUser.h неверен. Функ цию следовало бы определить как возвращающую значение типа BOOL, поскольку значение типа LRFSULT на самом деле возвращается через ее параметр Это создает определенные проблемы, так как SendAlebbageTimeout вернет FALSE, если Вы переда дите неверный описатель окна или если закончится заданный период ожидания. Един ственный способ узнать причину неудачного завершения функции — вызвать GetLast Error. Последняя вернет 0 (ERROR_SUCCESS), если ошибка связана с окончанием пе риода ожидания. А если причина в неверном описателе, GetLastError даст код 1400 (ERROR_INVALID_WINDOW_HANDLE).

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



Операционная система считает поток зависшим, если он прекращает обработку сообще ний более чем на 5 секунд.

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

Теперь рассмотрим вторую функцию, предназначенную для отправки межпоточ ныхсообщений:

BOOL SendMessageCallhack( HWND hwnd, UINT uHsg, WPARAM лРагат, LPARAM lParam, SENDASYNCPROC pfnResultCallBack, ULONG_PTR dwOata);

И вновь первые четыре параметра идентичны параметрам функции SendMessage При вызове Вашим потоком SendMessageCallback отправляет сообщение в очередь синхронных сообщений потока-приемника и тут же возвращает управление вызыва ющему (т e Вашему) потоку Закончив обработку сообщения, поток-приемник асин хронно отправляет свое сообщение в очередь ответных сообщений Вашего потока. Позже система уведомит Ваш поток об этом, вызвав написанную Вами функцию; у нее должен быть следующий прототип,

VOID CALLBACK ResultCallBack( HWND hwnd. UINT uMsg, ULONG_PIR dwData, LRESULT lResult);

Адрес этой функции обратного вызова передается SendMessageCallback в параметре pfnResultCallBack А при вызове ResultCallBack в первых двух параметрах передаются описатель окна, закончившего обработкусообщения, и код (значение) самого сооб щения. Параметр dwData функции ResultCallBack всегда получает значение, передан ное SendMessageCallback в одноименном параметре. (Система просто берет то, что указано там, и передает Вашей функции ResultCallBack) Последний параметр функ ции ResultCallBack сообщает результат обработки сообщения, полученный от окон ной процедуры

Поскольку SendMessageCallback, передавая сообщение другому потоку, немедлен но возвращает управление, ResultCallBack вызывается после обработки сообщения потоком-приемником не сразу, а с задержкой. Сначала поток-приемник асинхронно ставит сообщение в очередь ответных сообщений потока-отправителя Затем при первом же вызове потоком-отправителем любой из функций GetMessage, PeekMessage, WaitMessage или одной из Send-функций сообщение извлекается из очереди ответных сообщений, и лишь потом вызывается Ваша функция ResultCallback.



Существует и другое применение функции SendMessageCallback В Windows пре дусмотрен метод, позволяющий разослать сообщение всем перекрывающимся окнам (overlapped windows) в системе; он состоит в том, что Вы вызываете SendMessage и в параметре hwnd передаете ей HWND_BROAUCAST (определенный как -1) Этот ме тод годится только для широковещательной рассылки сообщений, возвращаемые значения которых Вас не интересуют, поскольку функция способна вернуть лишь одно значение, LRESULT. Но, используя SendMessageCallback, можно получить резуль таты обработки "широковещательного" сообщения от каждого перекрытого окна Ваша функция SendMessageCallback будет вызываться с рсзульгатом обработки сооб щения от каждого из таких окон.

Если SendMessageCallback вызывается для отправки сообщения окну, созданному вызывающим потоком, система немедленно вызывает оконную процедуру, а после обработки сообщения — функцию ResultCallBack, После возврата из ResultCallback выполнение начинается со строки, следующей за вызовом SendMessageCallback.

Третья функция, предназначенная для передачи межпоточных сообщений:

BOOL SendNotifyMessage( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

Поместив сообщение в очередь синхронных сообщений потока-приемника, она немедленно возвращает управление вызывающему потоку. Так. ведет себя и PostMessage, помните? Но два отличия SendNotifyMessage от PostMessage все же есть.

Во-первых, если SendNotifyMessage посылает сообщение окну, созданномудругим потоком, приоритет данного синхронного сообщения выше приоритета асинхрон ных сообщений, находящихся в очереди потока-приемника Иными словами, сооб щения, помещаемые в очередь с помощью SendNolifyMessage, всегда извлекаются до выборки сообщений, отправленных через PostMessage,

Во-вторых, если сообщение посылается окну, созданному вызывающим потоком, SendNotifyMessage работает точно так же, как и SendMessage, т. e не возвращает управ ление до окончания обработки сообщения

Большинство синхронных сообщений посылается окну для уведомления — что бы сообщить ему об изменении состояния и чтобы оно как-то отреагировало на это, прежде чем Вы продолжите свою работу. Например, WM_ACTIVATE, WM_DESTROY, WM_ENABLE, WM_SIZE, WM_SETFOCUS, WM_MOVE и многие Другие сообщения - это просто уведомления, посылаемые системой окну в синхронном, а не асинхронном режиме. Поэтому система не прерывает свою работу только ради того, чтобы окон ная процедура могла их обработать. Прямо противоположный эффект дает отправка сообщения WM_CREATE — тогда система ждет, когда окно закончит его обработку. Если возвращено значение -1, значит, окно не создано.



И, наконец, четвертая функция, связанная с обработкой межпоточных сообщений:

BOOL ReplyMessage(LRESULT lResult);

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

Поток, вызывающий ReplyMessage, передаст результат обработки сообщения через параметр lResult После вызова ReplyMessage выполнение потока-отправителя возоб новляется, а поток, занятый обработкой сообщения, продолжает эту обработку. Ни один из потоков не приостанавливается — оба работают, как обычно. Когда поток, обрабатывающий сообщение, выйдет из своей оконной процедуры, любое возвраща емое значение просто игнорируется.

Заметьте: ReplyMessage надо вызывать из оконной процедуры, получившей сооб щение, но нс из потока, вызвавшего одну из Send-функций. Поэтому, чтобы написать "защищенный от зависаний" код, следует заменить все вызовы SendMessage вызовами

одной из трех Send-функций и не полагаться на то, что оконная процедура будет вызывать именно ReplyMessage.

Учтите также, что вызов ReplyMessage при обработке сообщения, посланного этим же потоком, не влечет никаких действий. На это и указывает значение, возвращаемое ReplyMessage- TRUE. — при обработке межпоточного сообщения и FALSE — при попыт ке вызова функции для обработки внутрипоточного сообщения.

Если Вас интересует, является обрабатываемое сообщение внутрипоточным или межпоточным, вызовите функцию InSendMessage:

BOOL InSendMessage();

Имя этой функции не совсем точно соответствует тому, что она делает в действи тельности На первый взгляд, функция должна возвращать TRUE, ссли поток обраба тывает синхронное сообщение, и FALSE — при обработке им асинхронного сообще ния. Но это не так. Она возвращает TRUE, если поток обрабатывает межпоточное син хронное сообщение, и FALSE — при обработке им внутрипоточного сообщения (син хронного или асинхронного). Возвращаемые значения функций lnSendMessage и ReplyMessage идентичны.



Есть еще одна функция, позволяющая определить тип сообщения, которое обра батывается Вашей оконной процедурой:

DWORD InSendMessageEx(PVOID pvReserved);

Вызывая ее, Вы должны передать NULL в параметре pvReserved. Возвращаемое зна чение указывает на тип обрабатываемого сообщения. Значение ISMEX_NOSEND (0) говорит о том, что поток обрабатывает внутрипоточное синхронное или асинхрон ное сообщение. Остальные возвращаемые значения представляют собой комбинацию битовых флагов, описанных в следующей таблице

Флаг Описание
ISMEX_ SEND Поток обрабатывает межпоточное синхронное сообщение, посланное через SendMessage или SendMessageTtmeout; если флаг ISMEX REPLIED не установлен, поток-отправитель блокируется в ожидании ответа
ISMEX_NOTIFY Поток обрабатывает межпоточное синхронное сообщение, посланное череч SendNotify Message, поток- отправитель не ждет ответа и не блоки руется
ISMEX_CALLBACK Поток обрабатывает межпоточное синхронное сообщение, посланное через SendMessageCallback; поток- отправитель не ждет ответа и не бло кируется
ISMEX_REPLIED Поток обрабатывает межпоточпое синхронное сообщение и уже выз вал ReplyMessage; поток-отправитель не блокируется

Содержание раздела