【问题标题】:What is the "identity pointer" before a TTypeInfo there for?那里的 TTypeInfo 之前的“身份指针”是什么?
【发布时间】:2010-08-09 18:41:18
【问题描述】:

如果您对 Delphi 内部结构进行了足够多的研究,您会发现一些关于编译器生成的 TTypeInfo 记录的奇怪且显然未记录的内容。如果 PTypeInfo 指向地址 X 处的 TTypeInfo 记录,在X - 4,您会发现接下来的 4 个字节描述了指向 X 的指针。例如:

procedure test(info: PTypeInfo);
var
  addr: cardinal;
  ptr: PPointer;
begin
  addr := cardinal(info);
  writeln('addr: ', addr);
  dec(addr, 4);
  ptr := PPointer(addr);
  addr := cardinal(ptr^);
  writeln('addr: ', addr);
end;

将编译器生成的任何合法的 PTypeInfo 传递给这个例程,它会输出相同的地址两次。我在 TypInfo.pas 中浏览了一下,但我没有看到任何提到这个“身份指针”或它的用途的东西。有谁知道为什么会这样?从至少 D3 到 D2010 的每个版本的 Delphi 似乎都是如此。

【问题讨论】:

  • 我假设上面的代码中缺少var...
  • @Andreas:在哪里?我没有看到任何缺失的变量...

标签: delphi pointers rtti internals


【解决方案1】:

非常简单:包和动态链接。

BPL 是 DLL。 DLL 是通过正在修补的表链接起来的,而不是 EXE 中的所有代码或 DLL 链接到正在修补的 DLL(这将对在多个进程之间共享只读内存造成很大的危害)。为了防止在链接 BPL 时修改代码中某处对 TypeInfo(SomeType) 的引用,或者 EXE 或 DLL 的类型信息被修改,而是通过导入表进行间接处理。

在这个程序中,静态链接与针对 BPL 的链接很容易看出区别:

{$apptype console}
uses TypInfo, SysUtils;
type
  TFoo = class(TObject);
var
  x: PPTypeInfo;
begin
  x := GetTypeData(TypeInfo(TFoo))^.ParentInfo;
  Writeln(x^^.Name);
  Writeln(Format('x  %p', [x]));
  Writeln(Format('x^ %p', [x^]));
end.

在我的本地机器上,使用dcc32 test.pas 编译,它输出:

TObject
x  00401B64
x^ 00401B68

但是当使用 dcc32 -LUrtl test.pas 的 RTL 包编译时,它会输出:

TObject
x  004051F0
x^ 40001DA4

希望这可以解决问题。

【讨论】:

  • 我只是想提出一个非常相似的想法。感谢您的洞察力。
  • 所以该指针必须在某个地方让链接器完成它的工作,并且它恰好被放置在它指向的数据之前,当数据在同一个模块中时,按照惯例?好的,这是有道理的。谢谢。
  • @Mason 所有类型信息修正——从一个类型信息块指向另一个类型信息的指针——是 PPTypeInfo 类型,而不是 PTypeInfo 类型,用于处理动态链接情况。在静态链接的情况下,需要有一个中间指针才能使约定起作用,并且 typeinfo 本身的一部分与任何其他内容一样有意义。也就是说,链接器不存在;它的存在是因为约定,约定的存在是因为动态链接,这样做的方式是最大限度地提高页面共享的潜力。
  • 啊,这就是 PPTypeInfo 的用途。谢谢你的解释。
【解决方案2】:

不完全理解发生了什么,但是当您查看 TypInfo 单元中的 IsPublishedProp 时,您会看到它将实例的 ClassInfo 转换为指向 TypeInfo 的指针结构:

PTypeInfo(Instance.ClassInfo)

当您查看 ClassInfo 方法时,它返回一个简单的指针,其值似乎与 vmt 表相关:

  Result := PPointer(Integer(Self) + vmtTypeInfo)^;

vmtTypeInfo 的值为 -72。 -76 之前的四个字节是vmtInitTable。 vmtTypeInfo 后跟 FieldTable、MethodTable、DynamicTable 等。

vmtInitTable 值用于例如 TObject.CleanupInstance 并作为指向 TypeInfo 结构的指针传递给 _FinalizeRecord。

因此,指向 TypeInfo 结构的 TypeInfo 结构之前的四个字节似乎是设计使然,并且是 vmt 结构的一部分。

编辑

正如 Mason 正确指出的那样,上述内容完全是一条红鲱鱼(见 cmets)。我留下答案,这样其他人就不必追究了。

更新 为了避免混淆变量及其地址,我将 Mason 的测试程序改写如下:

procedure test(info: PTypeInfo);
begin
  writeln('value of info       : ', cardinal(info));
  writeln('info - 4            : ', cardinal(info) - 4);
  writeln('value 4 bytes before: ', cardinal(PPointer(cardinal(info)-4)^));
end;

并使用以下信息调用它:

procedure TryRTTIStuff;
begin
  writeln('TPersistent');
  test(TypeInfo(TPersistent));

  writeln('TTypeKind enumeration');
  test(TypeInfo(TTypeKind));

  writeln('Integer');
  test(TypeInfo(Integer));

  writeln('Nonsense');
  test(PTypeInfo($420000));
end;

前三个产生 Mason 描述的结果。我只添加了一个额外的 writeln 来显示最后一个 writeln 的指针值。 TryRTTIStuff 中的最后一次调用是为了表明,当您不传递指向有效 TypeInfo 结构的指针时,您不会在调用的第一个和第三个 writeln 上获得相同的值。

目前还没有关于 TypeInfo 发生了什么的线索。也许我们应该问问 Barry Kelly,因为他是新 D2010 RTTI 的作者,所以也应该对旧的有很多了解......

【讨论】:

  • 抱歉,您看错了。 TypeInfo 与 VMT 无关,因为它也可以用于非对象类型。 VMT 包含指向该类的 TTypeInfo 记录的指针,但这不是我要问的。
  • @Mason:您可能完全正确,但我认为“老式 RTTI”仅支持类和枚举的 TypeInfo?后者仅当您未在枚举声明中分配特定值时。我认为其他类型的 RTTI 直到 D2010 才出现(我使用的是 D2009)。
  • 没有。类型信息必须可用于可在 DFM 中使用的任何基本类型,因为这就是系统的发明目的:表单序列化和反序列化。
  • 哦,好的,当然。我认为 TTypeKinds “只是”类属性的描述符,但基本类型确实有自己的 TypeInfo 结构(我正在玩你的代码)。可惜 TypeInfo 方法是编译器的魔法不是吗...
  • 是的,众所周知,巴里有时会出现在这里并回答问题。我有点认为他或艾伦鲍尔可能是唯一可以对这个问题给出有效答案的人。 :(
【解决方案3】:

也许它是一个恰好在连续内存中的链表:)

【讨论】:

  • 不,因为在 TTypeInfo/TTypeData 结构中没有类似“下一个指针”的东西。不过,这个想法很有趣。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-22
  • 2023-03-28
相关资源
最近更新 更多