2

例如,有没有办法找出这个类有一个虚拟构造函数(在运行时)?

   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?

4

3 回答 3

4

通过 TypInfo 单元,看起来没有任何方法可以判断一个方法是否使用 RTTI 是虚拟的。但是,如果您有类引用,您可能可以通过检查 VMT 来滚动您自己的方法。

根据 Allen Bauer 的说法,在对这个问题的回答中,您可以在 vmtClassName 指向的值之前立即找到 VMT 的结尾。第一个用户定义的虚拟方法(如果有)位于类引用的地址。换句话说,pointer(Clazz)^. 既然您知道了 VMT 的用户定义部分的起点和终点,那么创建一个 while 循环将表中的每个指针与指向 Clazz.create 的方法指针的 Code 部分进行比较应该不会太难强制转换为 TMethod。如果你得到一个匹配,那么它是一个虚拟方法。如果不是,那就不是。

是的,这有点小技巧,但它会起作用。如果有人能找到更好的解决方案,他们就会有更多的权力。

于 2009-04-26T15:24:57.467 回答
3

你知道,我想得越多,我就越不喜欢我给出的最终被接受的答案。问题是,编写的代码只能处理编译时已知的信息。如果将 Clazz 定义为 TClass,那么将 Clazz.Create 放在 TMethod 中总是会给您一个指向 TObject.Create 的方法指针。

您可以尝试将 Clazz 定义为“TMyClass 的类”。问题是,你已经有了一个虚拟构造函数,所以它将为你提供它可以达到的最高级别的构造函数,它会覆盖该构造函数。但是从您的评论来看,您试图找到的是一个非虚拟构造函数(使用reintroduce;),它会破坏您的虚拟构造。您很可能正在使用工厂模式,这可能是一个问题。

唯一的解决方案是使用 RTTI 来查找实际附加到类的构造函数。您可以获得“名为 Create 的方法”的方法指针,并在我在其他答案中解释的技巧中使用它。为此,您的基本虚拟构造函数必须声明为已发布。这将强制所有覆盖它的方法也被发布。问题是,仍然有人可以使用reintroduce;在更高的地方声明一个未发布的构造函数,你的计划就会崩溃。您无法保证后代类会做什么。

这个问题没有技术解决方案。唯一真正有效的是教育。你的用户需要知道这个类是由一个工厂实例化的(或者你需要虚拟构造函数的任何原因),如果他们在派生类中重新引入构造函数,它可能会破坏事情。为此在文档中添加注释,并在源代码中添加注释。这几乎就是你能做的。

于 2009-04-26T20:05:07.263 回答
2

迈克尔,

我得到你的问题,但由于你的源代码没有编译,我认为你错过了你的问题的重点;-)

我的回答稍微详细说明了梅森在第二个回答中试图解释的内容。

手头的问题是您的问题暗示您有一个“类引用”(如 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.

回到你原来的问题:当你的类引用引用了一个具有虚拟构造函数的基类时,你确信你总是会使用间接调用一个虚拟构造函数。当您的类引用引用具有非虚拟构造函数的基类时,您肯定会始终使用直接调用来调用非虚拟构造函数。

希望这对您的问题有更多的了解。

——杰伦

于 2009-04-27T14:51:48.507 回答