class procedure в качестве TNotifyEvent

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

class procedure в качестве TNotifyEvent

Сообщение mike » 26.08.2025 16:08:10

Столкнулся со странным поведением FPC, которого никогда не наблюдал в аналогичных ситуациях в Delphi.

Есть некий объект App с событием OnEvent: TNotifyEvent, есть класс-заглушка со статичным методом вида class procedure TClass.Proc(Sender: TObject);.
Если сделать так:
Код: Выделить всё
App.OnEvent := @TClass.Proc;

То в момент вызова OnEvent внутри App вылетает AV с попыткой чтения по адресу $FFFFFFFFFFFFFFFF;
Но если сделать так:
Код: Выделить всё
App.OnEvent := TNotifyEvent(@TClass.Proc);

То потом все работает, метод вызывается без проблем.

Есть ситуации, в которых это поведение нормальное, или на баг напоролся? Компилятор из транка.

Добавлено спустя 11 минут 46 секунд:
Забыл добавить, что в этом же App есть еще одно событие типа TNotifyEvent, которому я присваиваю тот же обработчик @TClass.Proc и он работает нормально что с приведением к TNotifyEvent, что без него.
mike
новенький
 
Сообщения: 53
Зарегистрирован: 23.02.2007 17:25:00

Re: class procedure в качестве TNotifyEvent

Сообщение v-t-l » 26.08.2025 21:30:02

Код: Выделить всё
type TNotifyEvent = procedure(Sender: TObject) of object;


procedure of object означает, что в процедуру первым неявным параметром передается Self - указатель на экземпляр класса, а затем остальные (в данном случае Sender).

В class procedure такой параметр отсутствует, потому что такие процедуры вызываются без создания экземпляра.

Так что, Вы скорее всего ее неправильно применяете.
Последний раз редактировалось v-t-l 26.08.2025 23:04:24, всего редактировалось 1 раз.
v-t-l
энтузиаст
 
Сообщения: 744
Зарегистрирован: 13.05.2007 16:27:22
Откуда: Belarus

Re: class procedure в качестве TNotifyEvent

Сообщение Sharfik » 26.08.2025 21:48:52

App.OnEvent := Addr(TClass.Proc);

Есть ситуации, в которых это поведение нормальное, или на баг напоролся? Компилятор из транка.

Это баг Delphi, что пропускает всякую хрень приучая людей писать грязный по факту код. FPC же носом тыкает, в каждую ошибку кодера.
Аватара пользователя
Sharfik
энтузиаст
 
Сообщения: 814
Зарегистрирован: 20.07.2013 01:04:30

Re: class procedure в качестве TNotifyEvent

Сообщение mike » 27.08.2025 01:32:31

v-t-l писал(а):procedure of object означает, что в процедуру первым неявным параметром передается Self - указатель на экземпляр класса, а затем остальные (в данном случае Sender).
В class procedure такой параметр отсутствует, потому что такие процедуры вызываются без создания экземпляра.

В class procedure (без static, как у меня в примере) тоже есть Self, и в него тем же способом (через регистр EBX/RBX) передается ссылка на тип "своего" класса: Methods_(Delphi).
Так что по фактической сигнатуре статический и "обычный" методы совместимы. И если не обращаться к Self (что в таком классе все равно лишено всякого смысла), то такой метод прекрасно работает обработчиком события.
Данный прием используется и в стандартных библиотеках, когда нужно назначить stand-alone обработчик с минимальной обвязкой.

Добавлено спустя 1 минуту 36 секунд:
Sharfik писал(а):App.OnEvent := Addr(TClass.Proc);

А чем это отличается от App.OnEvent := @TClass.Proc; ?
mike
новенький
 
Сообщения: 53
Зарегистрирован: 23.02.2007 17:25:00

Re: class procedure в качестве TNotifyEvent

Сообщение WAYFARER » 27.08.2025 08:24:16

mike писал(а):А чем это отличается от App.OnEvent := @TClass.Proc; ?

@ возвращает указатель на процедуру/функцию. Если речь идёт о методе класса (статическом методе, без скрытого параметра Self), то @TClass.Proc даст адрес именно этой процедуры.
Если же Proc метод экземпляра (обычный procedure Proc; в классе, требующий Self), то @Obj.Proc вернёт TMethod-структуру (где хранится пара Code + Data).
Addr() же возвращает голый адрес кода (указатель на функцию), а посему не подходит.

Скорее всего компилятор формирует некорректный TMethod (поле Data оказывается мусором типа $FFFFFFFFFFFFFFFF), из-за чего вызов через событие падает.
Сам натыкался на это много раз, где-то неявное преобразование срабатывает корректно, где-то — нет. Скорее всего это баг транковой версии.

Код: Выделить всё
App.OnEvent := TNotifyEvent(@TClass.Proc);

А вот при явном приведении вы принуждаете компилятор правильно генерировать методный указатель.
Вывод - так и надо делать что бы избежать проблем.
Аватара пользователя
WAYFARER
энтузиаст
 
Сообщения: 550
Зарегистрирован: 09.10.2009 00:00:04
Откуда: г. Курган

Re: class procedure в качестве TNotifyEvent

Сообщение mike » 27.08.2025 13:24:12

Ну, в моем случае @TClass.Proc и Addr(TClass.Proc) дают один результат -- ошибку в одном событии и нормальную работу в другом. Что уже крайне подозрительно. Я потом попробую создать минималистичное приложение с этой проблемой.
А вот TNotifyEvent(@TClass.Proc) нормально работает в обоих событиях.
mike
новенький
 
Сообщения: 53
Зарегистрирован: 23.02.2007 17:25:00

Re: class procedure в качестве TNotifyEvent

Сообщение sts » 27.08.2025 14:11:12

я думаю тут проблема в чемто другом, в делфе предусмотрена поддержка procedure of object и для class procedure, т.е. чтобы это работало надо по разному формировать ссылку на метод и это случайно не получится. в fpc должно быть также, смущает @, может нужен режим делфи чтоб также работало?
sts
постоялец
 
Сообщения: 481
Зарегистрирован: 04.04.2008 12:15:44
Откуда: Тольятти

Re: class procedure в качестве TNotifyEvent

Сообщение mike » 27.08.2025 15:25:43

sts писал(а):чтобы это работало надо по разному формировать ссылку на метод и это случайно не получится

Вот из исходников самого Лазаруса фрагмент, файл colortty.pas:
Код: Выделить всё
type
  TColorTTY = class
    class procedure DoLazLoggerDebugLnEx({%H-}Sender: TObject; var LogTxt, {%H-}LogIndent: string;
      var {%H-}Handled: Boolean; const AnInfo: TLazLoggerWriteExEventInfo);
  end;

class procedure TColorTTY.DoLazLoggerDebugLnEx(Sender: TObject; var LogTxt, LogIndent: string;
  var Handled: Boolean; const AnInfo: TLazLoggerWriteExEventInfo);
var
  ...
begin
  ...
end;

initialization
  DebugLogger.OnDebugLnEx := @TColorTTY.DoLazLoggerDebugLnEx;
end.

Где:
Код: Выделить всё
TLazLoggerWriteExEvent = procedure(Sender: TObject; var LogTxt, LogIndent: string; var Handled: Boolean; const AnInfo: TLazLoggerWriteExEventInfo) of object;
...
property  OnDebugLnEx: TLazLoggerWriteExEvent


И такое там не в одном месте. В стандрантных библиотеках Delphi тоже попадается, откуда я данный прием и взял в свое время.
mike
новенький
 
Сообщения: 53
Зарегистрирован: 23.02.2007 17:25:00


Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: Google [Bot] и гости: 226

Рейтинг@Mail.ru