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

时间:2022-09-13 21:07:49

For example, is there a way to find out that this class has a virtual constructor (at runtime)?

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

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

For example, in this code I would like to test if the class referenced by Clazz has a virtual constructor:

例如,在这段代码中,我想测试Clazz引用的类是否有一个虚构造函数:

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

Is there a simple solution, for example using RTTI, which works in Delphi 6 to 2009?

有一个简单的解决方案,例如使用RTTI,它在Delphi 6到2009中有效吗?

3 个解决方案

#1


Looking through the TypInfo unit, it doesn't look like there's any way to tell if a method is virtual using RTTI or not. If you have a class reference, though, you can probably roll your own method by examining the VMT.

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

According to Allen Bauer, in an answer to this question, you can find the end of the VMT immediately before the value pointed to by vmtClassName. The first user-defined virtual method (if any) is found at the address of the class reference. In other words, pointer(Clazz)^. Now that you know the start and end points of the user-defined section of the VMT, it shouldn't be too difficult to make a while loop that compares each pointer in the table against the Code section of a method pointer to Clazz.create casted to a TMethod. If you get a match, then it's a virtual method. If not, then it isn't.

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

Yes, it's a bit of a hack, but it'll work. If anyone can find a better solution, more power to them.

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

#2


You know, the more I think about it, the less I like the answer I gave that ended up getting accepted. The problem is, the code as written can only deal with information known at compile-time. If Clazz is defined as a TClass, then putting Clazz.Create in a TMethod is always going to give you a method pointer to TObject.Create.

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

You could try defining Clazz as a "class of TMyClass". Thing is, you've already got a virtual constructor there, so it's going to give you the highest-level constructor it can reach that overrides that constructor. But from your comments, it looks like what you're trying to find is a non-virtual constructor (using reintroduce;) that will break your virtual construction. Most likely you're using a factory pattern, where this could be an issue.

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

The only solution to that is to use RTTI to find the constructor that's actually attached to the class. You can get a method pointer for "the method named Create" and use it in the trick I explained in my other answer. To do this, your base virtual constructor has to be declared published. This will force all methods that override it to also be published. Problem is, someone can still use reintroduce; to declare a non-published constructor higher up, and your scheme comes crashing to the ground. You don't have any guarantees about what descendant classes will do.

唯一的解决方案是使用RTTI来查找实际附加到类的构造函数。您可以获取“名为Create的方法”的方法指针,并在我在其他答案中解释的技巧中使用它。为此,必须声明基础虚拟构造函数已发布。这将强制所有覆盖它的方法也被发布。问题是,有人仍然可以使用重新引入;声明一个未发布的构造函数更高,你的方案崩溃了。您对后代类将执行的操作没有任何保证。

There's no technical solution to this question. The only thing that really works is education. Your users need to know that this class is instantiated by a factory (or whatever your reason is for needing a virtual constructor) and that if they reintroduce the constructor in a derived class, it could break things. Put a note in the documentation to this effect, and a comment in the source code. That's pretty much all you can do.

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

#3


Michael,

I get your question, but since your sourcecode does not compile, I think you miss the point of your question ;-)

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

My answer is a bit of an elaboration on what Mason tried to explain in his second answer.

我的回答是对梅森在第二个答案中试图解释的内容的一点阐述。

The issue at hand is that your question imples that you have a 'class reference' (like TClass or TComponentClass) that references to a base class that has a virtual constructor. However, TClass doesn't (TClass references a class that has a non-virtual constructor), but TComponentClass does.

手头的问题是你的问题实现了你有一个'类引用'(如TClass或TComponentClass)引用具有虚构造函数的基类。但是,TClass不会(TClass引用具有非虚构造函数的类),但TComponentClass会这样做。

You see the difference when disassembling the call to the constructor by using a class reference. When you call a virtual constructor through a class reference, the code is slightly different than when you call a non-virtual constructor:

通过使用类引用反汇编对构造函数的调用时,您会看到不同之处。通过类引用调用虚构造函数时,代码与调用非虚构造函数时略有不同:

  • calling a virtual constructor has an indirection
  • 调用虚拟构造函数具有间接性

  • calling a non-virtual constructor does a direct call
  • 调用非虚构造函数会直接调用

This disassembly shows what I mean:

这个反汇编显示了我的意思:

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

So when you have a variable of type class reference for which the constructor is virtual, and you call that constructor through that variable, you are sure that the actual class in that variable will have a virtual constructor.

因此,如果您有一个构造函数为虚拟类型类引用的变量,并且您通过该变量调用该构造函数,那么您确定该变量中的实际类将具有虚拟构造函数。

You can not determine on which actual class that constructor is implemented (well, not without extra debugging info, for instance from the .DCU, .MAP, .JDBG, or other sources).

您无法确定构造函数实现的实际类(嗯,不是没有额外的调试信息,例如来自.DCU,.MAP,.JDBG或其他来源)。

Here is the example code that does compile:

以下是编译的示例代码:

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.

To get back to your original question: When your class reference references a base class having a virtual constructor, you are sure that you will always call a virtual constructor using an indirection. When your class reference references a base class having a non-virtual constructor, you are sure that you will always call a non-virtual constructor using a direct call.

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

Hope this sheds some more light on your question.

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

--jeroen

#1


Looking through the TypInfo unit, it doesn't look like there's any way to tell if a method is virtual using RTTI or not. If you have a class reference, though, you can probably roll your own method by examining the VMT.

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

According to Allen Bauer, in an answer to this question, you can find the end of the VMT immediately before the value pointed to by vmtClassName. The first user-defined virtual method (if any) is found at the address of the class reference. In other words, pointer(Clazz)^. Now that you know the start and end points of the user-defined section of the VMT, it shouldn't be too difficult to make a while loop that compares each pointer in the table against the Code section of a method pointer to Clazz.create casted to a TMethod. If you get a match, then it's a virtual method. If not, then it isn't.

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

Yes, it's a bit of a hack, but it'll work. If anyone can find a better solution, more power to them.

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

#2


You know, the more I think about it, the less I like the answer I gave that ended up getting accepted. The problem is, the code as written can only deal with information known at compile-time. If Clazz is defined as a TClass, then putting Clazz.Create in a TMethod is always going to give you a method pointer to TObject.Create.

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

You could try defining Clazz as a "class of TMyClass". Thing is, you've already got a virtual constructor there, so it's going to give you the highest-level constructor it can reach that overrides that constructor. But from your comments, it looks like what you're trying to find is a non-virtual constructor (using reintroduce;) that will break your virtual construction. Most likely you're using a factory pattern, where this could be an issue.

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

The only solution to that is to use RTTI to find the constructor that's actually attached to the class. You can get a method pointer for "the method named Create" and use it in the trick I explained in my other answer. To do this, your base virtual constructor has to be declared published. This will force all methods that override it to also be published. Problem is, someone can still use reintroduce; to declare a non-published constructor higher up, and your scheme comes crashing to the ground. You don't have any guarantees about what descendant classes will do.

唯一的解决方案是使用RTTI来查找实际附加到类的构造函数。您可以获取“名为Create的方法”的方法指针,并在我在其他答案中解释的技巧中使用它。为此,必须声明基础虚拟构造函数已发布。这将强制所有覆盖它的方法也被发布。问题是,有人仍然可以使用重新引入;声明一个未发布的构造函数更高,你的方案崩溃了。您对后代类将执行的操作没有任何保证。

There's no technical solution to this question. The only thing that really works is education. Your users need to know that this class is instantiated by a factory (or whatever your reason is for needing a virtual constructor) and that if they reintroduce the constructor in a derived class, it could break things. Put a note in the documentation to this effect, and a comment in the source code. That's pretty much all you can do.

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

#3


Michael,

I get your question, but since your sourcecode does not compile, I think you miss the point of your question ;-)

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

My answer is a bit of an elaboration on what Mason tried to explain in his second answer.

我的回答是对梅森在第二个答案中试图解释的内容的一点阐述。

The issue at hand is that your question imples that you have a 'class reference' (like TClass or TComponentClass) that references to a base class that has a virtual constructor. However, TClass doesn't (TClass references a class that has a non-virtual constructor), but TComponentClass does.

手头的问题是你的问题实现了你有一个'类引用'(如TClass或TComponentClass)引用具有虚构造函数的基类。但是,TClass不会(TClass引用具有非虚构造函数的类),但TComponentClass会这样做。

You see the difference when disassembling the call to the constructor by using a class reference. When you call a virtual constructor through a class reference, the code is slightly different than when you call a non-virtual constructor:

通过使用类引用反汇编对构造函数的调用时,您会看到不同之处。通过类引用调用虚构造函数时,代码与调用非虚构造函数时略有不同:

  • calling a virtual constructor has an indirection
  • 调用虚拟构造函数具有间接性

  • calling a non-virtual constructor does a direct call
  • 调用非虚构造函数会直接调用

This disassembly shows what I mean:

这个反汇编显示了我的意思:

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

So when you have a variable of type class reference for which the constructor is virtual, and you call that constructor through that variable, you are sure that the actual class in that variable will have a virtual constructor.

因此,如果您有一个构造函数为虚拟类型类引用的变量,并且您通过该变量调用该构造函数,那么您确定该变量中的实际类将具有虚拟构造函数。

You can not determine on which actual class that constructor is implemented (well, not without extra debugging info, for instance from the .DCU, .MAP, .JDBG, or other sources).

您无法确定构造函数实现的实际类(嗯,不是没有额外的调试信息,例如来自.DCU,.MAP,.JDBG或其他来源)。

Here is the example code that does compile:

以下是编译的示例代码:

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.

To get back to your original question: When your class reference references a base class having a virtual constructor, you are sure that you will always call a virtual constructor using an indirection. When your class reference references a base class having a non-virtual constructor, you are sure that you will always call a non-virtual constructor using a direct call.

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

Hope this sheds some more light on your question.

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

--jeroen