如何检测Delphi类是否具有虚拟构造函数?

时间:2009-04-26 14:58:53

标签: delphi delphi-2009 rtti

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

   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 个答案:

答案 0 :(得分:4)

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

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

是的,这有点像黑客,但它会起作用。如果有人能找到更好的解决方案,那就更有力量了。

答案 1 :(得分:3)

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

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

唯一的解决方案是使用RTTI查找实际附加到类的构造函数。您可以获取“名为Create的方法”的方法指针,并在我在其他答案中解释的技巧中使用它。为此,必须将基础虚拟构造函数声明为已发布。这将强制所有覆盖它的方法也被发布。问题是,有人仍然可以使用重新引入; 来声明更高版本的非发布构造函数,并且您的方案崩溃了。你不知道后代类会做什么。

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

答案 2 :(得分: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.

回到原来的问题: 当类引用引用具有虚拟构造函数的基类时,您确信始终使用间接调用虚拟构造函数。 当类引用引用具有非虚构造函数的基类时,您确信始终使用直接调用来调用非虚构造函数。

希望这能为你的问题提供更多启示。

- 的Jeroen