delphi 测试指针是否为TObject示例

qaxu7uf2  于 2022-12-12  发布在  其他
关注(0)|答案(1)|浏览(211)

我正在尝试用 Delphi RTTI编写一些通用调试代码。我遇到的问题是我正在检查一个TList的内容,它只包含指针。现在我从我的代码中知道这些指针实际上是TObject引用(或一些后代)。
所以我的问题是:给定一个有效的指针,是否有一种安全的方法来确定它是否实际上是一个TObject引用?

yebdmbv4

yebdmbv41#

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

您只能确定地判断指针不是对象引用。
话虽如此,出于调试目的,有一种方法可以检测指针可能是对象引用,但您也可能得到误报-我们正在检查的内存内容可能完全偶然地满足检查。
每个对象示例还拥有一个指向其类虚方法表的指针- VMT。它还拥有一个指向其数据开始的指针-该指针的偏移量由System单元中声明的vmtSelfPtr常量定义。
vmtSelfPtr中的类引用与对象不同,因为它们将引用保留回自身。
上面的事实将告诉我们我们不是先在看一个类引用,然后我们将检查可能对象的VMT是否指向可能的类引用。
除此之外,对于每个指针,我们将首先检查它是否属于有效的地址空间。
您可以在此处找到有关VMT的更多信息内部数据格式-类类型
检测指针是否为可能对象的代码取自Spring 4D库:

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

**注意:**除了用于调试目的之外,在发布模式下,可以在您知道是nil、类引用或对象引用的任何指针上安全地调用IsValidObject。换句话说,您可以安全地使用它来区分类引用和对象引用。

相关问题