Delphi DynArraySize()只能在649个整数元素的数组中正常工作

Delphi DynArraySize()只能在649个整数元素的数组中正常工作,delphi,dynamic-arrays,rtti,delphi-10.2-tokyo,Delphi,Dynamic Arrays,Rtti,Delphi 10.2 Tokyo,我在Delphi10.2Update2中遇到了一个与RTTI相关的问题,并且能够追踪到更少的代码(见下文) 我有一些TPersistent-后代类TMyObj,它发布了类型为TArray的属性。当我通过GetDynArrayProp()接收它的值并通过DynArraySize()查询它的大小时,这只适用于649个元素的大小。在这个特殊计数之上,返回一些非常大的值 请注意,我的数组是从TDictionary的Keys属性的一个实例生成的,它有自己的ToArray方法。 我还试图修改TMyObj.G

我在Delphi10.2Update2中遇到了一个与RTTI相关的问题,并且能够追踪到更少的代码(见下文)

我有一些
TPersistent
-后代类
TMyObj
,它发布了类型为
TArray
的属性。当我通过
GetDynArrayProp()
接收它的值并通过
DynArraySize()
查询它的大小时,这只适用于649个元素的大小。在这个特殊计数之上,返回一些非常大的值

请注意,我的数组是从
TDictionary
Keys
属性的一个实例生成的,它有自己的
ToArray
方法。 我还试图修改
TMyObj.GetDynArray
,以便它直接返回
TArray
的实例,并且工作正常。 因此,我认为这可能以某种神秘的方式相互关联

我使用的
DynArraySize()
有什么问题?动态数组的这种神秘行为背后是什么?

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.
程序RTTIPropDynArray;
{$APPTYPE控制台}
使用
System.Classes、System.Generics.Collections、System.SysUtils、System.TypeInfo;
类型
TMyDict=t词典;
TMyArray=焦油;
TMyObj=类(持久)
私有的
价值:TMyDict;
函数GetDynArray:TMyArray;
公众的
构造函数创建(const ACount:Integer);
毁灭者毁灭;推翻
出版
属性DynArray:TMyArray读取GetDynArray;
结束;
{TMyObj}
构造函数TMyObj.Create(const ACount:Integer);
开始
f值:=TMyDict.Create;
而FValues.Count
简短回答:您的代码已损坏

长答覆:

对getter的调用正在创建一个新数组(请参见
System.Generics.Collections.pas
中的
TEnumerable.ToArrayImpl
),该数组将在
System.TypInfo.GetDynArrayProp
的尾声中解除分配(在那里放置一个断点并查看反汇编程序-它显示了
@DynArrayClear
)。由于没有对该数组的其他引用,因此它的内存会被释放(如果您进一步进入
System.pas
,您将看到它最终以
\u FreeMem
结束)。这意味着对该函数的每次调用都会返回一个悬空指针

现在,为什么在之前的所有通话中都能得到正确的结果?巧合-内存没有被其他任何东西重新分配

有两种可能的解决方案不需要重写getter:

  • 使用
    System.RTTI.pas
    中的RTTI作为
    TValue
    使引用保持活动状态
  • 编写自己版本的
    GetDynArrayProp
    ,使引用保持活动状态,但必须确保在创建内存泄漏后始终调用DynArrayClear
就我个人而言,我会使用第一个