如何检测Delphi类是否具有虚拟构造函数?
例如,有没有办法发现这个类(在运行时)有一个虚拟构造函数 例如,在这段代码中,我想测试Clazz引用的类是否具有虚拟构造函数:如何检测Delphi类是否具有虚拟构造函数?,delphi,delphi-2009,rtti,Delphi,Delphi 2009,Rtti,例如,有没有办法发现这个类(在运行时)有一个虚拟构造函数 例如,在这段代码中,我想测试Clazz引用的类是否具有虚拟构造函数: procedure Test; var Clazz: TClass; Instance: TObject; begin Clazz := TMyClass; Instance := Clazz.Create; end; 是否有一个简单的解决方案,例如使用RTTI,它可以在Delphi 6到2009中工作?查看TypInfo单元,似乎没有任何方法可以判断
procedure Test;
var
Clazz: TClass;
Instance: TObject;
begin
Clazz := TMyClass;
Instance := Clazz.Create;
end;
是否有一个简单的解决方案,例如使用RTTI,它可以在Delphi 6到2009中工作?查看TypInfo单元,似乎没有任何方法可以判断使用RTTI的方法是否是虚拟的。但是,如果您有一个类引用,您可能可以通过检查VMT来滚动您自己的方法 根据Allen Bauer的说法,在对的回答中,您可以在vmtClassName指向的值之前找到VMT的结尾。第一个用户定义的虚拟方法(如果有)位于类引用的地址。换句话说,
指针(Clazz)^
。既然您已经知道了VMT的用户定义部分的起点和终点,那么创建一个while循环来比较表中的每个指针与指向Clazz.create的方法指针的代码部分,并将其转换为TMethod应该不会太困难。如果你得到一个匹配,那么它就是一个虚拟方法。如果不是,那就不是
是的,这是一个有点黑客,但它会工作。如果有人能找到更好的解决方案,就给他们更多的力量。你知道,我越想,我越不喜欢我给出的最终被接受的答案。问题是,编写的代码只能处理编译时已知的信息。如果Clazz被定义为一个TClass,那么将Clazz.Create放在一个TMethod中总是会给您一个指向TObject.Create的方法指针 您可以尝试将Clazz定义为“TMyClass类”。问题是,你已经有了一个虚拟构造函数,所以它会给你一个最高级别的构造函数,它可以覆盖这个构造函数。但从您的评论来看,您试图找到的似乎是一个非虚拟构造函数(使用重新引入;),它将破坏您的虚拟构造。很可能您使用的是工厂模式,这可能是一个问题 唯一的解决方案是使用RTTI查找实际附加到类的构造函数。您可以获得“名为Create的方法”的方法指针,并在我在另一个答案中解释的技巧中使用它。为此,必须声明基本虚拟构造函数已发布。这将强制所有覆盖它的方法也被发布。问题是,有人仍然可以使用重新引入在上面声明一个未发布的构造函数,那么您的方案就会崩溃。对于后代类将做什么,您没有任何保证 这个问题没有技术解决方案。唯一真正有效的是教育。您的用户需要知道这个类是由工厂实例化的(或者您需要虚拟构造函数的任何原因),如果他们在派生类中重新引入构造函数,它可能会破坏一切。在文档中写一条注释,在源代码中写一条注释。这几乎是你所能做的了。迈克尔 我明白你的问题,但是由于你的源代码没有编译,我认为你没有抓住问题的重点;-) 我的回答是对梅森在第二个回答中试图解释的内容进行了详细阐述 目前的问题是,您的问题意味着您有一个“类引用”(如TClass或TComponentClass),它引用了一个具有虚拟构造函数的基类。 但是,TClass没有(TClass引用具有非虚拟构造函数的类),但TComponentClass有 在使用类引用反汇编对构造函数的调用时,您会看到不同之处。 通过类引用调用虚拟构造函数时,代码与调用非虚拟构造函数时略有不同:
procedure Test;
var
Clazz: TClass;
Instance: TObject;
begin
Clazz := TMyClass;
Instance := Clazz.Create;
end;
- 调用虚拟构造函数具有间接性
- 调用非虚拟构造函数会执行直接调用
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我是否正确理解,如果声明为虚拟的构造函数,将在VMT中引用?是的,与任何其他方法一样……如果您需要此信息,那么我认为您做错了……是的,没错,我想检查其他人的代码中是否有错误。如果构造函数不是声明为虚拟的,它将不会被调用,因此“真正的坏事情可能会发生”。是的,这正是我在第二篇文章中试图表达的观点。很好的解释。当您在ASM中进行说明时,这些事情总是更有意义(至少对于了解实际情况的程序员来说)+1.谢谢。总是很难得到一个既有效又能很好解释的简明示例:-)