【问题标题】:How can I detect if a Delphi class has a virtual constructor?如何检测 Delphi 类是否具有虚拟构造函数?
【发布时间】:2010-10-21 22:02:08
【问题描述】:

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

   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 中有效?

【问题讨论】:

  • ...如果您需要此信息,那么我认为您做错了...
  • 是的,确切地说,我想检查其他人的代码是否有问题。如果构造函数没有被声明为虚拟的,它就不会被调用,所以“可能会发生真正的坏事”。

标签: delphi delphi-2009 rtti


【解决方案1】:

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

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

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

【讨论】:

  • 我是否正确理解构造函数(如果声明为虚拟的)将在 VMT 中被引用?
  • 是的,和其他方法一样。
【解决方案2】:

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

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

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

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

【讨论】:

    【解决方案3】:

    迈克尔,

    我明白你的问题,但由于你的源代码无法编译,我认为你错过了问题的重点;-)

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

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

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

    希望这能更清楚地说明您的问题。

    --杰罗恩

    【讨论】:

    • 是的,这正是我在第二篇文章中试图表达的观点。很好的解释。当您在 ASM 中进行说明时,这些事情总是更有意义(至少对于了解实际情况的程序员而言)。 +1。
    • 谢谢。找到一个既有效又解释良好的简洁示例总是很困难:-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-16
    • 2012-04-13
    • 1970-01-01
    • 2010-09-09
    • 2015-12-11
    • 1970-01-01
    相关资源
    最近更新 更多