Как я могу определить, есть ли в классе Delphi виртуальный конструктор? - PullRequest
2 голосов
/ 26 апреля 2009

Например, есть ли способ узнать, что у этого класса есть виртуальный конструктор (во время выполнения)?

   TMyClass = class(TObject)
     MyStrings: TStrings;
     constructor Create; virtual;   
   end;

Например, в этом коде я хотел бы проверить, имеет ли класс, на который ссылается Clazz, виртуальный конструктор:

procedure Test;
var
  Clazz: TClass;
  Instance: TObject;
begin
  Clazz := TMyClass;
  Instance := Clazz.Create;
end;

Есть ли простое решение, например, использование RTTI, которое работает в Delphi 6 до 2009?

Ответы [ 3 ]

4 голосов
/ 26 апреля 2009

Просматривая блок TypInfo, не видно, что можно определить, является ли метод виртуальным с использованием RTTI или нет. Однако, если у вас есть ссылка на класс, вы, вероятно, можете применить свой собственный метод, изучив VMT.

Согласно Аллену Бауэру, в ответе на этот вопрос вы можете найти конец VMT непосредственно перед значением, на которое указывает vmtClassName. Первый пользовательский виртуальный метод (если есть) находится по адресу ссылки на класс. Другими словами, pointer(Clazz)^. Теперь, когда вы знаете начальную и конечную точки пользовательского раздела VMT, не составит труда создать цикл while, который сравнивает каждый указатель в таблице с разделом Code указателя метода на Clazz.create. приведен к методу. Если вы получаете совпадение, то это виртуальный метод. Если нет, то это не так.

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

3 голосов
/ 27 апреля 2009

Знаете, чем больше я об этом думаю, тем меньше мне нравится ответ, который я дал, и в итоге меня приняли. Проблема в том, что написанный код может иметь дело только с информацией, известной во время компиляции. Если Clazz определен как TClass, то помещение Clazz.Create в TMethod всегда даст вам указатель на метод TObject.Create.

Вы можете попытаться определить Clazz как "класс TMyClass". Дело в том, что у вас уже есть виртуальный конструктор, поэтому он предоставит вам конструктор самого высокого уровня, которого он может достичь, который переопределяет этот конструктор. Но из ваших комментариев видно, что вы пытаетесь найти не виртуальный конструктор (использующий reintroduce; ), который сломает вашу виртуальную конструкцию. Скорее всего, вы используете фабричный шаблон, где это может быть проблемой.

Единственное решение для этого - использовать RTTI, чтобы найти конструктор, который действительно присоединен к классу. Вы можете получить указатель метода для «метода с именем Create» и использовать его в приеме, который я объяснил в моем другом ответе. Для этого ваш базовый виртуальный конструктор должен быть объявлен опубликован . Это заставит все методы, которые переопределяют это, быть также опубликованным. Проблема в том, что кто-то все еще может использовать reintroduce; , чтобы объявить неопубликованный конструктор выше, и ваша схема рухнет на землю. У вас нет никаких гарантий относительно того, что будут делать классы-потомки.

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

2 голосов
/ 27 апреля 2009

Michael

Я получил ваш вопрос, но так как ваш исходный код не компилируется, я думаю, вы упустили смысл вашего вопроса; -)

Мой ответ немного сложнее того, что Мейсон пытался объяснить в своем втором ответе.

Проблема в том, что ваш вопрос подразумевает, что у вас есть «ссылка на класс» (например, TClass или TComponentClass), которая ссылается на базовый класс, имеющий виртуальный конструктор. Тем не менее, TClass не (TClass ссылается на класс, который имеет не виртуальный конструктор), а TComponentClass делает.

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

  • вызов виртуального конструктора имеет косвенное указание
  • вызов не виртуального конструктора делает прямой вызов

Эта разборка показывает, что я имею в виду:

TestingForVirtualConstructor.dpr.37: ComponentClassReference := TMyComponentClass;
00416EEC A1706D4100       mov eax,[$00416d70]
TestingForVirtualConstructor.dpr.38: Instance := ComponentClassReference.Create(nil); // virtual constructor
00416EF1 33C9             xor ecx,ecx
00416EF3 B201             mov dl,$01
00416EF5 FF502C           call dword ptr [eax+$2c]
TestingForVirtualConstructor.dpr.39: Instance.Free;
00416EF8 E8CFCDFEFF       call TObject.Free
TestingForVirtualConstructor.dpr.41: ClassReference := TMyClass;
00416EFD A1946E4100       mov eax,[$00416e94]
TestingForVirtualConstructor.dpr.42: Instance := ClassReference.Create(); // non-virtual constructor
00416F02 B201             mov dl,$01
00416F04 E893CDFEFF       call TObject.Create
TestingForVirtualConstructor.dpr.43: Instance.Free;
00416F09 E8BECDFEFF       call TObject.Free

Поэтому, когда у вас есть переменная ссылки на класс типа, для которой конструктор является виртуальным, и вы вызываете этот конструктор через эту переменную, вы уверены, что фактический класс в этой переменной будет иметь виртуальный конструктор.

Вы не можете определить, на каком фактическом классе реализован этот конструктор (ну, не без дополнительной информации об отладке, например, из .DCU, .MAP, .JDBG или других источников).

Вот пример кода, который компилируется:

program TestingForVirtualConstructor;

{$APPTYPE CONSOLE}

uses
  Classes, SysUtils;

type
  TMyComponentClass = class(TComponent)
    MyStrings: TStrings;
    constructor Create(Owner: TComponent); override;
  end;

constructor TMyComponentClass.Create(Owner: TComponent);
begin
  inherited;
end;

type
  TMyClass = class(TObject)
    MyStrings: TStrings;
    constructor Create();
  end;

constructor TMyClass.Create();
begin
  inherited;
end;

procedure Test;
var
  // TComponentClass has a virtual constructor
  ComponentClassReference: TComponentClass;
  ClassReference: TClass;
  Instance: TObject;
begin
  ComponentClassReference := TMyComponentClass;
  Instance := ComponentClassReference.Create(nil); // virtual constructor
  Instance.Free;

  ClassReference := TMyClass;
  Instance := ClassReference.Create(); // non-virtual constructor
  Instance.Free;
end;

begin
  try
    Test;
  except
    on E: Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

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

Надеюсь, это прояснит ваш вопрос.

- Йерун

...