Таблица виртуальных методов

Я использую этот блок:

procedure ExecMethod(Target: TClass; const MethodName: string; const Args: array of TValue);
var
  LContext: TRttiContext;
  LType: TRttiType;
  LMethod: TRttiMethod;
begin
  LType := LContext.GetType(Target);
  for LMethod in LType.GetMethods do
    if (LMethod.Parent = LType) and (LMethod.Name = MethodName) then begin
      LMethod.Invoke(Target.Create, Args);
      break;
    end;
end;

как это:

ExecMethod(TFuncClass, 'Test1', []);
ExecMethod(TFuncClass, 'Test2', ['hey']);
ExecMethod(TFuncClass, 'Test3', [100]);

в этом классе:

  TFuncClass = class(TObject)
    published
      procedure Test1;
      procedure Test2(const str: string);
      procedure Test3(i: integer);
      // there's more, each one with different prototype
  end;

var
  FuncClass: TFuncClass;

но потом я продолжаю получать нарушения прав доступа... или недопустимый класс указателя приведения (или любой другой)..

1 ответ

Решение

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

Код вопроса обобщает исходный код для работы с любым конкретным классом и при этом становится технически неверным. Чтобы понять почему, вам нужно понять, как Delphi создает объекты из ссылок на классы:

Когда вы вызываете конструктор для переменной-ссылки на класс (как в Target.Create), компилятор использует полученные знания, как во время компиляции, чтобы решить, какой конструктор вызвать. В этом случае целью вызова является TClassи единственный конструктор, который знает компилятор, доступен для этого типа TObject.Createэто конструктор, который называется. Если TFuncClass имеет некоторый другой конструктор - даже если он соответствует сигнатуре с нулевым аргументом, унаследованной от TObject - это никогда не называется. Тип созданного объекта все равно будет отображаться как TFuncClassхотя - то ClassType функция вернется TFuncClassи is Оператор будет работать как положено.

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

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

Было бы гораздо лучше, если бы вызывающая сторона отвечала за предоставление экземпляра для вызова методов, например так:

procedure ExecMethod(Target: TObject; const MethodName: string; const Args: array of TValue);
var
  LContext: TRttiContext;
  LType: TRttiType;
  LMethod: TRttiMethod;
begin
  LType := LContext.GetType(Target.ClassType);
  for LMethod in LType.GetMethods(MethodName) do
    // TODO: Beware of overloaded methods
    LMethod.Invoke(Target, Args);
end;

Используйте эту функцию так:

FuncClass := TFuncClass.Create(...);
try
  ExecMethod(FuncClass, 'Test1', []);
  ExecMethod(FuncClass, 'Test2', ['hey']);
  ExecMethod(FuncClass, 'Test3', [100]);
finally
  FuncClass.Free
end;

Обратите внимание, что все это предполагает, что второй параметр, строковое имя метода, фактически предоставляется некоторой переменной, значение которой неизвестно до времени выполнения. Если вы передаете строковый литерал в ExecMethodтогда вам следует прекратить звонить ExecMethodпрекратите возиться с RTTI и просто вызовите нужный метод напрямую: FuncClass.Test2('hey'),

Другие вопросы по тегам