Delphi:如何使用DynArraySetLength设置RTTI访问的动态数组的长度?

con*_*tor 3 arrays delphi dynamic rtti

我想设置动态数组的长度,如本文所述.我有两个类TMyClass和相关的TChildClass定义为

TChildClass = class
private
  FField1:  string;
  FField2:  string;
end;

TMyClass = class
private
  FField1:  TChildClass;
  FField2:  Array of TChildClass;
end;
Run Code Online (Sandbox Code Playgroud)

数组扩充实现为

var
  RContext:     TRttiContext;
  RType:        TRttiType;
  Val:          TValue;      // Contains the TMyClass instance
  RField:       TRttiField;  // A field in the TMyClass instance
  RElementType: TRttiType;   // The kind of elements in the dyn array
  DynArr:       TRttiDynamicArrayType;
  Value:        TValue;  // Holding an instance as referenced by an array element
  ArrPointer:   Pointer;
  ArrValue:     TValue;
  ArrLength:    LongInt;
  i:            integer;
begin
  RContext := TRTTIContext.Create;
  try
    RType := RContext.GetType(TMyClass.ClassInfo);
    Val := RType.GetMethod('Create').Invoke(RType.AsInstance.MetaclassType, []);
    RField := RType.GetField('FField2');
    if (RField.FieldType is TRttiDynamicArrayType) then begin 
      DynArr := (RField.FieldType as TRttiDynamicArrayType);
      RElementType := DynArr.ElementType;
      // Set the new length of the array
      ArrValue := RField.GetValue(Val.AsObject);
      ArrLength := 3;   // Three seems like a nice number
      ArrPointer := ArrValue.GetReferenceToRawData;
      DynArraySetLength(ArrPointer, ArrValue.TypeInfo, 1, @ArrLength);
      { TODO : Fix 'Index out of bounds' }
      WriteLn(ArrValue.IsArray, ' ', ArrValue.GetArrayLength);
      if RElementType.IsInstance then begin
        for i := 0 to ArrLength - 1 do begin
          Value := RElementType.GetMethod('Create').Invoke(RElementType.AsInstance.MetaclassType, []);
          ArrValue.SetArrayElement(i, Value);
          // This is just a test, so let's clean up immediatly
          Value.Free;
        end;
      end;
    end;
    ReadLn;
    Val.AsObject.Free;
  finally
    RContext.Free;
  end;
end.
Run Code Online (Sandbox Code Playgroud)

作为D2010 RTTI的新手,我怀疑错误可能取决于从类实例中获取ArrValue,但后续WriteLn打印"TRUE",所以我已经排除了这一点.然而,令人失望的是,同样的WriteLn报告称ArrValue的大小为0,这是由"索引越界"确认的 - 我在尝试设置数组中的任何元素时得到的(通过ArrValue.SetArrayElement(i, Value);).有谁知道我在这里做错了什么?(或许有更好的方法可以做到这一点?)TIA!

Mas*_*ler 8

动态数组很难处理.它们被引用计数,而DynArraySetLength中的以下注释应该可以解释这个问题:

//如果没有共享堆对象(ref count = 1),只需调整它的大小.否则,我们复制一份

你的对象持有一个引用,TValue也是如此.此外,GetReferenceToRawData为您提供指向数组的指针.您需要说PPointer(GetReferenceToRawData)^实际数组传递给DynArraySetLength.

一旦你有了它,你可以调整它,但你留下了副本.然后你必须将它设置回原始数组.

TValue.Make(@ArrPointer, dynArr.Handle, ArrValue);
RField.SetValue(val.AsObject, arrValue);
Run Code Online (Sandbox Code Playgroud)

总而言之,使用列表而不是数组可能要简单得多.使用D2010,您可以使用Generics.Collections,这意味着您可以创建TList<TChildClass>TObjectList<TChildClass>拥有列表类的所有好处,而不会丢失类型安全性.

  • 经过一段时间的调查,我发现 TValue.MakeWithoutCopy 解决了内存泄漏的问题。 (2认同)