There is no safe way to determine whether valid pointer is TObject
reference
You can only tell with certainty that pointer is not an object reference.
Having said that, for debugging purposes there is a way to detect that pointer might be an object reference, but you may also get false positives - memory content we are inspecting may satisfy the check by pure chance.
Every object instance also holds a pointer to its class virtual method table - VMT. It also has a pointer pointing to start of its data - offset of that pointer is defined by vmtSelfPtr
constant declared in System
unit.
Class references in vmtSelfPtr
differ from objects as they hold reference back to self.
Above facts will tells us we are not looking at a class reference first, and then we will check whether possible object's VMT points to possible class reference.
Besides that for each pointer we will first check that it belongs to valid address space.
You can find more information about VMT here Internal Data Formats - Class Types
The code that detects whether pointer is possible object is taken from
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;
And we can use that function like:
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.
Note: IsValidObject
should only be used on valid pointers - meaning pointers that point to valid allocated memory. You cannot detect whether object instance behind the pointer has been released or not.
If you have following code, you will still get TRUE
as the result of the IsValidObject
call.
o := TObject.Create;
o.Free;
Writeln(IsValidObject(Pointer(o))); // TRUE
Note: Besides for debugging purposes, IsValidObject
can be safely called in release mode on any pointer you know it is either nil
, class reference, or object reference. In other words you can safely use it to distinguish between class and object references.