什么是“身份指针”?在TTypeInfo之前有什么用?

发布于 2024-09-13 18:07:51 字数 565 浏览 10 评论 0原文

如果您在 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 中浏览了一下,但没有看到任何提到这个“身份指针”或它的用途的内容。有谁知道为什么会这样?这似乎在 Delphi 的每个版本中都是如此,至少从 D3 到 D2010。

If you poke around enough in Delphi internals, you'll find something strange and apparently undocumented about TTypeInfo records generated by the compiler. If the PTypeInfo points to a TTypeInfo record at address X, at X - 4 you'll find the next 4 bytes describe a pointer to X. For example:

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;

Pass any legitimate PTypeInfo generated by the compiler into this routine, and it'll output the same address twice. I've poked around in TypInfo.pas a little, but I don't see anything that mentions this "identity pointer" or what it's there for. Does anyone know why this is there? This appears to be true in every version of Delphi from at least D3 to D2010.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

年少掌心 2024-09-20 18:07:51

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

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

在此程序中,很容易看出静态链接与针对 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

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

TObject
x  004051F0
x^ 40001DA4

希望这可以解决问题。

It's very simple: packages and dynamic linking.

BPLs are DLLs. DLLs are linked up through tables being patched, rather than all the code in the EXE or DLL linking against the DLL being patched (which would do great harm to sharing of read-only memory between multiple processes). To prevent the need for a reference to TypeInfo(SomeType) somewhere in the code, or typeinfo of an EXE or DLL, being modified when linking against the BPL, instead there's an indirection through the import table.

It's easy to see the difference when linking statically versus linking against a BPL in this program:

{$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.

On my local machine, compiled with dcc32 test.pas, it outputs:

TObject
x  00401B64
x^ 00401B68

But when compiled with the RTL package with dcc32 -LUrtl test.pas, it outputs:

TObject
x  004051F0
x^ 40001DA4

Hopefully this clears it up.

青衫负雪 2024-09-20 18:07:51

不完全理解发生了什么,但是当您查看 TypInfo 单元中的 IsPublishedProp 时,您会发现它转换了实例作为指向 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 的测试过程,如下所示:

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 的作者,所以应该对旧的有很多了解......

Don't understand completely what is going on, but when you have a look at for example IsPublishedProp in the TypInfo unit, you'll see that it casts the ClassInfo of the instance as a pointer to a TypeInfo structure:

PTypeInfo(Instance.ClassInfo)

When you look at the ClassInfo method, it returns a simple pointer the value of which seems related to the vmt table:

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

vmtTypeInfo has a value of -72. Four bytes before that at -76 is vmtInitTable. vmtTypeInfo is followed by FieldTable, MethodTable, DynamicTable etc.

the vmtInitTable value is used in for example TObject.CleanupInstance and passed to _FinalizeRecord as the pointer to the TypeInfo structure.

So the four bytes before the TypeInfo structure pointing to the TypeInfo structure seem to be there by design and part of the vmt structure.

Edit

As Mason rightly pointed out the above is a complete red herring (see comments). I am leaving the answer so others won't have to chase it down.

Update
To avoid confusion over variables and their addresses, I have rewritten Mason's test procedure as follows:

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;

and call it with the following information:

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;

The first three produce the results that Mason describes. I only added an extra writeln to show the pointer value for the last writeln. The last call in TryRTTIStuff is to show that when you do not pass in a pointer to a valid TypeInfo structure, you do not get the same value on the first and third writeln's for the call.

No clue yet as to what is going on with the TypeInfo. Maybe we should ask Barry Kelly as he is the author of the new D2010 RTTI so should know a lot about the old one as well...

黯然#的苍凉 2024-09-20 18:07:51

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

maybe its a linked list that happens to be in contiguous memory:)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文