首页 > 解决方案 > DynArraySize() 只能在最多 649 个整数元素的数组中正常工作

问题描述

我在Delphi 10.2 Update 2中遇到了与 RTTI 相关的问题,并且能够将其追踪到更少的代码(见下文)。

我有一些发布类型属性的TPersistent后代类。当我通过它接收它的值并通过它查询它的大小时,它的大小只能达到 649 个元素的大小。在这个特殊计数之上,返回一些非常大的尺寸值。TMyObjTArray<Integer>GetDynArrayProp()DynArraySize()

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

我的使用有什么问题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.

标签: delphidynamic-arraysrttidelphi-10.2-tokyo

解决方案


简短回答:您的代码已损坏

长答案:

对 getter 的调用正在创建一个新数组(参见参考资料),该数组TEnumerable<T>.ToArrayImpl在(在此处放置一个断点并查看反汇编程序 - 它显示)System.Generics.Collections.pas的结尾处被释放。由于没有其他对该数组的引用,因此它的内存被释放(如果您进一步深入,您会看到它最终以. 这意味着对该函数的每次调用都会返回一个悬空指针!System.TypInfo.GetDynArrayProp@DynArrayClearSystem.pas_FreeMem

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

想到了两种不涉及重写 getter 的可能解决方案:

  • System.Rtti.pas使用来自as的 RTTITValue使参考保持活动状态
  • 编写您自己的版本以GetDynArrayProp使引用保持活动状态-但您必须确保在之后始终调用 DynArrayClear 否则会造成内存泄漏

我个人会使用第一个。


推荐阅读