我想设置动态数组的长度,如this post中所述。我有两个类TMyClass和相关的TChildClass定义为
TChildClass = class
private
FField1: string;
FField2: string;
end;
TMyClass = class
private
FField1: TChildClass;
FField2: Array of TChildClass;
end;
数组扩充实现为
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.
作为D2010 RTTI的新用户,我怀疑错误可能取决于从类实例中获取ArrValue,但随后的WriteLn
打印“TRUE”,所以我已经排除了这一点。然而,令人失望的是,相同的WriteLn
报告ArrValue的大小为0,这是由“索引越界”确认的 - 我在尝试设置数组中的任何元素时得到的异常(通过{{ 1}})。有谁知道我在这里做错了什么? (或许有更好的方法可以做到这一点?)TIA!
答案 0 :(得分:8)
动态数组很难处理。它们被引用计数,而DynArraySetLength中的以下注释应该可以解释这个问题:
//如果没有共享堆对象(ref count = 1),只需调整它的大小。否则,我们制作副本
你的对象持有一个引用,TValue也是如此。此外,GetReferenceToRawData为您提供指向数组的指针。您需要说PPointer(GetReferenceToRawData)^
以将实际数组传递给DynArraySetLength。
一旦你有了它,你可以调整它,但你留下了副本。然后你必须将它设置回原始数组。
TValue.Make(@ArrPointer, dynArr.Handle, ArrValue);
RField.SetValue(val.AsObject, arrValue);
总而言之,使用列表而不是数组可能要简单得多。使用D2010,您可以使用Generics.Collections,这意味着您可以创建TList<TChildClass>
或TObjectList<TChildClass>
并享受列表类的所有好处,而不会失去类型安全性。
答案 1 :(得分:0)
我认为您应该将数组定义为单独的类型:
TMyArray = array of TMyClass;
并使用它。
从一个旧的基于RTTI的XML序列化器我知道你使用的一般方法应该工作(D7..2009测试):
procedure TXMLImpl.ReadArray(const Name: string; TypeInfo: TArrayInformation; Data: Pointer; IO: TParameterInputOutput);
var
P: PChar;
L, D: Integer;
BT: TTypeInformation;
begin
FArrayType := '';
FArraySize := -1;
ComplexTypePrefix(Name, '');
try
// Get the element type info.
BT := TypeInfo.BaseType;
if not Assigned(BT) then RaiseSerializationReadError; // Not a supported datatype!
// Typecheck the array specifier.
if (FArrayType <> '') and (FArrayType <> GetTypeName(BT)) then RaiseSerializationReadError;
// Do we have a fixed size array or a dynamically sized array?
L := FArraySize;
if L >= 0 then begin
// Set the array
DynArraySetLength(PPointer(Data)^,TypeInfo.TypeInformation,1,@L);
// And restore he elements
D := TypeInfo.ElementSize;
P := PPointer(Data)^;
while L > 0 do begin
ReadElement(''{ArrayItemName},BT,P,IO); // we allow any array item name.
Inc(P,D);
Dec(L);
end;
end else begin
RaiseNotSupported;
end;
finally
ComplexTypePostfix;
end;
end;
希望这会有所帮助..