7. Вывод информации в окно. Механизм перерисовки окна. Понятие области обновления окна. Операции с областью обновления окна


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



страница4/21
Дата28.11.2016
Размер2.21 Mb.
Просмотров5842
Скачиваний1
1   2   3   4   5   6   7   8   9   ...   21

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

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

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

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



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

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

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

DWORD GetWindowThreadProcessId(HWND hwnd,PDWORD pdwProcessId);

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

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

VOID PostQuitMessage(int nExitCode);

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

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

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

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

SendMessage:

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

Оконная процедура обработает сообщение, и только по окончании обработки функция SendMessage вернет управление.

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

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

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

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

Ожидая возврата управления функцией 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_NOTIMEOUTIFNOTHUNG или комбинацию этих флагов.

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

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

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

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

BOOL SendMessageCallback(HWND hwnd, UINT uMsg, WPARAM wParam,LPARAM lParam,

SENDASYNCPROC pfnResultCallBack, ULONG_PTR dwData);

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

VOID CALLBACK ResultCallBack(HWND hwnd, UINT uMsg, ULONG_PTR 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_BROADCAST (определенный как –1). Этот метод годится только для широковещательной рассылки сообщений, возвращаемые значения которых Вас не интересуют, поскольку функция способна вернуть лишь одно значение, LRESULT. Но, используя SendMessageCallback, можно получить результаты обработки «широковещательного» сообщения от каждого перекрытого окна. Ваша функция SendMessageCallback будет вызываться с результатом обработки сообщения от каждого из таких окон. Если SendMessageCallback вызывается для отправки сообщения окну, созданному вызывающим потоком, система немедленно вызывает оконную процедуру, а после обработки сообщения — функцию ResultCallBack. После возврата из ResultCallback выполнение начинается со строки, следующей за вызовом SendMessageCallback.

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

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

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

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

Во-вторых, если сообщение посылается окну, созданному вызывающим потоком, SendNotifyMessage работает точно так же, как и SendMessage, т. е. не возвращает управление до окончания обработки сообщения. Большинство синхронных сообщений посылается окну для уведомления — чтобы сообщить ему об изменении состояния и чтобы оно как-то отреагировало на это, прежде чем Вы продолжите свою работу. Например, 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 — при обработке им внутрипоточного сообщения (синхронного или асинхронного). Возвращаемые значения функций InSendMessage и ReplyMessage идентичны. Есть еще одна функция, позволяющая определить тип сообщения, которое обрабатывается Вашей оконной процедурой:

DWORD InSendMessageEx(PVOID pvReserved);

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

ISMEX_SEND Поток обрабатывает межпоточное синхронное сообщение, посланное

ISMEX_NOTIFY Поток обрабатывает межпоточное синхронное сообщение, посланное

ISMEX_CALLBACK Поток обрабатывает межпоточное синхронное сообщение, посланное

ISMEX_REPLIED Поток обрабатывает межпоточное синхронное сообщение и уже выз-

через SendMessage или SendMessageTimeout; если флаг ISMEX_REPLIED не установлен, поток-отправитель блокируется в ожидании ответа через SendNotifyMessage; поток-отправитель не ждет ответа и не блокируется через SendMessageCallback; поток-отправитель не ждет ответа и не блокируется вал ReplyMessage; поток-отправитель не блокируется
Передача данных через сообщения

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

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

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

В lParam передается адрес строки (с новым заголовком), расположенной в адресном пространстве Вашего процесса. Получив это сообщение, оконная процедура программы Calculator берет lParam и пытается манипулировать чем-то, что, «по ее мнению», является указателем на строку с новым заголовком. Но адрес в lParam указывает на строку в адресном пространстве Вашего процесса, а не программы Calculator. Вот Вам и долгожданная неприятность — нарушение доступа к памяти. Но если Вы все же выполните показанную ранее строку, все будет работать нормально. Что за наваждение? А дело в том, что система отслеживает сообщения 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. Оконная процедура возвращает число символов в строке заголовка окна. Это значение система использует при создании проекции файла, разделяемой двумя процессами. Создав проекцию файла, система посылает для его заполнения сообщение WM_GET- TEXT. Затем переключается обратно на процесс, первым вызвавший функцию SendMessage, копирует данные из общей проекции файла в буфер, на который указывает 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 задает число байтов, пересылаемых в другой процесс, а lpData указывает на первый байт данных. Адрес, идентифицируемый элементом lpData, находится, конечно же, в адресном пространстве отправителя. Увидев, что Вы посылаете сообщение WM_COPYDATA, SendMessage создает проекцию файла размером cbData байтов и копирует данные из адресного пространства Вашей программы в эту проекцию. Затем отправляет сообщение окну-приемнику. При обработке этого сообщения принимающей оконной процедурой параметр lParam указывает на структуру COPYDATASTRUCT, которая находится в адресном пространстве процесса-приемника. Элемент lpData этой структуры указывает на проекцию файла в адресном пространстве процесса-приемника.

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

1) Отправляйте его всегда синхронно; никогда не пытайтесь делать этого асинхронно. Последнее просто невозможно: как только принимающая оконная процедура обработает сообщение, система должна освободить проекцию файла. При передаче WM_COPYDATA как асинхронного сообщения появится неопределенность в том, когда оно будет обработано, и система не сможет освободить память, занятую проекцией файла.

2) На создание копии данных в адресном пространстве другого процесса неизбежно уходит какое-то время. Значит, пока SendMessage не вернет управление, нельзя допускать изменения содержимого общей проекции файла каким-либо другим потоком.

3) Сообщение WM_COPYDATA позволяет 16-разрядным приложениям взаимодействовать с 32-разрядными (и наоборот), как впрочем и 32-разрядным — с 64-разрядными (и наоборот). Это удивительно простой способ общения между новыми и старыми приложениями. К тому же, WM_COPYDATA полностью поддерживается как в Windows 2000, так и в Windows 98. Но, если Вы все еще пишете 16-разрядные Windows-приложения, учтите, что сообщение WM_COPYDATA и структура COPYDATASTRUCT в Microsoft Visual C++ версии 1.52 не определены. Вам придется добавить их определения самостоятельно:

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

#define WM_COPYDATA 0x004A

typedef VOID FAR* PVOID;

typedef struct tagCOPYDATASTRUCT {

DWORD dwData;

DWORD cbData;

PVOID lpData;

} COPYDATASTRUCT, FAR* PCOPYDATASTRUCT;

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

Поделитесь с Вашими друзьями:
1   2   3   4   5   6   7   8   9   ...   21


База данных защищена авторским правом ©nethash.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал