Меню Рубрики

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

Message методы, или обработка сообщений классами в Delphi

Written on 03 Февраля 2009 . Posted in Delphi

Наверняка каждый из нас хотя бы раз в своей практике, но встречался с кодом вида:

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

Однако, как это работает? Почему это работает? И как следует правильно это использовать? Какие могут возникнуть проблемы, подводные камни? Давайте посмотрим.

Предпосылки

Логика возникновения подобного трюка лично мне совершенно ясна — разработчики Delphi хотели ясный, понятный и читаемый код при разработке визуальных компонент. Как всем известно, при создании окна необходимо задать оконную функцию. То есть некую callback функцию, которая принимает по ссылке сообщение и возвращает результат. Во всех примерах приложений на чистом WinAPI эта оконная функия состоит из case с перечислением всех сообщений, которое окно хочет обрабатывать и вызова DefWindowProc для остальных сообщений. Для обработки небольшого числа сообщений этот case не страшен. Однако если окно обрабатывает большое количество сообщений и обработчики сообщений содержат ветвления, секции исключений и прочие запутывающие вещи, то отладка, поиск обработки нужного сообщения и чтение подобного превращается в сущий кошмар (А ведь если взглянуть на количество контролов и классов в Delphi можно прийти в ужас от подобной перспективы).

Разработчики RTL пошли хитрым путем. Вместо огорода из case они придумали хитрый, изящный и весьма интересный трюк — message методы.

Как это работает

Message методы — часть языка Delphi, эта функциональность реализована уже в TObject, так что создавая любой класс вы уже имеете возможность обрабатывать сообщения (об этом чуть позднее). При добавлении message метода (сигнатура процедуры при этом должна быть определенного вида: procedure Name(var Message: MessageRecord); message [число]) в класс этот метод располагается в vmt по адресу, кратному числовому значению сообщения, стоящего после ключевого слова message. Message методы — динамические, ведь обработка сообщения должна быть не только у базового класса, но и у его потомков. Чтобы «послать сообщение», классу нужно сформировать струкутру Message (не обязательно TMessage, об этом чуть позже) и вызвать метод Dispatch нужного класса. Этот метод производит поиск в vmt адрес метода по смещению Msg структуры Message у класса и, если его нет непосредственно у класса, среди message методов его родителей. Если адрес метода не найден (то есть обработка такого сообщения не присутствует ни у одного класса в иерархии), то производится вызов метода DefaultHandler. Таким образом, схема обработки сообщений в VCL приобретает более удобную для восприятия и модификации форму. Вместо:

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

После выборки сообщения из очереди, оно направляетя в оконную процедуру (1), где пробегается через case (2) и направляется в блок обработки сообщения (3), затем результат возвращается в качестве результата оконной функции (4).

Типичная схема обработки сообщений в VCL будет выглядеть приблизительно так:

После выборки сообщения (1), оно направляется в оконную процедуру WndProc (2). В ней происходит вызов метода Dispatch (3), который ищет, есть ли у TWinControl обработчик данного сообщения (4). Пусть в обработчике будет произведен вызов inherited. Метод динамический, поэтому будет производиться поиск среди message методов родителя (то есть TControl). Для удобства понимания (хотя на деле конечно не так, убедитесь в этом, открыв окно CPU) пройдем такой же путь — через Dispatch. Структура, содержащая сообщение направляется в родительский класс (4.1, 4.2), в нем идет поиск обработчика. Если обработчик не найден, вызывается метод DefaultHandler. Поскольку DefaultHandler у TWinControl переопределен, то произойдет его вызов (4.3), в котором будет вызов обработчика сообщений по умолчанию, то есть для Windows это DefWindowProc. Далее будет воврат в обработчик (4) и в оконную процедуру вернется уже структура с измененным значением поля Result, которое в итоге отдается как результат обработки сообщения.

Исходя из вышеописанного алгоритма, можно сделать несколько выводов:

Во-первых, методы динамические. При этом не обязательно писать override или называть метод тем же именем и даже принимать структуру того же типа, размера и с тем же выравниванием полей, вызов inherited внутри message метода приведет к вызову message метода родителя с передачей туда структуры Message, либо, если у родительских классов нет обработки этого сообщения, к вызову DefaultHandler. Это играет очень важную роль при работе с VCL.

Во-вторых, message методы не обязательно должны принимать именно тип TMessage, описанный в модуле Messages. Достаточно того, чтобы структура имела первое поле типа DWORD, чтобы можно было осуществить переход по адресу, равному числовому значению этого поля. На остальные параметры структуры не налагается ограничений. VCL использует TMessage, поскольку все сообщения Windows, а так же пользовательские сообщения CN_BASE + xxx имеют одну (или сходную по размерам) структуру. Однако, структуру обязательно нужно передавать по ссылке.

Надо заметить, что на диапазон обрабатываемых сообщений налагается ограничение, а именно от 1 до 49151. Почему 49151? Потому что данный прием был введен прежде всего для обработки сообщений Windows, а в Windows номера сообщений от 1 до WM_USER-1 зарезервированы системой, от WM_USER до $7FFF — для пользовательских сообщений и от WM_APP до $C000-1 (49151) — для сообщений на уровне приложения. От $C000 до $FFFF идет диапазон строковых пользовательских сообщений уровня приложения, создаваемых через RegisterWindowMessage, результат вызова фунции невозможно предсказать на этапе компиляции, поэтому логику обработки подобных сообщений лучше делать в оконной процедуре. По поводу оконной процедуры также стоит отметить, что вначале сообщение идет в статический метод MainWndProc, а в нем уже идет безопасный вызов WndProc. Для того, чтобы Windows могла принять MainWndProc как оконную функцию, VCL использует функцию Classes.MakeObjectInstance. Функция возвращает адрес на процедуру, которую можно отдать Windows как оконную, и которая перенаправит все вызовы оконной функции в метод класса.

Inherited

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

Подводные камни

Не всегда в VCL можно решить задачу обработки сообщения исключительно переопределением message метода. Иногда это не приводит ни к какому результату, потому, что помимо обработки сообщения в message методе идет ее обработка и в WndProc. Яркий тому пример. Автору вопроса необходимо было запретить рисование системной стрелки в выпадающем меню. Но обработка WM_DRAWITEM не приводила ни к чему, потому что в WndProc состояние Canvas пункта меню возвращалось в исходное. Поэтому иногда все-таки приходится лезть в WndProc, хоть это и нехорошо :).

Message методы также являются одной из причин, по которой классический сабклассинг (то есть переопределение оконной процедуры окна через SetWindowLong) в Delphi крайне не рекомендуется. Одной из причин, как известно, является метод Perform у TControl, перенаправляющий сообщения напрямую в оконную процедуру. Если внутри кода класса будет вызов Perform, то будет вызван метод WndProc класса, а не фактическая оконная процедура окна (которая, как известно получается из приведения WndProc к виду, которому требует Windows через MakeObjectInstance). Однако и message методы тут не самые лучшие помощники — метод Dispatch направляет сообщение сразу в обработчик, а не в оконную процедуру. Поэтому проводя сабклассинг нужно быть готовым к подобным фокусам.

Если читатель знаком с WTL или MFC в си, то он явно заметил, что подобный механизм есть и там — через карты сообщений. Там имеется похожая проблема — если сообщение пришло через непосредственный вызов SendMessage, то эти сообщения обходят фильтры сообщений.

Собственная выгода

Итак, стало понятно как это работает в VCL. Однако использование message методов не ограничивается только лишь оконными (то есть обладающими хендлом) компонентами, и класс TControl тому подтверждение — все неоконные контролы способны обрабатывать сообщения. И оконный компонент ответственнен за перенаправление сообщений подчиненным неоконным контролам. Именно message методы сделали это возможным! Более того, их использование также не ограничивается только обработкой сообщений Windows — вы можете с таким же успехом обрабатывать свои сообщения, вооружившись знанием о том, как они работают. К примеру, у вас есть задача обмена некими данными между классами, однако заранее нельзя предугадать какими именно и как обрабатывать результат. Можно нагородить лес из кучи методов, предоставляющих возможность обмена разнотипными данными, а можно воспользоваться механизмом передачи сообщений. Например, задав такую структуру:

и 2 сообщения — передача строки и передача числа:

можно обмениваться строковыми и целочисленными данными между классами через message методы:

Опять же, взяв на вооружение механизмы в VCL, можно писать приложения на чистом винапи, не городя при этом многокилометровых case в оконных процедурах 😉 Или можно писать свой набор неоконных компонент и при этом спокойно обрабатывать оконные сообщения. Это раскрывает широкие возможности для разгула фантазии:)

Несколько слов о передаче строк и потоках

Если вы решились использовать подобный прием в своих проектах, то должны четко понимать что и как следует передавать. Рекоммендуется в структурах не использовать слишком много полей, желательно использовать только целые поля, чтобы передавать адреса передаваемых данных. Если вы передаете строку string, то помните про счетчик ссылок (очень хорошая статья об этом вот тут), если передаете PChar, то позаботьтесь о корректном выделении и освобождении памяти. Я бы порекомендовал WideString, поскольку в них нет счетчика ссылок.

При использовании message методов в отдельном потоке не забывайте про синхронизацию. Dispatch — это не SendMessage, при передаче сообщения классу в потоке с другим контекстом остановки потока не будет!

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

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

Источник

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

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

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

  • Процедура должна быть методом объекта
  • Процедуре должен передаваться один передаваемый по ссылке параметр, т.е. с помощью описания var. Тип параметра должен быть TMessage или другой, зависящий от типа специализированного сообщения
  • Описание процедуры должно включать ключевое слово message, за которым должна следовать константа, задающая тип обрабатываемого сообщения

Вот пример объявления процедуры, обрабатывающей сообщение WM_Paint

[соглашение по присвоению имён требует присваивать обработчику сообщения то же имя, что и имя обрабатываемого сообщения, но без символа подчёркивания и указанием первым знаков имени прописными буквами]

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

Для этого сначала нужно объявить процедуру в частных объявлениях (в области Private объекта TForm1):

Теперь в разделе implementation модуля добавляем определение процедуры (в этом случае указание ключевого слова message не требуется):

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

Источник

Основы работы с Windows API

Основы работы с Windows API — Сообщения Windows

Содержание материала

Сообщения Windows

Человеку, знакомому с Delphi, должна быть ясна схема событийного управления. Программист пишет только код реакции на какое-либо событие, а дальше программа ждёт, когда система сочтёт, что настало время передать управление этому участку кода. Простые программы в Delphi состоят исключительно из методов реакции на события вроде OnCreate, onclick, OnCloseQerry и т. д. Причём событием называется не только событие в обычном смысле этого слова, то есть когда происходит что-то внешнее, но и ситуация, когда событие используется просто для передачи управления основной программе в тех случаях, когда VCL не может сама справиться с какой-то задачей. Примером такого события является, например, TListBox.OnDrawItem. Устанавливая стиль списка в lbOwnerDrawFixed или lbOwnerDrawVariable, программист как бы сообщает VCL, что он не доволен теми средствами рисования элементов списка, которыми она располагает, и что он берёт эту часть задачи на себя. И каждый раз, когда возникает необходимость в рисовании элемента, VCL передаёт управление специально написанному коду. На самом деле разница между двумя типами событий весьма условна. Можно так же сказать, что когда пользователь нажимает клавишу, VCL не знает, что делать, и поэтому передаёт управление обработчику OnKeyPress.

Событийное управление не есть изобретение авторов Delphi. Такой подход исповедует сама система Windows. Только здесь события называются сообщениями (message), что, на мой взгляд, даже лучше отражает ситуацию. Windows посылает программе сообщения, связанные либо с тем, что произошло что-то внешнее (мышь, клавиатура. ), либо с тем, что самой системе потребовались от программы какие-то действия. Самым распространённым таким действием является предоставление информации. Например, когда Windows хочет узнать заголовок окна, она посылает этому окну специальное сообщение, в ответ на которое окно должно сообщить системе свой заголовок. Ещё бывают сообщения, которые просто уведомляют программу о начале какого-то действия (например, о начале перетаскивания окна) и предоставляют возможность вмешаться. Но это вмешательство необязательно.

В Delphi для реакции на каждое событие обычно создаётся свой метод. В Windows одна процедура, называемая оконной, обрабатывает все сообщения. В языке Си нет понятия , поэтому при использовании Паскаля может возникнуть путаница. Дело в том, что то, что называется оконной процедурой, на самом деле является функцией. Тем не менее, я буду использовать общепринятый термин . Каждое сообщение имеет свой уникальный номер, а оконная процедура обычно целиком состоит из оператора case, и каждому сообщению соответствует своя альтернатива этого оператора. Номера сообщений учить не надо, потому что можно использовать константы, описанные в модуле Messages.dcu. Эти константы начинаются с префикса, указывающего на принадлежность сообщения к какой-то группе. Например, сообщения общего назначения начинаются с WM_: например, WM_Paint, WM_GetTextLength. Сообщения, специфичные, например, для кнопок, начинаются с префикса BM_. Остальные группы сообщений также связаны либо с теми или иными элементами управления, либо со специальными действиями, например, с динамическим обменом данными (dynamic data exchange, DDE). Обычной программе приходится обрабатывать довольно много сообщений, поэтому оконная процедура бывает, как правило, очень длинной и громоздкой. Оконная процедура описывается программистом как callback функция и указывается при создании оконного класса. Таким образом все окна данного класса имеют одну и ту же оконную процедуру. Впрочем, существует возможность породить так называемый подкласс, то есть новый класс, наследующий все свойства существующего, за исключением оконной процедуры. Несколько подробнее об этом будет сказано далее.

Кроме номера, каждое сообщение содержит два параметра — WParam и LParam. Буквы и означают и , то есть первый параметр 16-разрядный, а второй — 32-разрядный. Однако так было только в старых, 16-разрядных версиях Windows. В 32-разрядных версиях оба параметра 32-разрядные, несмотря на их названия. Конкретный смысл каждого параметра зависит от сообщения. В некоторых сообщениях один или оба параметра могут вообще не использоваться, в других — наоборот, двух параметров даже не хватает. В этом случае один из параметров (обычно LParam) содержит указатель на дополнительные данные. После обработки сообщения оконная процедура должна вернуть какое-то значение. Обычно это значение просто сигнализирует, что сообщение не нуждается в дополнительной обработке, но в некоторых случаях оно более осмысленно, например, WM_SetIcon должно вернуть дескриптор иконки, которая была установлена ранее. Если программист не хочет обрабатывать сообщение самостоятельно, он должен вызвать для его обра-ботки функцию DefWindowProc.

Обработка сообщения требует времени, иногда довольно значительного. За это время окну может быть отправлено ещё несколько сообщений. Чтобы они не пропали, Windows организует так называемую очередь сообщений. Очередь сообщений своя для каждой нити. Нить должна сама выбирать сообщения из этой очереди, транслировать их и затем вызывать функцию Dispatch-Message, чтобы направить это сообщение в нужную оконную процедуру. Всё это лучше не писать самому, а оставить на совести VCL, которая прекрасно с этим справляется. При программировании в Delphi обычно требуется либо нестандартная реакция на сообщение, либо отправка сообщения другому окну.

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

Кроме параметров WParam и LParam, каждому сообщению приписывается время возникновения и координаты курсора в момент возникновения. Эти параметры можно узнать с помощью функций GetMessagePos и GetMessageTime.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

  • Delphi манифест windows 10
  • Delphi временная папка windows
  • Delphi windows имя пользователя
  • Delphi windows socket error 10057
  • Delphi manifest windows 10