Новые книги

Сергей Васильев – первый инвестор и председатель совета директоров Рамблера в 1999–2001 гг. Его новая книга посвящена освоению русского интернета с начала нулевых годов, самого яркого и бурного времени его развития, до наших дней.

В книге описана история становления одного из первых и легендарных российских интернет-порталов – Рамблера. Какие вызовы стояли в те дни перед зарождавшимся интернет-бизнесом, какие проблемы приходилось решать и чем все это закончилось. В середине 2000-х автор вместе с партнерами взялся за освоение украинского интернет-пространства; здесь в перипетии жесткого и конкурентного бизнеса начали вплетаться политика и война…

Это реальная хроника событий, фактов, удач и поражений в российском и украинском интернете глазами одного из его первопроходцев. Но эта книга не только про интернет, она – откровенный рассказ автора об инвестициях, людях, бизнесе и политике.
PascalABC.NET — это язык программирования Паскаль нового поколения, включающий классический Паскаль, большинство возможностей языка Delphi, а также ряд собственных расширений. Он реализован на платформе Microsoft.NET и содержит все современные языковые средства: классы, перегрузку операций, интерфейсы, обработку исключений, обобщенные классы и подпрограммы, сборку мусора, лямбда-выражения, средства параллельного программирования.

PascalABC.NET является мультипарадигменным языком: на нем можно программировать в структурном, объектно-ориентированном и функциональном стилях.

PascalABC.NET — это также простая и мощная интегрированная среда разработки, поддерживающая технологию IntelliSense, содержащая средства автоформатирования, встроенный отладчик и встроенный дизайнер форм.

Глава 26. Оконные сообщения

ЧАСТЬ VI ОПЕРАЦИИ С ОКНАМИ

ГЛАВА 26 Оконные сообщения

В этой главе я расскажу, как работает подсистема передачи сообщений в Windows применительно к приложениям с графическим пользовательским интерфейсом Раз рабатывая подсистему управления окнами в Windows 2000 и Windows 98, Microsoft преследовала две основные цели

  • обратная совместимость с 16-разрядной Windows, облегчающая перенос суще ствующих 16-разрядных приложении,
  • отказоустойчивость подсистемы управления окнами, чтобы ни один поток не мог нарушить работу других потоков в системе

К сожалению, эти цели прямо противоречат друг другу В 16-разрядной Windows передача сообщения в окно всегда осуществляется синхронно отправитель не может продолжить работу, пока окно не обработает полученное сообщение Обычно так и нужно Но, если на обработку сообщения потребуется длительное время или если окно «зависнет», выполнение отправителя просто прекратится А значит, такая операцион ная система не вправе претендовать на устойчивость к сбоям

Это противоречие было серьезным вызовом для команды разработчиков из Micro soft В итоге было выбрано компромиссное решение, отвечающее двум вышеупомя нутым целям Помните о них, читая эту главу, и Вы поймете, почему Microsoft сделала именно такой выбор

Для начала рассмотрим некоторые базовые принципы Один процесс в Windows может создать до 10 000 User-объектов различных типов — значков, курсоров, окон ных классов, меню таблиц клавиш-акселераюров и т д Когда поток из какого-либо процесса вызывает функцию, создающую один из этих объектов последний перехо дит во владение процесса Поэтому, если процесс завершается, не уничтожив данный объект явным образом, операционная система делает этo за него Однако два User объектa (окна и ловушки) принадлежат только создавшему их потоку И вновь, если поток создает окно или устанавливает ловушку а потом завершается, операционная система автоматически уничтожает окно или удаляет ловушку

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

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

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

Очередь сообщений потока

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

Создавая какой-либо поток, система предполагает, что он не будет иметь отноше ния к поддержке пользовательского интерфейса. Это позволяет уменьшшь объем выделяемых ему системных ресурсов. Но, как только поток обратится к той или иной GUI-функции (например, для проверки очереди сообщений или создания окна), сис тема автоматически выделит сму дополнительные ресурсы, необходимые для выпол нения задач, связанных с пользовательским интерфейсом А ссли конкретнее, то сис тема создает структуру THREADINFO и сопоставляет ее с этим потоком

Элементы этой структуры используются, чтобы обмануть поток — заставить его считать, будто он выполняется в среде, принадлежащей только ему. THREADINFO — это внутренняя (недокументированная) структура, идентифицирующая очередь асин хронных сообщений потока (posted-message queue), очередь синхронных сообщений потока (sent-message queue), очередь ответных сообщений (reply-message queue), оче редь виртуального ввода (virtualized input queue) и флаги пробуждения (wakc flags), она также включает ряд других переменных-членов, характеризующих локальное состояние ввода для данного потока На рис 26-1 показаны структуры THREADINFO, сопоставленные с тремя потоками.

Структура THREADINFO — фундамент всей подсистемы передачи сообщений; чи тая следующие разделы, время от времени посматривайте на эту иллюстрацию.

Посылка асинхронных сообщений в очередь потока

Когда с потоком связывается структура THREADINFO, он получает свой набор очере дей сообщений. Если процесс создает три потока и все они вызывают функцию Create Window, то и наборов очередей сообщений будет тоже три Сообщения ставятся в очередь асинхронных сообщений вызовом функции PostMessage:

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

При вызове этой функции система определяет, каким потоком создано окно, иден тифицируемое параметром hwnd, Далее система выделяет блок пямяти, сохраняет в нем параметры сообщения и записывает этот блок в очередь асинхронных сообще ний данного потока. Кроме того, функция устанавливает флаг пробуждения QS_POST MESSAGE (о нем — чуть позже). Возврат из PostMessage происходит сразу после того, как сообщение поставлено в очередь, поэтому вызывающий поток остается в неведе нии, обработано ли оно процедурой соответствующего окна На самом деле вполне вероятно, что окно даже не получит это сообщение Такое возможно, если поток, создавший это окно, завершится до того, как обработает все сообщения из своей очереди.

rihter26-1.jpg

Рис. 26-1. Три потока и соответствующие им структуры THREADINFO

Сообщение можно поставить в очередь асинхронных сообщений потока и вызо вом PostThreadMessage:

BOOL PostThreadMessage( DWORD dwThreadId, UINT uMsg, WPARAM wParam, LPARAM lParam);

NOTE
Какой поток создал окно, можно определить с помощью GetWindowThreadPro cessId:

DWORD GetWindowThreadProcessId( HWND hwnd PDWORD pdwProccssId);

Она возвращает уникальный общесистемный идентификатор потока, ко торый создал окно, определяемое параметром hwnd. Передав адрес перемен ной типа DWORD в параметре pdwProcessId, можно получить и уникальный общесистемный идентификатор процесса, которому принадлежит этот поток. Но обычно такой идентификатор не нужен, и мы просто передаем NULL

Нужный поток идентифицируется первым параметром, dwThreadId. Когда сооб щение помещено в очередь, элемент hwnd структуры MSG устанавливается как NULL. Применяется эта функция, когда приложение выполняет какую то особую обработку в основном цикле выборки сообщений потока, — в этом случае он пишется так, что бы после выборки сообщения функцией GetMessage (или PeekMessage) код в цикле сравнивал hwnd с NULL и, выполняя эту самую особую обработку, мог проверить зна чение элемента msg структуры MSG. Если поток определил, что сообщение не адре совано какому-либо окну, DispatchMessage не вызывается, и цикл переходит к выбор ке следующего сообщения.

Как и PostMessage, функция PostThreadMessage возвращает управление сразу после того, как сообщение поставлено в очередь потока И вновь вызывающий поток оста ется в неведении о дальнейшей судьбе сообщения

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

VOID PostOuitMessage(int nExilCode);

Она вызывается для того, чтобы завершить цикл выборки сообщений потока Ее вызов аналогичен вызову

PostThreadMessage(GetCurrentThreadId(), WM_OUIT, nExitCode, 0);

Но в действительности PostQuitMessage не помещает сообщение ни в одну из оче редей структуры THREADINFO. Эта функция просто устанавливает флаг пробуждения QS_QUIT (о нем я тоже расскажу чуть позже) и элемент nExitCode структуры THREAD INFO. Так как эти операции не могут вызвать ошибку, функция PostQuitMessage не возвращает никаких значений (VOID).

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

Оконное сообщение можно отправить непосредственно оконной процедуре вызовом 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; поток-отправитель не блокируется

Пробуждение потока

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

Флаги состояния очереди

Во время выполнения поток может опросить состояние своих очерсдсй вызовом GetQueueStatus:

DWORD GetQueueStatus(UINT fuFlags);

Параметру fuFlags — флаг или группа флагов, объединенных побитовой операци ей OR, он позволяет проверить значения отдельных битов пробуждения (wake bits) Допустимые значения флагов и их смысл описаны в следующей таблице.

Флаг

Сообщение в очереди

QS_KEY

WM_KEYUP,WM_KEYDOWN, WM_SYSKEYUP или WM_SYSKEYDOWN

QS_MOUSEMOVE

WM_MOUSEMOVE

QS_MOUSEBTITTON

WM_?BUTTON* (где знак вопроса заменяет букву L, М или R, а звездочка — DOWN, UP или DBLCLK)

QS_MOUSE

То же, что QS_MOUSEMOVE | QS_MOUSEBUTTON

QS_INPUT

То же, что QS_MOUSE | QS_KEY

QS_PAINT

WM_PAINT

QS_TIMER

WM_TIMER

QS_HOTKEY

WM_HOTKEY

QS_POSTMESSAGE

Асинхронное сообщение (отличное от события аппаратного ввода), этот флаг идентичен QS_ALLPOSTMESSAGE с тем исклю чением, что сбрасывается при отсутствии асинхронных сообще ний в диапазоне действия фильтра сообщений

QS_ALLPOSTMESSAGE

Асинхронное сообщение (отличное от события аппаратного вво да); этот флаг идентичен QS_POSTMESSAGE с тем исключением, что сбрасывается лишь при полном отсутствии каких-либо асин хронных сообщений (вне зависимости от фильтра сообщений)

QS ALLEVENTS

Тоже, что QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY

QS_QUIT

Сообщает о вызове PostQuitMessage, этот флаг не задокументиро ван, его нет в WinUser.h, и он используется самой системой

QS_SENUMESSAGE

Синхронное сообщение, посланное другим потоком

QS_ALLINPUT

То же, что QS_ALLEVENTS | QS_SENDMESSAGF

При вызове GetQueueStatus параметр fuFlags сообщает функции, наличие каких типов сообщений в очереди следует проверить. Чем меньше идентификаторов QS_* объединено побитовой операцией OR, тем быстрее отрабатывается вызов Результат сообщается в старшем слове значения, возвращаемого функцией. Возвращаемый на бор флагов всегда представляет собой подмножество того набора, который Вы зап росили от функции. Например, если Вы делаете такой вызов.

BOOL fPaintMsgWaiting = HIWORD(GetQueueStatus(QS TIMER)) & OS_PAINT;

ю значение fPaintMsgWaiting всегда будет равно FALSE независимо от наличия в оче реди сообщения WM_PAINT, так как флаг QS_PAINT функции не передан

Младшсс слово возвращаемого значения содержит типы сообщений, которые помещены в очередь, но не обработаны с момента последнего вызова GetQueueStatus, GetMessage или PeekMessage.

Не все флаги пробуждения обрабатываются системой одинаково Флаг QS_MOUSE MOVE устанавливается, если в очереди есть необработанное сообщение WM_MOUSE

MOVE. Когда GetMessage или PeekMessage (с флагом PM_REMOVE) извлекают последнее сообщение WM_MOUSEMOVE, флаг сбрасывается и остается в таком состоянии, пока в очереди ввода снова не окажется сообщение WM_MOUSEMOVE. Флаги QS_KEY, QS_MOUSEBUTTON и QS_HOTKEY действуют при соответствующих сообщениях ана логичным образом.

Флаг QS_PAINT обрабатывается иначе. Он устанавливается, если в окне, созданном данным потоком, имеется недействительная, требующая псрсрисовки область Когда область, занятая всеми окнами, созданными одним потоком, становится действитель ной (обычно в результате вызова ValidateRect, ValidateRegion или BeginPaint), флаг QS_PAINT сбрасывается. Еще pay подчеркну: данный флаг сбрасывается, только если становятся действительными все окна, принадлежащие потоку. Вызов GetMessage или PeekMessage на этот флаг пробуждения пе влияет.

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

Флаг QS_TIMER устанавливается после срабатывания таймера (созданного пото ком). После того как функция GetMessage или PeekMessage вернет WM_TIMER, флаг сбрасывается и остается в таком состоянии, пока таймер вновь не сработает

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

Есть еще один (недокументированный) флаг состояния очереди — QS_QUIT. Он устанавливается при вызове потоком PostQuitMessage. Сообщение WM_QUIT при этом не добавляется к очереди сообщений И учтите, что GetQueueStatus не возвращает состояние этого флага

Алгоритм выборки сообщений из очереди потока

Когда поток вызывает GetMessage или PeekMessage, система проверяет флаги состоя ния очередей потока и определяет, какое сообщение надо обработать (рис 26-2)

1. Если флаг QS_SENDMESSAGE установлен, система отправляет сообщение соот ветствующей оконной процедуре GetMessage и PeekMessage контролируют процесс обработки и пе передают управление потоку сразу после того, как оконная процедура обработает сообщение, вместо этого обе функции ждут следующего сообщения.

2. Если очередь асинхронных сообщений потока не пуста, GetMessage и Peek Message заполняют переданную им структуру MSG и возвращают управление Цикл выборки сообщений (расположенный в потоке) в этот момент обычно обращается к DispatchMessage, чтобы соответствующая оконная процедура об работала сообщение.

3. Если флаг QS_QUIT установлен, GetMessage и PeekMessage возвращают сообще ние WM__QUIT (параметр wParam которого содержит указанный код заверше ния) и сбрасывают этот флаг.

4 Если в очереди виртуального ввода потока есть какие-то сообщения, GetMessage и PeekMessage возвращают сообщение, связанное с аппаратным вводом.

5. Если флаг QS_PAINT установлен, GetMessage и PeekMessage возвращают сооб щение WM_PAINT для соответствующего окна

6 Если флаг QS_TIMER установлен, GetMessage и PeekMessage возвращают сооб щение WM_TIMER.

rihter26-2.jpgРис. 26-2 Алгоритм выборки сообщений из очереди потока

Хоть и трудно в это поверить, но для такого безумия есть своя причина. Главное, из чего исходила Microsoft, разрабатывая описанный алгоритм, — приложения долж ны слушаться пользователя, и именно его действия (с клавиатурой и мышью) управ ляют программой, порождая события аппаратного ввода Работая с программой, поль зователь может нажать кнопку мыши, что приводит к генерации последовательности определенных собьний. А программа порождает отдельные события, асинхронно отправляя сообщения в очсрсдь потока

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

Прекрасный пример такой последовательности событий — вызов функции Trans lateMessage, проверяющей, не было ли выбрано из очереди ввода сообщение WM_KEY DOWN или WM_SYSKEYDOWN. Если одно из этих сообщений выбрано, система про веряет, можно ли преобразовать информацию о виртуальной клавише в символьный эквивалент. Если это возможно, TranslateMessage вызывает PostMessage, чтобы помес тить в очередь асинхронных сообщений WM_CHAR или WM_SYSCHAR При следую щем вызове GetMessage система проверяет содержимое очереди асинхронных сооб щений и, если в ней есть сообщение, извлекает его и возвращает потоку. Возвращает ся либо WM_CHAR, либо WM_SYSCHAR. При следующем вызове GetMessage система обнаруживает, что очсрсдь асинхронных сообщений пуста Тогда она проверяет оче редь ввода, где и находит сообщение WM_(SYS)KEYUP; именно оно и возвращается функцией GetMessage.

Поскольку система устроена так, а нс иначе, последовательность аппаратных со бытий:

WM_KEYDOWN
WM_KEYUP

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

WM_KEYDOWN
WM_CHAR
WM_KEYUP

Вернемся к тому, как система решает, что за сообщение должна вернуть функций GctMessage или PeekMessage. Просмотрев очередь асинхронных сообщений, система, прежде чем перейти к проверке очереди виртуального ввода, проверяет флаг QS_QUIT Вспомните этот флаг устанавливается, когда поток вызывает PostQuitMessage. Вызов PostQuitMcssage дает примерно ют же эффект, что и вызов PostMessage, которая поме щает сообщение в конец очереди и тем самым заставляет обрабатывать его до про верки очереди ввода Так почему же PostQuitMessage устанавливает флаг вместо того, чтобы поместить WM_QUIT в очередь сообщений? На то есть две причины.

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

те кода сообщение WM_USER будет извлечено до WM_QUIT, даже если WM_USER асин хронно помещено в очередь после вызова PostQuitMessage.

case WM_CLOSE:

PostQuitMessage(0);
PostMessage(hwnd, WM_USER, 0, 0);

А теперь о последних двух сообщениях: WM_PAINT и WM_TIMER. Сообщение WM_PAINT имеет низкий приоритет, так как прорисовка экрана — операция не са мая быстрая. Если бы это сообщение посылалось всякий раз, когда окно становится недействительным, быстродействие системы снизилось бы весьма ощутимо. Но по мещая WM_PAINT после ввода с клавиатуры, система работает гораздо быстрее. На пример, из меню можно вызвать какую-нибудь команду, открывающую диалоговое окно, выбрать в нем что-то, нажать клавишу Enter — и проделать все это даже до того, как окно появится на экране. Достаточно быстро нажимая клавиши, Вы наверняка за метите, что сообщения об их нажатии извлекаются прежде, чем дело доходит до со общений WM_PAINT. А когда Вы нажимаете клавишу Enter, подтверждая тем самым значения параметров, указанных в диалоговом окне, система разрушает окно и сбра сывает флаг QS_PAINT

Приоритет WM_TIMER еще ниже, чем WM_PAINT. Почему? Допустим, какая-то программа обновляет свое окно всякий раз, когда получает сообщение WM_TIMER. Если бы оно поступало слишком часто, программа просто не смогла бы обновлять свое окно Но поскольку сообщения WM_PAINT обрабатываются до WM_TIMER, такая проблема не возникает.

NOTE
Функции GetMessage и PeekMessage проверяют флаги пробуждения только для вызывающего потока. Это значит, что потоки никогда не смогут извлечь сооб щения из очереди, присоединенной к другому потоку, включая сообщения для потоков того же процесса.

Пробуждение потока с использованием объектов ядра или флагов состояния очереди

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

Чтобы поток ждал собственных сообщений, вызовите функцию MsgWaitForMultiple Objects или MsgWaitForMultipleObjectsEx:

DWORD MsgWaitForMultipleOb]ects( DWORD nCount, PHANDLE phOb]ects, BOOL fWaitAll, DWORD dwMilUseconds, DWORD dwWakeMask);

DWORD MsgWaitForMultipleObjectsEx( DWORD nCount, PHANDLE phObjects, DWORD dwMilUseconds,DWORD dwWakeMask, DWORD dwFlags);

Эти функции аналогичны WaitForMultipleObjects (см главу 9). Разница в том, что при их использовании поток становится планируемым, когда освобождается какой нибудь из указанных объектов ядра или когда оконное сообщение нужно переслать окну, созданному этим потоком.

Внутренне система просто добавляет объект ядра "событие" в массив описателей ядра Параметр dwWakeMask сообщает системе, в какой момент объект-событие дол жно переходить R свободное состояние. Его допустимые значения идентичны тем, которые можно передавать в функцию GetQueueStatus.

WaitForMultipleObjects обычно возвращает индекс освобожденного объекта (в диа пазоне от WAIT_OBJECT_0 до WAIT_OBJECT_0 + nCount - 1). Задание параметра dwWa keMask равносильно добавлению еще одного описателя При выполнении условия, определенного маской пробуждения,MsgWaitForMullipleObjects(Ex) возвращает значе ние WAIT_OBJECT_0 + nCount.

Вот пример вызова MsgWaitForMultipleObjects

MsgWaitForMultipleObjects(0, NULL, TRUE, INFINITE, QS_INPUT);

Описатели синхронизирующих объектов в этом операторе не передаются — па раметры nCount и phObjects равны соответственно 0 и NULL. Мы указываем функции ждать освобождения всех объектов Но в действительности задан лишь один объект, и с тем же успехом параметру fWaitAll можно было бы присвоить знячение FALSE. Мы также сообщаем, что будем ждать — сколько бы времени это ни потребовало — появ ления в очереди ввода потока сообщения от клавиатуры или мыши.

Начав пользоваться функцией MsgWaitForMultipleObjects в своих программах, Вы быстро поймете, что она лишена многих важных качеств. Вот почему Microsoft при шлось создать более совершенную функцию MsgWaitForMultipleObjectsEx, которая по зволяет задать в параметре dwFlags любую комбинацию следующих флагов.

Флаг

Описание

MWMO_WAITALL

Функция ждет освобождения всех объектов ядра и появления в очереди потока указанных сообщений (без этого флага функ ция ждет освобождения одного из объектов ядра или появле ния в очереди одного из указанных сообщений)

MWMO_ALERTABLE

Функция ждет в «тревожном» состоянии

MWMO_INPUTAVAILABLE

Функция ждет появления в очереди потока одного из указан ных сообщений

Если Вам не нужны эти дополнительные возможности, передайте в dwFlags нуле вое значение.

При использовании MsgWaitForMultipIeObjects(Ex) учитывайте, что.

  • эти функции лишь включают описатель внутреннего объекта ядра «событие» в массив описателей объектов ядра, и значение параметра nCount не должно превышать 63 (MAXIMUM_WAIT_OBJECTS - 1);
  • если в параметре fWaitAll передастся FALSE, функции возвращают управление при освобождении объекта ядра или при появлении в очереди потока сооб щения заданного типа,
  • если в параметре fWaitAll передается TRUE, функции возвращают управление при освобождении всех объектов ядра и появлении в очереди потока сообщения заданного типа. Такое поведение этих функций преподносит сюрприз многим разработчикам Ведь очень часто поток надо пробуждать при освобож дении всех объектов ядра или при появлении сообщения указанного типа. Но функции, действующей именно так, нет;
  • при вызове любая из этих функций на самом деле проверяет в очереди пото ка только новые сообщения заданного типа.

Заметьте, что и последняя особенность этих функций — не очень приятный сюр приз для многих разработчиков. Возьмем простой пример Допустим, в очереди по токи находятся два сообщения о нажатии клавиш. Если теперь вызвать MsgWaitForMul tipleObjects(Ex) и задать в dwWakeMask значение QS_INPUT, поток пробудится, извле чет из очереди первое сообщение и обработает его Но на повторный вызов MsgWait ForMultipleObjects(Ex) поток никак не отреагирует — ведь новых сообщений в очере ди нет.

Этот механизм создал столько проблем разработчикам, что Microsoft пришлось добавить в MsgWaitForMultipleObjectsEx поддержку флага MWMO_INPUTAVATLABLE

Вот как надо писать цикл выборки сообщений при использовании MsgWaitForMul tipleObjectsEx

BOOL fQuit = FALSE; // надо ли завершить цикл?

while (!fQuit)
{

// поток пробуждается при освобождении обьекта ядра ИЛИ
// для обработки сообщения от пользовательского интерфейса

DWORD dwResult = MsgWaitForMultipleObjectsEx(1, &hEvent, INFINITE, QS_ALLEVENTS, MWMO_INPUTAVAILABLE);

switch (dwResult}
{

case WAIT_OBJECT_0:

// освободилось событие
break;

case WAIT_OBJECT_0 + 1:

// в очереди появилось сообщение
// разослать все сообщения MSG msg;

while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{

if (msg.message == WM_QUIT)
{

// сообщение WM_QUIT - выходим из цикла
fQuit = TRUE;

}
else
{

// транслируем и пересылаем сообщение
TranslateMessage(&msg);
DispatchMessage(&msg);

}

}

// наша очередь пуста
break;

}

}

// конец цикла while

Передача данных через сообщения

Здесь мы обсудим, как система обеспечивает передачу данных между процессами с помощью сообщений В некоторых оконных сообщениях параметр lParam задает адрес блока памяти. Например, сообщение WM_SETTEXT использует lParam как ука затель на строку (с нулевым символом в конце), содержащую новый текст для окна. Рассмотрим такой вызов:

SendMessage(FindWindow{NULL, "Calculator"), WM_SETTEXT, 0, (LPARAM) "A Test Caption" );

Вроде бы все достаточно безобидно определяется описатель окна Calculator и делается попытка изменить его заголовок на «A Test Caption». Но приглядимся к тому, что тут происходит

В lParam передается адрес строки (с новым заголовком), расположенной в адрес ном пространстве Вашего процесса. Получив это сообщение, оконная процедура программы Calculator берет lParam и пытается манипулировать чем-то, что, «по ее мнению", является указателем на строку с новым заголовком.

По адрес в lParam указывает на строку в адресном пространстве Вашего процес са, а не программы Calculator Вот Вам и долгожданная неприятность — нарушение доступа к памяти. Но если Вы все же выполните показанную ранее строку, все будет работать нормально. Что за наваждение5

А дело в том, что система отслеживает сообщения WM_SETTEXT и обрабатывает их не так, как большинство других сообщений. При вызове SendMessage внутренний код функции проверяет, не пытаетесь ли Вы послать сообщение WM_SETTEXT. Если это так, функция копирует строку из Вашего адресного пространства в проекцию файла и делает его доступным другому процессу. Затем сообщение посылается пото ку другого процесса. Когда поток-приемник готов к обработке WM_SETTEXT, он оп ределяет адрес общей проекции файла (содержащей копию строки) в адресном про странстве своего процесса Параметру lParam присваивается значение именно этого адреса, и WM_SETTEXT направляется нужной оконной процедуре. После обработки этого сообщения, проекция файла уничтожается Не слишком ли тут накручено, а?

К счастью, большинство сообщений не требует такой обработки — она осуществ ляется, только если сообщение посылается другому процессу. (Заметьте: описанная обработка выполняется и для любого сообщения, параметры wParam или lParam ко торого содержат указатель на какую-либо структуру данных )

А вот другой случай, когда от системы требуется особая обработка, — сообщение WM_GETTEXT. Допустим, Ваша программа содержит код:

char szBuf[200];

SendMessage(FindWindow(NULL, "Calculator"), WM_GETTEXT, Sizeof(szBuf), (LPARAM) szBuf);

WM_GETTEXT требует, чтобы оконная процедура программы Calculator помести ла в буфер, на который указывает szBuf, заголовок своего окна. Когда Вы посылаете это сообщение окну другого процесса, система должна на самом деле послать два сообщения. Сначала — WM_GETTEXTLENGTH Оконная процедура возвращает число символов в строке заголовка окна. Это значение система использует при создании проекции файла, разделяемой двумя процессами,

Создав проекцию файла, система посылает для cro заполнения сообщение WM_GET TEXT Затем переключается обратно на процесс, первым вызвавший функцию SendMes-

sage, копирует данные из общей проекции файла в буфер, на который указывает szBuf, и заставляет SendMessage вернуть управление

Что ж, все хороши, пока Вы посылаете сообщения, известные системе А если мы определим собственное сообщение (WM_USER + x), собираясь отправить его окну другого процесса? Система не «поймет», что нам нужна общая проекция файла для корректировки указателей при их пересылке. Но выход есть — что сообщение WM_COPYDATA:

COPYDATASTRUCT cds;

SendMessage(hwndReceiver, WM_COPYDATA, (WPARAM) hwndSender, (LPARAM) &cds);

COPYDATASTRUCT структура, определенная в WinUser.h:

typedef struct tagCOPYDATASTRUCT
{

ULONG_PTR dwData;
DWORD cbData;
PVOID lpData;

} COPYDATASTRUCT;

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

Элемент cbData задает число байтов, пересылаемых в другой процесс, a lpData указывает на первый байт данных Адрес, идентифицируемый элементом lpData, на ходится, конечно же, в адресном пространстве отправителя

Увидев, что Вы посылаете сообщение WM_COPYDATA, SendMessage создает проек цию файла размером cbData байтов и копирует данные из адресного пространства Вашей программы в эту проекцию. Затем отправляет сообщение окну-приемнику При обработке этого сообщения принимающей оконной процедурой параметр lParam указывает на структуру COPYDATASTRUCT, которая находится в адресном простран стве процесса-приемника Элемент lpData этой структуры указывает на проекцию файла в адресном пространстве процесса-приемника.

Вам следует помнить о трех важных вещах, связанных с сообщением WM_COPY DATA

  • Отправляйте его всегда синхронно, никогда не пытайтесь делать этого асинх ронно Последнее просто невозможно: как только принимающая оконная про цедура обработает сообщение, система должна освободить проекцию файла. При передаче WM_COPYDATA как асинхронного сообщения появится неопре деленность в том, когда оно будет обработано, и система не сможет освобо дить память, занятую проекцией файла.
  • На создание копии данных в адресном пространстве другого процесса неиз бежно уходит какое-то время Значит, пока SendMessage не вернст управление, нельзя допускать изменения содержимого общей проекции файла каким-либо другим потоком
  • Сообщение WM_COPYDATA позволяет 16-разрядным приложениям взаимо действовать с 32-разрядными (и наоборот), как впрочем и 32-разрядным — с 64-разрядными (и наоборот). Это удивительно просюй способ общения меж ду новыми и старыми приложениями. К тому же, WM_COPYDATA полностью поддерживается как в Windows 2000, так и в Windows 98 Но, если Вы все еще пишете 16-разрядные Windows-приложсния, учтите, что сообщение WM_COPY

DATA и структура COPYDATASTRUCT в Microsoft Visual С++ версии 1 52 не оп ределены Вам придется добавить их определения самостояельно.

// включите этот код в свою 16-разрядную Windows-программу
#define WM_COPYDATA 0x004A

typedef VOID FAR* PVOID;

typedef struct taqCOPYDATASTRUCT
{

DWORD dwData;
DWORD cbDdta;
PVOID lpData;

} COPYDATASTRUCT, FAR* PCOPYDATASTRUCT;

Сообщение WM_COPYDATA — мощный инструмент, позволяющий разработчикам экономить массу времени при решении проблем связи между процессами И очень жаль, что применяется оно нечасто Насколько полезно это сообщение, иллюстриру ет программа-пример LastMsgBoxInfo из главы 22

Программа-пример CopyData

Эта программа, «26 CopyData.exe» (см. листинг иа рис 26-3), демонстрирует приме нение сообщения WM_COPYDAIA при пересылке блока данных из одной программы в другую Файлы исходного кода и ресурсов этой программы находятся в каталоге 26 CopyData на компакт-диске, прилагаемом к книге Чтобы увидеть программу CopyData в действии, запустите минимум две ее копии, при этом каждая копия открывает диа логовое окно, показанное ниже

rihter26-3.jpg

Если Вы хотите посмотреть, как данные копируются из одного приложения в дру гое, то сначала измените содержимое полей Datal и Data2 Затем щелкните одну из двух кнопок Send Data* To Other Windows Программа отправит данные всем выпол няемым экземплярам CopyData, и в их полях появится новые данные

А теперь обсудим принцип работы программы. Щелчок одной из двух кнопок приводит к.

  1. Инициализации элемента dwData структуры COPYDATASTRUCT нулевым зна чением (если выбрана кнопка Scnd Datal To Other Windows) или единицей (если выбрана кнопка Send Data2 To Other Windows)
  2. Подсчету длины текстовой строки (в символах) из соответствующего поля с добавлением единицы, чтобы учесть нулевой символ в конце Полученное чис ло символов преобразуется в количество байтов умножением на sizeof(TCHAR), и результат записывается в элемент cbData структуры COPYDATASTRUCT
  3. Вызову _alloca, чтобы выделить блок памяти, достаточный для хранения стро ки с учетом концевого нулевого символа Адрес этого блока записывается в элемент lpData все той же структуры
  4. Копированию текста из поля в выделенный блок памяти

Теперь все готово для пересылки в другие окна Чтобы определить, каким окнам следует посылать сообщение WM_COPYDATA, программа вызывает FindWindowEx и передает заголовок своего диалогового окна - благодаря этому перечисляются толь ко другие экземпляры данной программы. Найдя окна всех экземпляров, программа пересылает им сообщение WM_COPYDATA, что заставляет их обновить содержимое своих полей.

CopyData

 

Как Windows манипулирует с ANSI/Unicode-символами и строками

WINDOWS 98
Windows 98 поддерживает классы и процедуры окон только в формате ANSI

Регисрируя новый класс окна, Вы должны сообщить системе адрес оконной проце дуры, которая отвечает за обработку сообщений для этого класса. В некоторых сооб щениях (например, WM_SETTEXT) параметр lParam является указателем на строку. Для корректной обработки сообщения система должна заранее знать, в каком формате оконная процедура принимает строки — ANSI или Unicode.

Выбирая конкретную функцию для регистрации класса окна, Вы сообщаете сис теме формат, приемлемый для Вашей оконной процедуры Если Вы создаете структу ру WNDCLASS и вызываете RegisterClassA, система считает, что процедура ожидает исключительно ANSI-строки и символы А регистрация класса окна через Rеgister ClassW заставит систему полагать, что процедуре нужен Unicode. И, конечно же, в за висимости от того, определен ли UNICODE при компиляции модуля исходного кода, макрос RegisterClass будет раскрыт либо в RegisterClassA, либо в RegisterClassW

Располагая описателем окна, Вы можете выяснить, какой формат символов и строк требует оконная процедура Для этого вызовите функцию:

BOOL IsWindowUnicode(HWND hwnd);

Если оконная процедура ожидает передачи данных только в Unicode, эта функция возвращает TRUE, в ином случае — FALSE.

Если Вы сформировали ANSI-строку и посылаете сообщение WM_SETTEXT окну, чья процедура принимает только Unicode-строки, то система перед отсылкой сооб щения автоматически преобразует его в нужный формат. Так что необходимость в вызове lsWindowUnicode возникает нечасто

Система автоматически выполняет все преобразования и при создании подклас ca окна. Допустим, что для заполнения своего поля ввода оконная процедура ожида ет передачи символов и строк в Unicode Кроме того, где-то в программе Вы создаете поле ввода и подкласс оконной процедуры, вызывая

LONG_PTR SetWindowLongPtrA( HWND hwnd, int nlndex, LONG_PTR dwNewLong);

или

LONG_PTR SetWindowLongPtrW( HWND bwnd, int nIndGx, LONG_PTR dwNewLong);

При этом Вы передаете в параметре nlndex значение GOLP_WNDPROC, а в пара мегре dwNewLong — адрес своей процедуры полкласса. Но что будет, если Ваша про цедура ожидает передачи символов и строк в формате ANSI? B принципе, это чрева то проблемами. Система определяет, как преобразовывать строки и символы в зави симости от функции, вызванной Вами для создания подкласса Используя SetWmdow LongPtrA, Вы сообщаете Windows, что новая оконная процедура (Вашего подкласса) принимает строки и символы только в ANSI. (Вызвав IsWindowUnicode после SetWin dowLongPtrA, Вы получили бы FALSE, так как новая процедура не принимает строки и символы в Unicode.)

Но теперь у нас новая проблема: как сделать так, чтобы исходная процедура полу чала символы и строки в своем формате? Для корректного преобразования системе нужно знать две вещи. Во-первых, текущий формат символов и строк. Эту информа цию мы предоставляем, вызывая одну из двух функций — CallWindowProcA или CalWin dowProcW

LRESULT CallWindowProcA( WNDPROC wndprcPrev, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

LRESULT CallWindowProcW( WNDPROC wndprcPrev, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

При передаче исходной оконной процедуре ANSI-строк процедура подкласса дол жна вызывать CalWindowProcA, а при передаче Unicode-строк — CallWtndowProcW

Второе, о чем должна знать система, — тип символов и строк, ожидаемый исход ной оконной процедурой Система получает эту информацию по адресу этой проце дуры. Когда Вы вызываете SetWindowLongPtrA или SetWindowIongPtrW, система прове ряет, создаете ли Вы ANSI-подкласс Unicode-процедуры окна или наоборот. Если при создании подкласса тип строк нс меняется, SetWindowLongPtr просто возвращает ад рес исходной процедуры. В ином случае SetWmdowLongPtr вместо этого адреса воз вращает описатель внутренней структуры данных.

Эта структура содержит адрес исходной оконной процедуры и значение, которое указывает на ожидаемый ею формат строк При вызове CallWindowProc система про веряет, что Вы передаете — адрес оконной пропедуры или описатель внутренней структуры данных. В первом случае система сразу обращается к исходной оконной процедуре, так как никаких преобразований не требуется, а во втором случае систе ма сначала преобразует символы и строки в соответствующую кодировку и только потом вызывает исходную оконную процедуру.