Delphi: how to set RTTI dynamic array length using DynArraySetLength?

I would like to set the length of the dynamic array as suggested in this post . I have two TMyClass classes and its associated TChildClass, defined as

TChildClass = class private FField1: string; FField2: string; end; TMyClass = class private FField1: TChildClass; FField2: Array of TChildClass; end; 

Array extension implemented as

 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 clean up immediatly Value.Free; end; end; end; ReadLn; Val.AsObject.Free; finally RContext.Free; end; end. 

Being new to D2010 RTTI, I suspected that the error might depend on getting ArrValue from the class instance, but the subsequent WriteLn prints "TRUE", so I solved it. However, unfortunately, the same WriteLn reports that the size of ArrValue is 0, which is confirmed by the "Index Outside the Borders" - an exception that I get when I try to set any of the elements in the array (via ArrValue.SetArrayElement(i, Value); ) Does anyone know what I'm doing wrong here? (Or maybe there is a better way to do this?) TIA!

+3
source share
2 answers

Dynamic arrays hardly work. They are counted, and the following comment inside DynArraySetLength should shed light on the problem:

// If the heap object is not shared (ref count = 1), just resize it. Otherwise we will make a copy

Your object contains one link to it, as well as TValue. In addition, GetReferenceToRawData gives you a pointer to an array. You have to say PPointer(GetReferenceToRawData)^ to get the actual array to go to DynArraySetLength.

Once you get this, you can resize it, but you are left with a copy. Then you should set it back to the original array.

 TValue.Make(@ArrPointer, dynArr.Handle, ArrValue); RField.SetValue(val.AsObject, arrValue); 

In general, it may be much easier to just use a list instead of an array. With D2010, you get Generics.Collections, which means you can make a TList<TChildClass> or TObjectList<TChildClass> and have all the advantages of a list class without losing type safety.

+7
source

I think you should define the array as a separate type:

 TMyArray = array of TMyClass; 

and use it.

From the old RTTI-based XML serializer, I know that the general method you use should work (verified by 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; 

Hope this helps.

0
source

Source: https://habr.com/ru/post/907931/


All Articles