【问题标题】:Test if a Pointer is a TObject instance测试 Pointer 是否为 TObject 实例
【发布时间】:2022-12-10 04:44:57
【问题描述】:

我正在尝试使用 Delphi RTTI 编写一些通用调试代码。我遇到的问题是我正在检查只包含指针的 TList 的内容。现在我从我的代码中知道这些指针实际上是 TObject 引用(或某些后代)。

所以我的问题是:给定一个有效的 Pointer 是否有一种安全的方法来确定它是否实际上是一个 TObject 引用?

【问题讨论】:

  • 一般来说,这是做不到的。
  • 欢迎来到Pointer 的缺点。
  • 不可能的。你可能有一个 Pointer 只是巧合地指向看起来就像一个对象实例的内存。
  • 好吧,这似乎很一致。由于这是仅调试代码,我想我可以试试看。群!
  • 该指针也可以指向刚刚释放并且在被覆盖之前仍处于有效状态的先前对象实例。

标签: delphi rtti delphi-10.1-berlin


【解决方案1】:

没有安全的方法来确定有效指针是否为 TObject 引用

您只能确定指针不是对象引用。

话虽如此,对于调试目的有一种方法可以检测到该指针可能是一个对象引用,但您也可能会得到误报——我们正在检查的内存内容可能完全符合检查条件。


每个对象实例还持有一个指向其类虚方法表——VMT 的指针。该指针的位置由System单元中声明的vmtSelfPtr常量定义。

该入口槽中的类引用保留对 self 的引用,这一事实将告诉我们我们正在寻找可能的类引用。

从给定的指针值开始,我们获得潜在类的地址,并检查该类是否在其自己的类指针中指向自身。

除此之外,对于每个指针,我们将首先检查它是否属于有效地址空间。

您可以在此处找到有关 VMT 的更多信息Internal Data Formats - Class Types

检测指针是否是可能对象的代码取自 Spring4D library:

uses
  {$IFDEF MSWINDOWS}
  Windows,
  {$ENDIF }
  TypInfo;

function IsValidObject(p: PPointer): Boolean;
{$IFDEF MSWINDOWS}
var
  memInfo: TMemoryBasicInformation;
{$ENDIF}

  function IsValidAddress(address: Pointer): Boolean;
  begin
    // Must be above 64k and 4 byte aligned
    if (UIntPtr(address) > $FFFF) and (UIntPtr(address) and 3 = 0) then
    begin
{$IFDEF MSWINDOWS}
      // do we need to recheck the virtual memory?
      if (UIntPtr(memInfo.BaseAddress) > UIntPtr(address))
        or ((UIntPtr(memInfo.BaseAddress) + memInfo.RegionSize) < (UIntPtr(address) + SizeOf(Pointer))) then
      begin
        // retrieve the status for the pointer
        memInfo.RegionSize := 0;
        VirtualQuery(address, memInfo, SizeOf(memInfo));
      end;
      // check the readability of the memory address
     if (memInfo.RegionSize >= SizeOf(Pointer))
        and (memInfo.State = MEM_COMMIT)
        and (memInfo.Protect and (PAGE_READONLY or PAGE_READWRITE
          or PAGE_WRITECOPY or PAGE_EXECUTE or PAGE_EXECUTE_READ
          or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY) <> 0)
        and (memInfo.Protect and PAGE_GUARD = 0) then
{$ENDIF}
      Exit(True);
    end;
    Result := False;
  end;

begin
  Result := False;
  if Assigned(p) then
  try
{$IFDEF MSWINDOWS}
    memInfo.RegionSize := 0;
{$ENDIF}
    if IsValidAddress(p)
      // not a class pointer - they point to themselves in the vmtSelfPtr slot
      and not (IsValidAddress(PByte(p) + vmtSelfPtr)
      and (p = PPointer(PByte(p) + vmtSelfPtr)^)) then
      if IsValidAddress(p^) and IsValidAddress(PByte(p^) + vmtSelfPtr)
        // looks to be an object, it points to a valid class pointer
        and (p^ = PPointer(PByte(p^) + vmtSelfPtr)^) then
        Result := True;
  except
  end; //FI:W501
end;

我们可以像这样使用该功能:

var
  o: TObject;
  p: Pointer;
  i: NativeInt;

begin
  i := 5;
  p := @i;
  o := TObject.Create;

  Writeln(IsValidObject(Pointer(o)));  // TRUE
  Writeln(IsValidObject(p));           // FALSE
end.

笔记:IsValidObject 应该只用于有效的指针——意思是指向有效分配内存的指针。您无法检测指针后面的对象实例是否已被释放。

如果您有以下代码,您仍然会得到 TRUE 作为 IsValidObject 调用的结果。

  o := TObject.Create;
  o.Free;
  Writeln(IsValidObject(Pointer(o)));  // TRUE

【讨论】:

    猜你喜欢
    • 2012-03-31
    • 1970-01-01
    • 2019-09-23
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-09
    • 2019-05-21
    相关资源
    最近更新 更多