I experienced a RTTI-related problem in Delphi 10.2 Update 2 and was able to track it down to a fewer amount of code (see below).
I have some TPersistent
-descendant class TMyObj
that publishes a property of type TArray<Integer>
. When I recieve its value via GetDynArrayProp()
and query its size via DynArraySize()
this works only up to a size of exactly 649 elements. Above this special count some very big size value is returned.
Note, that my array is generated from an instance of TDictionary<Integer,Boolean>
's Keys
property with its very own ToArray
method.
I also tried to modify TMyObj.GetDynArray
so that it returns a instance of TArray<Integer>
directly and it worked correctly.
Thus, I think that could correlate in some mystical way.
What is wrong with my use of DynArraySize()
? What's behind this mystical behaviour of dynamic arrays?
program RTTIPropDynArray;
{$APPTYPE CONSOLE}
uses
System.Classes, System.Generics.Collections, System.SysUtils, System.TypInfo;
type
TMyDict = TDictionary<Integer,Boolean>;
TMyArray = TArray<Integer>;
TMyObj = class(TPersistent)
private
FValues: TMyDict;
function GetDynArray: TMyArray;
public
constructor Create(const ACount: Integer);
destructor Destroy; override;
published
property DynArray: TMyArray read GetDynArray;
end;
{ TMyObj }
constructor TMyObj.Create(const ACount: Integer);
begin
FValues := TMyDict.Create;
while FValues.Count < ACount do
FValues.AddOrSetValue(Random(MaxInt), False);
end;
destructor TMyObj.Destroy;
begin
FreeAndNil(FValues);
inherited;
end;
function TMyObj.GetDynArray: TMyArray;
begin
Result := FValues.Keys.ToArray;
end;
function Test(const ACount: Integer): Boolean;
var
LInstance: TMyObj;
LExpectedSize: Integer;
LDynArraySize: Integer;
begin
LInstance := TMyObj.Create(ACount);
try
LExpectedSize := Length(LInstance.DynArray);
LDynArraySize := DynArraySize(GetDynArrayProp(LInstance, 'DynArray'));
Result := LExpectedSize = LDynArraySize;
if not Result then
WriteLn(Format('Expected size: %d; DynArraySize: %d', [LExpectedSize, LDynArraySize]));
finally
LInstance.Free;
end;
end;
var
LCount: Integer;
begin
Randomize;
LCount := 1;
while Test(LCount) do
Inc(LCount);
ReadLn;
end.
Short answer: Your code is broken
Long answer:
The call to the getter is creating a new array (see TEnumerable<T>.ToArrayImpl
in System.Generics.Collections.pas
) which is being deallocated in the epilogue of System.TypInfo.GetDynArrayProp
(put a breakpoint there and look into the disassembler - it shows @DynArrayClear
). Since there is no other reference to this array its memory gets deallocated (if you step into System.pas
further you will see that it eventually ends up in _FreeMem
). That means every call to this function is returning a dangling pointer!
Now why do you get correct results in all prior calls? Coincidence - the memory has not been reallocated by anything else.
Two possible solutions come into mind that don't involve rewriting the getter:
System.Rtti.pas
as TValue
keeps the reference aliveGetDynArrayProp
that keeps the reference alive - but you have to make sure to always call DynArrayClear after or you create memory leaksPersonally I would use the first one.