Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Создание эффективных приложений для Windows Джеффри Рихтер 2004 (Книга).pdf
Скачиваний:
385
Добавлен:
15.06.2014
Размер:
8.44 Mб
Скачать

Заметьте: 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.

Рис. 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 имеет низкий приоритет, так как прорисовка экрана — операция не са мая