Delphi:在运行时查找从给定基类下降的类?

Ian*_*oyd 8 delphi rtti delphi-5

是否正在运行时,查找从特定基类下降的所有类?

例如,假装有一个类:

TLocalization = class(TObject)
...
public
   function GetLanguageName: string;
end;
Run Code Online (Sandbox Code Playgroud)

或者假装有一堂课:

TTestCase = class(TObject)
...
public
   procedure Run; virtual;
end;
Run Code Online (Sandbox Code Playgroud)

或者假装有一堂课:

TPlugIn = class(TObject)
...
public
   procedure Execute; virtual;
end;
Run Code Online (Sandbox Code Playgroud)

或者假装有一堂课:

TTheClassImInterestedIn = class(TObject)
...
public
   procedure Something;
end;
Run Code Online (Sandbox Code Playgroud)

在运行时,我想找到所有下降的类,TTestCase以便我可以使用它们.

是否可以查询RTTI以获取此类信息?

或者:德尔福有没有办法让每个班级走路?然后我可以简单地打电话:

RunClass: TClass;

if (RunClass is TTestCase) then
begin
   TTestCase(RunClass).Something;
end;
Run Code Online (Sandbox Code Playgroud)

也可以看看

Mas*_*ler 10

它可以使用RTTI完成,但不能在Delphi 5中完成.为了找到符合特定条件的所有类,首先需要能够找到所有类,并且在Delphi 2010中引入了执行此操作所需的RTTI API.你会这样做:

function FindAllDescendantsOf(basetype: TClass): TList<TClass>;
var
  ctx: TRttiContext;
  lType: TRttiType;
begin
  result := TList<TClass>.Create;
  ctx := TRttiContext.Create;
  for lType in ctx.GetTypes do
    if (lType is TRttiInstanceType) and
       (TRttiInstanceType(lType).MetaclassType.InheritsFrom(basetype)) then
      result.add(TRttiInstanceType(lType).MetaclassType);
end;
Run Code Online (Sandbox Code Playgroud)


Pat*_*kvL 9

嗯,是的,有一种方法,但你不会喜欢它.(显然,我需要这样一个免责声明,以防止我的其他完全有帮助的评论被那些知识渊博的,但不那么宽容的'高级'SO成员所淹没.)

仅供参考:以下描述是我在Delphi 5最新和最好的时候实际编写的一段代码的高级概述.从那时起,该代码被移植到更新的Delphi版本(目前直到Delphi 2010)并且仍然有效!

对于初学者,您需要知道一个类只不过是VMT和附带函数的组合(可能还有一些类型信息,具体取决于编译器版本和设置).您可能知道,类(由TClass类型标识)只是指向该类VMT的内存地址的指针.换句话说:如果您知道类的VMT的地址,那么它也是TClass指针.

有了这条知识牢记在你的脑海中,你实际上可以扫描你的可执行内存,并为每个地址测试它是否看起来像'VMT.所有似乎是VMT的地址都可以添加到列表中,从而可以完整地概述可执行文件中包含的所有类!(实际上,这甚至可以让您访问仅在单元的实现部分中声明的类,以及从作为二进制文件分发的组件和库链接的类!)

当然,有些地址似乎是一个有效的VMT,但实际上是一些随机的其他数据(或代码) - 但是通过我提出的测试,这在我身上从未发生过(大约6年)在十多个积极维护的应用程序中运行此代码).

所以这是你应该做的检查(按照这个确切的顺序!):

  1. 地址是否等于TObject的地址?如果是这样,这个地址是VMT,我们就完成了!
  2. 阅读TClass(地址).ClassInfo; 如果分配:
    1. 它应该属于一个代码段(不,我不会详细介绍它 - 只是google it up)
    2. 此ClassInfo的最后一个字节(通过添加SizeOf(TTypeInfo)+ SizeOf(TTypeData)确定)也应该属于该代码段
    3. 这个ClassInfo(类型为PTypeInfo)应该将它的Kind字段设置为tkClass
    4. 在此ClassInfo上调用GetTypeData,生成一个PTypeData
      1. 这也应该属于有效的代码段
      2. 它的最后一个字节(通过添加SizeOf(TTypeData)确定)也应该属于该代码段
      3. 在这个TypeData中,它的ClassType字段应该等于被测试的地址.
  3. 现在读取偏移量vmtSelfPtr上的VMT,并测试这是否会导致正在测试的地址(应指向自身)
  4. 读取vmtClassName并检查它是否指向有效的类名(检查指针是否再次驻留在有效段中,字符串长度是否可接受,并且IsValidIdent应返回True)
  5. 读取vmtParent - 它也应该属于有效的代码段
  6. 现在转换为TClass并读取ClassParent - 它也应该属于有效的代码段
  7. 读取vmtInstanceSize,它应该是> = TObject.InstanceSize和<= MAX_INSTANCE_SIZE(你要确定)
  8. 从它的ClassParent读取vmtInstanceSize,它也应该是> = TObject.InstanceSize并且<=先前读取的实例大小(父类永远不能大于子类)
  9. (可选)您可以检查索引0及以上的所有VMT条目是否都是有效的代码指针(尽管确定VMT中的条目数有点问题......但没有指示).
  10. 使用ClassParent递归这些检查.(这应该达到上面的TObject测试,或者悲惨地失败!)

如果所有这些检查都成立,则测试地址是有效的VMT(就我而言)并且可以添加到列表中.

祝你好好实现这一切,我花了大约一个星期来做到这一点.

请告诉你它是如何工作的.干杯!

  • @David:不,他们差不多等同.使用您自己的注册表,您始终只能使用您主动注册的课程.使用Patrick的方法,您可以检测**您的exe中存在的所有**类...无需RTTI或注册表.在D2010 +中没有RTTI是一个优势,因为它有助于避免exe的代码膨胀. (3认同)
  • 有趣的方法,但是当创建自己的类注册表之类的东西以更安全的方式实现相同的东西时,它是否具有风险和复杂的解决方案? (2认同)