2010-03-27 1 views
3

Je voudrais définir la longueur d'un tableau dynamique, comme suggéré dans this post. J'ai deux classes TMyClass et TChildClass liés définis commeDelphi: comment définir la longueur d'un tableau dynamique RTTI-accédé en utilisant DynArraySetLength?

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

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

L'augmentation de tableau est mis en œuvre comme

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. 

Étant nouveau D2010 RTTI, je soupçonne l'erreur pourrait dépendre de l'obtention ArrValue de l'instance de classe , mais les WriteLn suivants imprime "TRUE", donc je l'ai exclu. Malheureusement, le même WriteLn signale que la valeur de ArrValue est 0, ce qui est confirmé par l'exception "Index out of bounds" que j'obtiens en essayant de définir n'importe lequel des éléments du tableau (par l'intermédiaire de ArrValue.SetArrayElement(i, Value);). Est-ce que quelqu'un sait ce que je fais mal ici? (Ou peut-être y at-il une meilleure façon de le faire?) TIA!

Répondre

7

Les tableaux dynamiques sont un peu difficile de travailler avec. Ils sont comptés de référence, et le commentaire suivant dans DynArraySetLength devrait faire la lumière sur le problème:

// Si l'objet tas n'est pas partagé (ref count = 1), il suffit de le redimensionner. Sinon, nous en faisons une copie

Votre objet contient une référence, ainsi que TValue. En outre, GetReferenceToRawData vous donne un pointeur sur le tableau. Vous devez indiquer PPointer(GetReferenceToRawData)^ pour obtenir le tableau actuel à transmettre à DynArraySetLength.

Une fois que vous avez cela, vous pouvez le redimensionner, mais il vous reste une copie. Ensuite, vous devez le remettre sur le tableau d'origine.

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

Dans l'ensemble, il est probablement beaucoup plus simple d'utiliser une liste au lieu d'un tableau. Avec D2010 vous avez Generics.Collections disponible, ce qui signifie que vous pouvez faire un TList<TChildClass> ou TObjectList<TChildClass> et avoir tous les avantages d'une classe de liste sans perdre la sécurité de type.

+0

Merci Mason, je suppose que j'aurais dû enquêter un peu plus avant de demander ... Quoi qu'il en soit; Je me sens plutôt paresseux ces derniers temps, et TList et/ou TObjectList est plus pratique donc je vais faire un tour. :) +1 – conciliator

+0

J'ai juste besoin de la même chose et ça marche bien, mais je reçois une fuite de mémoire chaque fois que j'ajoute un nouvel élément. Quelques idées pour résoudre ce problème? –

+1

Après un certain temps d'enquête, j'ai découvert que TValue.MakeWithoutCopy résout le problème de la fuite de mémoire. –

0

Je pense que vous devez définir le tableau comme un type distinct:

TMyArray = array of TMyClass; 

et utiliser.

D'un ancien sérialiseur XML basé RTTI Je sais la méthode générale que vous utilisez doit travailler (D7..2009 testé):

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

Merci pour l'entrée Ritsaert. Cependant, je pense que Mason a souligné le problème sous-jacent. – conciliator

Questions connexes