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

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

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

Ответить
mike
новенький
Сообщения: 55
Зарегистрирован: 23.02.2007 16:25:00

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

Сообщение mike »

Столкнулся со странным поведением 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, что без него.
v-t-l
энтузиаст
Сообщения: 744
Зарегистрирован: 13.05.2007 16:27:22
Откуда: Belarus

Сообщение v-t-l »

Код: Выделить всё

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

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

Так что, Вы скорее всего ее неправильно применяете.
Последний раз редактировалось v-t-l 26.08.2025 22:04:24, всего редактировалось 1 раз.
Аватара пользователя
Sharfik
энтузиаст
Сообщения: 836
Зарегистрирован: 20.07.2013 01:04:30

Сообщение Sharfik »

App.OnEvent := Addr(TClass.Proc);
Есть ситуации, в которых это поведение нормальное, или на баг напоролся? Компилятор из транка.
Это баг Delphi, что пропускает всякую хрень приучая людей писать грязный по факту код. FPC же носом тыкает, в каждую ошибку кодера.
mike
новенький
Сообщения: 55
Зарегистрирован: 23.02.2007 16:25:00

Сообщение mike »

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; ?
Аватара пользователя
WAYFARER
энтузиаст
Сообщения: 564
Зарегистрирован: 09.10.2009 00:00:04
Откуда: г. Курган

Сообщение WAYFARER »

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);
А вот при явном приведении вы принуждаете компилятор правильно генерировать методный указатель.
Вывод - так и надо делать что бы избежать проблем.
mike
новенький
Сообщения: 55
Зарегистрирован: 23.02.2007 16:25:00

Сообщение mike »

Ну, в моем случае @TClass.Proc и Addr(TClass.Proc) дают один результат -- ошибку в одном событии и нормальную работу в другом. Что уже крайне подозрительно. Я потом попробую создать минималистичное приложение с этой проблемой.
А вот TNotifyEvent(@TClass.Proc) нормально работает в обоих событиях.
sts
энтузиаст
Сообщения: 519
Зарегистрирован: 04.04.2008 12:15:44
Откуда: Тольятти

Сообщение sts »

я думаю тут проблема в чемто другом, в делфе предусмотрена поддержка procedure of object и для class procedure, т.е. чтобы это работало надо по разному формировать ссылку на метод и это случайно не получится. в fpc должно быть также, смущает @, может нужен режим делфи чтоб также работало?
mike
новенький
Сообщения: 55
Зарегистрирован: 23.02.2007 16:25:00

Сообщение mike »

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 тоже попадается, откуда я данный прием и взял в свое время.
Ответить