Bazaprogram.ru

Новости из мира ПК
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Wm user delphi

Wm user delphi

Перелистывал книгу «Delphi 5. Руководство разработчика», наткнулся на такую фразу:
Сообщение должно иметь идентификатор в диапазоне от WM_USER+100 до $7FFFF .

Почему от WM_USER+100, а не 200 или 300? Для чего тогда зарезервированы идентификаторы от WM_USER до WM_USER+99.
Я всегда пользовался идентификаторами от WM_USER+1 и т.д. и никаких проблем не было.


Игорь Шевченко ( 2002-11-06 14:49 ) [1]

А полнее цитату нельзя привести ?


LongIsland ( 2002-11-06 14:49 ) [2]

А там ошибки нет? Кажется позавчера читал несколько другое.


MBo ( 2002-11-06 14:52 ) [3]

В этом диапазоне в исходниках описано несколько редких сообщений. Правда, встречается и WM_USER+100 и WM_USER+101


Игорь Шевченко ( 2002-11-06 14:54 ) [4]

Для работы со своими окнами можно использовать от WM_USER до $1FFFF.


TTCustomDelphiMaster ( 2002-11-06 15:06 ) [5]

MBo © (06.11.02 14:52)
Ага точно. Посмотрел исходники куча разных компонент активно используют сообщения от WM_USER+1 до WM_USER+200. Странно что об этом нигде не сказано 🙁

Игорь Шевченко © (06.11.02 14:49)
А там больше ничего интересного нет

LongIsland © (06.11.02 14:49)
WM_USER+100 написано в 2 местах, в тексте и примере. Думаю что с переводом числа 100 с англиского ошибки вознкнуть не могло


TTCustomDelphiMaster ( 2002-11-06 15:19 ) [6]

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

Игорь Шевченко © (06.11.02 14:54)
Range Meaning
0 through WM_USER — 1 Messages reserved for use by Windows.
WM_USER through 0x7FFF Integer messages for use by private window classes.
0x8000 through 0xBFFF Messages reserved for future use by Windows.
0xC000 through 0xFFFF String messages for use by applications.
Greater than 0xFFFF Reserved by Windows for future use.


Alex4444444444 ( 2002-11-06 15:25 ) [7]

Delo v tom, chto Delphi sam ispol»zuet ogromnoe colichestvo soobshenij (sm. Controls.pas). Navernoe, WM_USER..WM_USER + $100 zareservirovany dlya Delphi (no ne dlya Windows).


Игорь Шевченко ( 2002-11-06 15:33 ) [8]

TTCustomDelphiMaster © (06.11.02 15:19)

Про компоненты: надо учесть, что имеются сообщения вида CM_xxx и CN_xxx и не пересекаться с ними при описании собственных сообщений.


TTCustomDelphiMaster ( 2002-11-06 19:40 ) [9]

Теперь понятно почему нужно использовать идентификаторы от WM_USER+100. Достаточно поместить на форму RichEdit.

procedure TForm1.Button1Click(Sender: TObject);
var
Msg: TMessage;
begin
with Msg do
begin
Msg := WM_USER + 67;
WParam :=0;
LParam :=$FF;
Result :=0;
end;
BroadCast(Msg);
end;

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 приобретает более удобную для восприятия и модификации форму. Вместо:

Читать еще:  Задачи по css

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

После выборки сообщения из очереди, оно направляетя в оконную процедуру (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 направляет сообщение сразу в обработчик, а не в оконную процедуру. Поэтому проводя сабклассинг нужно быть готовым к подобным фокусам.

Читать еще:  Как сделать таблицу ссылкой в css

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

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

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

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

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

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

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

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

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

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

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

How to send and handle a custom Windows message

These notes apply to Delphi running on a Microsoft Windows platform.

Delphi allows you to define your own messages when can then be posted on the Windows message queue and then handled by your own custom handler in your application.

The notes here show how to:

Define your own custom message

Each message must have a unique id. The constant WM_USER is defined (in the Messages unit) as the first message number available for application use.

These messages should be defined in the interface section of the unit containing the form that will be handling them. Alternately, to guarantee that all messages are unique within an application consider defining all your messages together in a single unit.

  • WM_USER is fine for internal application messages.
  • Use WM_APP for messages between applications.
  • Use the RegisterMessage API call if you need a message number that is guaranteed to be unique in a system.

Define a handler for the message

To add a message handler to a form, define a procedure that takes an argument of type TMessage and add the directive » message » with the message id that the procedure is to handle.

type
TMyForm = class(TForm)
.
.
.
private
procedure OnMyMessage(var Msg: TMessage); message WM_MY_MESSAGE;
procedure OnAnoMessage(var Msg: TMessage); message WM_ANO_MESSAGE;
.
.

Send the message

To send a message use either:

PostMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): BOOL;

SendMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): BOOL;

both of which are defined in the Windows unit. Both of these will append the message to the end of the application’s Windows message queue. The difference between them is that SendMessage will not return until the message has been handled, whereas PostMessage will return straight away.

For example, for a form to send itself one of the messages defined above:

Pass data via messages

The additional parameters wParam and lParam provide a mechanism to pass some additional information along with the message. Both of these parameters are defined as type Integer . At the recipient end the values can be extracted from the message by simply referencing the WParam and LParam members of TMessage .

Читать еще:  Setwindowpos delphi пример

Note: The technique described here is suited for sending data from a thread to the main process (or form) of the application.

The wParam and lParam members are ideally suited for passing integer data, but can also be used to pass objects, for example:

and at the recipient end:

procedure TMyForm.OnAnoMessage(var Msg: TMessage);
var
myObject: TMyClass;
begin
myObject := TMyClass(msg.WParam);
.
.

The things to be aware of if you use this approach:

  1. It can only be used if you are sending messages within the same process — because memory in the sending process cannot be accessed by other processes.
  2. If you do send messages within the same process (and are using SendMessage ) then only use the technique to send messages from a separate Thread.
  3. You must ensure that the pointer being sent is still valid when it is used by the recipient. A simple approach is to use SendMessage rather than PostMessage . Consider if you use PostMessage and then free the object being sent, the recipient may pick up the reference to the object after it has been freed — at best this will be a source of difficult to find bugs, at worst it may crash your program.

These notes are believed to be correct for Delphi 6 and Delphi 7 running under Microsoft Windows.

About the author: Brian Cryer is a dedicated software developer and webmaster. For his day job he develops websites and desktop applications as well as providing IT services. He moonlights as a technical author and consultant.

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

Основы работы с Windows API — Сообщения, определяемые пользователем

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

Сообщения, определяемые пользователем

Использование сообщений очень удобно в тех случаях, когда нужно заставить окно выполнить какое-то действие. Поэтому Windows предоставляет возможность программисту создавать свои сообщения, которые могут быть локальными или глобальными. Использование локальных сообщений связано с некоторым риском. Дело в том, что эти сообщения должны посылаться только окнам, то есть тем, оконные процедуры которых написаны так, чтобы правильно интерпретировать это сообщение. Если послать такое сообщение окну, его реакция может быть непредсказуемой, потому что человек, писавший его оконную процедуру, мог использовать сообщение с этим же номером для своих целей. Всё это вовсе не значит, что обмен локальными сообщениями возможен только внутри одной программы: если разные программы написаны так, что они правильно понимают одно и то же локальное сообщение, они могут без каких-либо ограничений обмениваться им. Немного повторюсь: важно только чтобы отправитель и получатель сообщения одинаково понимали его. В справочной системе специально указывается, что недопустимо отправлять такие сообщения окнам классов ‘BUTTON’, ‘EDIT’, ‘LISTBOX’ и ‘COMBOBOX’.

В Windows (и, соответственно, в модуле Messages.dcu) определена специальная константа WM_User, равная $400 (1024). Впрочем, нет гарантии, что в следующих версиях Windows значение этой константы не изменится. Номера стандартных сообщений лежат в диапазоне от 0 до WM_User-1. Для локальных пользовательских сообщений оставлен диапазон от WM_User до $7FFF (32767). Забегая чуть вперёд, скажу, что для глобальных пользовательских сообщений оставлен диапазон от $C000 до $FFFF (от 49152 до 65535).

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

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

Неудобство использования таких сообщений очевидно — их номера определяются только после начала выполнения программы, при компиляции они ещё неизвестны. Поэтому обработка таких сообщений описанным ранее методом невозможна — мы не знаем, какой номер писать после слова message. Здесь может помочь виртуальный метод WndProc, имеющийся в классе TControl (и в TForm как в его потомке). Этот метод получает все сообщения, поступающие окну. Если перекрыть этот метод, то ничего не мешает сравнивать внутри него номер пришедшего и определённого пользователем сообщения. Например, так:

var WM_MyUserDemoMessage: Cardinal;

procedure TForm1.FormCreate(Sender: TObject);

WM_MyUserDemoMessage := RegisterWndowMessage( ‘WM_MyUserDemoMessage’ )

procedure TForm1.WndProc( var Message : TMessage);

Ссылка на основную публикацию
Adblock
detector