Что называют сообщением windows

Что называют сообщением windows

Рустэм Галеев aka Roustem

7. Сообщения Windows

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

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

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

Настало время исправить это положение. Для получения сообщения из очереди существует функция GetMessageA из модуля User32.dll. Эта функция перемещает очередное сообщение из очереди в структуру типа MSG, адрес которой мы указываем в параметрах. Структура MSG имеет следующее строение:

Смещение Размер, байт Поле
0 4 Описатель окна, которому предназначено сообщение
4 4 Код сообщения
8 4 Параметр сообщения wParam
0Ch 4 Параметр сообщения lParam
10h 4 Время отправки сообщения
14h 4 Координата x указателя мыши в момент посылки сообщения
18h 4 Координата y указателя мыши в момент посылки сообщения

Как указывалось в прошлой статье, основной рабочей единицей в Windows является окно, и сообщения посылаются именно окнам. Чтобы самому послать сообщение, нужно указать описатель окна, для которого сообщение предназначено. Эта информация записывается в первое поле структуры. Параметры wParam и lParam содержат дополнительную информацию, которая специфична для каждого кода сообщения. Система добавляет также к каждому сообщению информацию о времени и координатах курсора мыши в момент отправки сообщения.

Функция GetMessageA принимет 4 параметра, которые размещаются в стеке в следующем порядке:

  1. максимальный код сообщения, который принимает функция;
  2. минимальный код сообщения, который принимает функция;
  3. описатель окна, для которого нужно получить сообщение;
  4. адрес структуры MSG, куда должно быть скопировано сообщение.

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

Переделаем созданное в прошлый раз приложение так, чтобы включить в него вызов функции GetMessageA. Причем сделаем это с некоторым «запасом» — оставим место (в первую очередь, в структурах для импорта) еще для 2 функций, которые нам пригодятся в дальнейшем.

В секции данных нам потребуется лишь зарезервировать место для структуры MSG размером 28 байтов; но, поскольку мы ее разместим после строк по смещению 3020h, а после нее ничего нет, никаких изменений в файл data.txt вводить не потребуется — просто надо запомнить, что адрес структуры (после загрузки ее в память) будет 403020h. Сюда в процессе работы приложения будут копироваться сообщения системы.

Читайте также:  Windows 10 общественная сеть как поменять частную

Зато изменить потребуется файл rdata.txt. GetMessageA и другие функции, которые мы собираемся добавить потом, находятся в одном модуле User32.dll, поэтому вторую IAT и вторую таблицу поиска придется увеличить на 12 байтов, а таблицу импорта и следующие за ней строки сместить. Соответственно потребуется также изменить смещения строк во всех таблицах. Файл rdata.txt будет выглядеть следующим образом:

В секции кода после вызова функции CreateWindowExA добавляется вызов функции GetMessageA (предварительно в стек помещаются 4 параметра — три нулевых и адрес структуры MSG в секции данных). Цикл также меняется: теперь внутри него находится функция GetMessageA, и соответствующая инструкция должна передать управление команде помещения в стек первого параметра для этой функции. Теперь это уже не просто цикл, а цикл обработки сообщений — один из главных элементов приложения Windows (хотя сама обработка пока не совсем полноценная).

Рассмотрим подробнее инструкцию безусловного перехода, которая на ассемблере обозначается мнемоникой JMP. Опкод можно представить следующим образом:

«Буквально» эта инструкция добавляет с учетом знака непосредственное значение (следующее за опкодом) к текущему значению регистра EIP. Обратите внимание на наличие бита знакового расширения s. Напомним, что если он равен 1, за опкодом следует всего один байт данных, который расширяется со знаком (т.е. старший бит этого байта заполняет 3 оставшихся старших байта). Если s = 0, за опкодом следуют 4 байта.

Как вы уже знаете, в регистре EIP находится адрес начала следующей инструкции. Таким образом, данная инструкция изменяет адрес следующей инструкции и тем самым осуществляет безусловный переход на исполнение кода где-то в другом месте. Указанный операнд является смещением этого «другого места» относительно начала следующей инструкции. Если смещение положительно, переход осуществляется «вперед»; если отрицательно (старший бит = 1), переход «назад». Сама инструкция перехода в короткой форме занимает 2 байта, поэтому смещение -2 означает переход на себя (отсчет ведется от следующей инструкции!); переведем это отрицательное число в бинарную форму: 2 = 00000010b; двоичное дополнение = 11111101b, добавив единицу, получим: -2 = 11111110b. Полная инструкция будет (s = 1):

— то, что мы уже использовали в прошлый раз.

На этот раз нам нужно подсчитать число байтов, которые следует «перескочить» — это будет прыжок «назад», как вы уже поняли (и смещение соответственно отрицательное). В файле code.txt находим строку «параметр ExitProcess (код завершения = 0)» и заменяем ее следующим кодом:

Подсчитаем число получившихся байтов: 17. Еще 2 займет сама команда перехода; поэтому, чтобы перейти на начало первой инструкции ‘6a 0’, нужно добавить к значению EIP смещение -19 (11101101b = EDh). Сама же инструкция перехода будет иметь вид FEh EDh. Остаток файла должен выглядеть так:

В файле ‘header.txt’ необходимо изменить лишь смещение таблицы импорта:

Размер таблицы остался тем же. Файл ‘make.bat’ также остался без изменений. Строим новый файл wnd.exe и запускаем его.

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

Чтобы от сообщений был какой-то толк, их нужно не просто забирать из очереди, но и направлять соответствующему окну. Для этой цели служит еще одна функция из модуля User32.dll — DispatchMessageA. Эта функция принимает лишь один аргумент — адрес структуры MSG, содержащей соответствующее сообщение. Добавим в наше приложение эту функцию.

В секции данных никаких изменений не будет. В секцию .rdata нужно добавить после имеющихся строк строку ‘DispatchMessageA’, а также указать ее смещение в IAT(2) и таблице поиска в специально оставленных для этого местах. Найдем строку:

— и заменим ее на следующую:

Это нужно сделать в двух местах — в IAT(2) и в таблице поиска для User32.dll. В конец файла нужно добавить строку ‘DispatchMessageA’:

Теперь изменим секцию кода. В файле ‘code.txt’ найдем строки:

Теперь вставим после них инструкции помещения в стек параметра и вызова функции DispatchMessageA:

Читайте также:  Какие лицензии бывают для windows

Дальше следует безусловный переход на начало цикла. Однако, мы добавили 11 новых байтов, поэтому смещение будет теперь не -19, а -30 (или E2h). Поэтому конец файла должен выглядеть так:

Файлы header.txt и make.bat не изменились. Строим очередную версию wnd.exe.

Вот это другое дело! Теперь окно выглядит так, как оно должно было выглядеть — это одна большая кнопка! Причем реагирует на щелчки мышью! А само окно можно перемещать, изменять его размеры, сворачивать, разворачивать и даже закрывать! Правда, в последнем случае не спешите радоваться — окно-то вы закрыли, но приложение осталось работать (оно все еще крутится в цикле обработки сообщений). Убедиться в этом несложно, заглянув в менеджер задач (нажав Ctrl-Alt-Del) — вы увидите выполняющуюся задачу ‘wnd’ (если файл не был переименован). Ее снова придется «прибивать» отсюда. Это происходит потому, что класс окна «BUTTON», как уже говорилось, не предназначен для создания самостоятельных окон. При закрытии главного окна приложения в цикл сообщений посылается соответствующее уведомление о завершении приложения; в нашем случае этого не происходит. К тому же наш цикл вообще не предусматривает возможности выхода из него — в дальнейшем мы реализуем и такую возможность.

Чтобы рассмотреть еще одну функцию, использующуюся в цикле сообщений, изменим класс нашего окна на «EDIT». Для этого в файле ‘data.txt’ просто поменяем строку «BUTTON» на «EDIT». И все! Снова строим wnd.exe, запустив make.bat.

И какая разительная перемена! Поэкспериментируйте с этим приложением сами. В качестве подсказки: воспользуйтесь контекстным меню, щелкнув в окне правой клавишей мыши; а для копирования текста в буфер можно воспользоваться Блокнотом.

Я думаю, вы уже заметили особенность нового окна: хотя можно пользоваться командами редактирования текста из всплывающего меню и двигать текстовый курсор с помощью стрелок, набрать текст на клавиатуре не удается. Это происходит потому, что окно получает лишь сообщения о нажатиях клавиш, но они не преобразуются в сообщения о поступлении соответствующих символов. Как раз для решения этой задачи и служит последняя функция — TranslateMessage, тоже из модуля User32.dll. Эта функция принимает один параметр — конечно же, опять все тот же адрес структуры MSG, и помещается в цикл обработки сообщений как раз перед вызовом функции DispatchMessageA.

Добавим эту функцию. Для этого в конце файла ‘rdata.txt’ добавляем соответствующую строку:

Второй 0 после «DispatchMessageA» нужен для выравнивания начала следующей строки по четному адресу. Необходимо также изменить (тоже в двух местах — для IAT(2) и таблицы поиска User32.dll) строки:

В файле code.txt очередные 11 байтов нужно добавить до вызова DispatchMessageA (не забыв изменить смещение в инструкции безусловного перехода на начало цикла), так что конец файла будет выглядеть так:

Вот теперь, построив очередную версию wnd.exe, мы получим почти полноценный текстовый редактор! Хотя пока он работает лишь с одной строкой; но зато создан полностью в машинных кодах!

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

Сообщения Windows (типы сообщений, принципы работы системы сообщений Windows, система сообщений Delphi, специализированные записи, обработка сообщений)

При разработке приложений обычно достаточно возможностей работы с событиями, предоставляемыми VCL. Но при создании нестандартных приложений и при разработке собственных компонентов Delphi часто требуется непосредственно обрабатывать сообщения Windows и создавать на их основе события Delphi.

Сообщение Windows – это извещение о некотором имевшем место событии, посылаемое Windows в адрес приложения. Любые действия пользователя – щелчок мышью, изменение размеров окна приложения, нажатие клавиши на клавиатуре – вынуждают Windows отправить приложению сообщение, извещающее о том, что же произошло в системе. Сообщение представляет собой запись, объявленную в модуле Windows следующим образом:

TMsg = packed record

hwnd: HWND; // дескриптор окна-получателя

message: UINT; // идентификатор сообщения

wParam: WPARAM; // 32 бита дополнительной информации

Читайте также:  Wscsvc что это за служба windows 10

lParam: LPARAM; // еще 32 бита дополнительной информации

time: DWORD; // время создания сообщения

pt: TPoint; // положение указателя мыши в момент создания сообщения

Каждому сообщению Windows поставлено в соответствие определенное числовое значение, которое заносится в поле message записи TMsg. В Delphi эти величины, идентифицирующие сообщения определены в модуле Messages. Имя каждой константы начинается с символов WM (Windows Message). Примеры некоторых сообщений приведены в таблице.

Идентификатор сообщения Значение Описание
WM_ACTIVATE $0006 Окно активируется или деактивируется. Что именно происходит, определяется младшим словом wParam (LOWORD(wParam)). Старшее слово содержит значение типа BOOL, которое содержит значение True, если окно минимизируется. Поле lParam содержит дескриптор окна, которое будет деактивировано, когда наше окно активируется или дескриптор окна, которое будет активировано, если наше окно деактивируется
WM KEYDOWN $0100 На клавиатуре была нажата клавиша
WM KEYUP $0101 На клавиатуре была отпущена клавиша
WM PAINT $000F Требуется перерисовать клиентскую часть окна
WM CLOSE $0010 Окно должно быть закрыто
WM_QUIT $0012 Приложение должно быть завершено

Какие для каждого события данные содержатся в wParam и lParam догадаться невозможно и надо узнавать в справочной системе или в литературе.

Принципы работы системы сообщений Windows

Система сообщений Windows состоит из трех составляющих:

1. Очередь сообщений. Windows поддерживает отдельную очередь сообщений для каждого приложения. Приложение Windows должно получать сообщения из этой очереди и передавать их соответствующему окну.

2. Цикл сообщений. Группа операторов, осуществляющая выборку сообщения из очереди и передачу его соответствующему окну для обработки.

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

Цикл сообщений встроен в модуль Forms, благодаря чему прикладному программисту не нужно беспокоиться о выборке сообщений из очереди и передаче их соответствующим процедурам окон. Кроме того, Delphi помещает информацию из записи типа TMsg в собственную запись типа TMessage.

TMessage = packed record

case Integer of

0: (WParam: Longint;

1: (WParamLo: Word;

Эта запись содержит меньше информации, чем в исходной записи TMsg. Кроме того, в TMessage содержится поле Result, которое используется для возврата результата обработки сообщения.

Обработка сообщений в Delphi

Обработка сообщений означает, что приложение тем или иным образом реагирует на получаемые от операционной системы сообщения.

Сообщение в Delphi может быть обработано четырьмя способами.

1. С помощью процедуры обработки сообщения.

2. В методе WndProc.

3. В методе Dispatch.

4. С помощью обработки события OnMessage объекта Application.

1. Наиболее простой способ обработать некоторое сообщение Windows, это создать для него специальную процедуру обработки сообщения. Такая процедура должна отвечать следующим требованиям:

• Должна быть методом.

• Должна иметь один var-параметр типа TMessage или любого специализированного типа.

• В конце объявления содержится ключевое слово message и константа, задающая тип обрабатываемого сообщения.

Пример процедуры, обрабатывающей сообщение WM_PAINT:

procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;

2. В классе TControl имеется виртуальный метод WndProc:

procedure WndProc(var Message: TMessage); virtual;

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

3. У класса TObject существует виртуальный метод Dispatch:

procedure Dispatch(var Message); virtual;

Этот метод также можно перекрыть у класса-потомка, чтобы обработать одно или несколько сообщений.

4. Последний способ – использовать событие OnMessage объекта Application.

// фрагмент модуля Forms:

TMessageEvent = procedure (var Msg: TMsg; var Handled: Boolean) of object;

property OnMessage: TMessageEvent read FOnMessage write FOnMessage;

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

В дополнение к обычной записи типа TMessage в Delphi определен набор специализированных записей для всех типов сообщений Windows. Они предназначены для работы с дополнительными данными сообщений без необходимости декодировать значения полей wParam и lParam.

Названия этих типов соответствуют типу сообщения, для которого они предназначены (начинаются с буквы T, и в имени нет символа подчеркивания). Для примера ниже приведено определение записи для события WM_ACTIVATE.

// фрагмент модуля Windows:

// фрагмент модуля Messages:

TWMActivate = packed record

Active: Word; // WA_INACTIVE, WA_ACTIVE, WA_CLICKACTIVE

Оцените статью