Opérations d'exécution sur les types

De RAD Studio
Aller à : navigation, rechercher

Remonter à Utilisation des informations RTTI - Index

En plus d'une introspection de types complète, RTTI offre aussi la possibilité d'opérer sur les variables et les instances des types introspectés. Cette rubrique est une introduction aux méthodes et aux propriétés exposées par les objets RTTI qui permettent la modification à l'exécution de valeurs basées sur leurs informations de type.

Utilisation des champs

Les champs sont des membres de classe ou d'enregistrement qui stockent des informations. Pour accéder aux informations des champs, vous devez d'abord obtenir une instance de TRttiInstanceType ou TRttiRecordType qui décrit la classe ou le type d'enregistrement. En utilisant les méthodes GetField, GetFields et GetDeclaredFields exposées, vous devez obtenir les objets TRttiField qui décrivent les champs individuels. TRttiField expose ensuite un ensemble de méthodes qui permettent de définir et d'obtenir la valeur d'un champ individuel dans une instance du type décrit.

L'exemple suivant illustre comment obtenir des informations sur un champ d'un type classe. En utilisant ces informations, la valeur du champ est lue indirectement, avec RTTI :

 type
     { Définir une classe de test avec 2 champs }
     TTest = class
         FInteger: Integer;
         FString: String;
     end;
 
 var
     LContext: TRttiContext;
     LClass: TRttiInstanceType;
     LVar: TTest;
 
 begin
     { Obtenir le contexte RTTI }
     LContext := TRttiContext.Create;
 
     { Obtenir les informations de type pour la classe TTest }
     LClass := LContext.GetType(TTest) as TRttiInstanceType;
 
     { Créer une nouvelle instance de TTest et initialiser ses valeurs }
     LVar := TTest.Create;
     LVar.FInteger := 10;
     LVar.FString := 'Hello RTTI!';
 
     { Imprimer les valeurs de LVar en utilisant le RTTI }
     Writeln('La valeur de FInteger dans LVar est ', LClass.GetField('FInteger')
       .GetValue(LVar).ToString());
     Writeln('La valeur de FString dans LVar est ', LClass.GetField('FString')
       .GetValue(LVar).ToString());
 
 end.
 #pragma explicit_rtti fields(public)
 
 class TTest : TObject {
 public:
 	int FInteger;
 	String FString;
 };
 
 int _tmain(int argc, _TCHAR* argv[]) {
 	// Obtenir le contexte RTTI
 	TRttiContext context;
 
 	// Obtenir les informations de type pour la classe TTest
 	TRttiType *type = context.GetType(__delphirtti(TTest));
 	TRttiInstanceType *instanceType = dynamic_cast<TRttiInstanceType*>(type);
 	if (instanceType) {
 		// Créer une nouvelle instance de TTest et initialiser ses valeurs
 		TTest *var = new TTest();
 		var->FInteger = 10;
 		var->FString = "Hello RTTI!";
 
 		// Imprimer les valeurs de var en utilisant le RTTI
 		// champ FInteger
 		TRttiField *field = instanceType->GetField("FInteger");
 		if (field) {
 			printf("%ls\n", field->GetValue(var).ToString());
 		}
 
 		// champ FString
 		field = instanceType->GetField("FString");
 		if (field) {
 			printf("%ls\n", field->GetValue(var).ToString());
 		}
 	}
 
 	return 0;
 }

Pour définir la valeur des champs, utilisez la méthode SetValue :

 { Modifier la valeur des champs dans l'instance de LVar }
     LClass.GetField('FInteger').SetValue(100);
     LClass.GetField('FString').SetValue('Hello Again!');

Les deux méthodes GetValue et SetValue opèrent sur un type appelé TValue, qui est une forme légère du type Variant. L'utilisation de valeurs TValue rend le code plus clair ; elle nécessite moins de méthodes et de transtypages de types. TValue effectue automatiquement des transtypages internes pour les valeurs sur lesquelles il opère.

Utilisation des propriétés

D'une certaine façon, les propriétés se comportent exactement comme les champs ; elles transportent aussi une valeur informationnelle. La différence essentielle entre les propriétés et les champs est que les propriétés ne stockent pas nécessairement une valeur informationnelle, mais peuvent utiliser des méthodes pour la générer, si nécessaire. Pour travailler avec les propriétés, vous devez obtenir un objet TRttiInstanceType (qui représentera les informations RTTI de classe), puis utiliser la méthode GetProperty, GetProperties ou GetDeclaredProperties pour opérer sur la propriété spécifique. TRttiProperty expose aussi les méthodes GetValue et SetValue, qui permettent un accès facile à la valeur de la propriété.

En supposant que la classe exemple TTest expose 2 propriétés en lecture/écriture, IntProp et StrProp, le code suivant illustre comment lire ou modifier les valeurs de ces propriétés :

 { Imprimer les valeurs des propriétés, en utilisant le RTTI }
 Writeln('La valeur de IntProp est ', LClass.GetProperty('IntProp').GetValue(LVar).ToString());
 Writeln('La valeur de StrProp est ', LClass.GetProperty('StrProp').GetValue(LVar).ToString());

Pour définir la valeur des propriétés, utilisez la méthode SetValue :

 { Modifier la valeur des champs dans l'instance de LVar }
 LClass.GetField('IntProp').SetValue(100);
 LClass.GetField('StrProp').SetValue('Hello Again!');

Les propriétés d'instances exposent des informations supplémentaires spécifiques aux classes (par exemple, Index ou Default). Pour obtenir ces informations spécifiques, transtypez l'instance de TRttiProperty obtenue en TRttiInstanceProperty (assurez-vous que le type sur lequel vous opérez est une classe).

Utilisation des propriétés indexées

Les propriétés indexées peuvent avoir des informations de type à l'exécution à partir de XE2 (Pulsar). Vous pouvez utiliser les méthodes suivantes pour obtenir les informations RTTI associées :

Pour obtenir et définir la valeur d'une propriété indexée, vous devez fournir :

  • L'instance de classe spécifique (comme pour les propriétés non indexées).
  • Les informations d'index (encapsulées dans un tableau).

Examinez les exemples suivants :

 var
   Inst: TStringList;
   RttiContext: TRttiContext;
   ClassRtti: TRttiType;
   IndexedProperty: TRttiIndexedProperty;
 
 begin
   // ..
 
   ClassRtti := RttiContext.GetType(TStrings);
 
   Inst := TStringList.Create; // Définir l'instance de TStrings
   Inst.Add('CA');
   Inst.Add('NV');
 
   // Afficher la valeur de la propriété indexée (l'index doit être spécifié)
   IndexedProperty := ClassRtti.GetIndexedProperty('Strings');
   WriteLn(IndexedProperty.GetValue(Inst, [0]).AsString); // affiche 'CA'
 
   // ...
 // ...
 
   TRttiType *type = context.GetType(__delphirtti(TStrings));
   TRttiInstanceType *instanceType = dynamic_cast<TRttiInstanceType*>(type);
 
   TStringList *inst = new TStringList(); // Définir l'instance de TStrings
   inst->Add("CA");
   inst->Add("NV");
 
   // Afficher la valeur de la propriété indexée (l'index doit être spécifié)
   TRttiIndexedProperty *indexedProperty = instanceType->GetIndexedProperty("Strings");
   Rtti::TValue index = TValue::From(0); // index 0
   System::OpenArray<Rtti::TValue>params(index);
   printf("%ls\n", indexedProperty->GetValue(inst, params, params.GetHigh()).AsString().c_str()); // affiche "CA"
 
   // ...

Invocation des méthodes

Une des fonctionnalités les plus utiles autorisées par les classes RTTI est l'invocation des méthodes. L'invocation des méthodes permet un appel dynamique à une méthode statique, d'interface, de classe ou d'instance. Pour invoquer des méthodes, obtenez d'abord un objet TRttiMethod qui décrit la méthode que vous voulez invoquer. TRttiMethod expose une méthode appelée Invoke qui pemet d'appeler la méthode dynamiquement, étant donné le pointeur d'instance et un ensemble de paramètres.

Supposons que vous disposez d'une classe TTest déclarée comme suit :

 type
   TTest = class
   public
     procedure WriteString(const AStr: String);
   end;
 
 procedure TTest.WriteString(const AStr: String);
 begin
   { Ecrire seulement la chaîne transmise }
   Writeln(AStr);
 end;
 class __declspec(delphirtti) TTest : public TObject {
 public:
   void WriteString(UnicodeString AStr) {
     // Ecrire seulement la chaîne transmise
     printf("%ls", AStr.c_str());
   }
 };

le code utilisé pour invoquer la méthode WriteString dynamiquement ressemble à ceci :

 { Obtenir les informations de type pour la classe TTest }
 LClass := LContext.GetType(TTest) as TRttiInstanceType;
 
 { Créer une nouvelle instance de TTest }
 LVar := TTest.Create;
 
 { Appeler la méthode WriteString }
 LClass.GetMethod('WriteString').Invoke(LVar, ['Hello World!']);
 // Obtenir les informations de type pour la classe TTest
 TRttiInstanceType *LClass =
   dynamic_cast<TRttiInstanceType*>(context.GetType(__delphirtti(TTest)));
 
 // Créer une nouvelle instance de TTest
 std::unique_ptr<TTest>LVar(new TTest()); // RAII wrapper
 
 // Appeler la méthode WriteString
 Rtti::TValue msg = TValue::From(UnicodeString("Hello World!"));
 System::OpenArray<Rtti::TValue>params(msg);
 LClass->GetMethod("WriteString")->Invoke(LVar.get(), params,
   params.GetHigh());

Si vous modifiez la définition de WriteString, qui devient une méthode de classe au lieu d'une méthode d'instance, les paramètres requis pour l'invocation changent aussi. Maintenant, au lieu d'une instance, le paramètre instance doit être une référence de classe :

 { Appeler la méthode de classe WriteString }
 LClass.GetMethod('WriteString').Invoke(TTest, ['Hello World!']);
 // Aucun équivalent dans C++

Voir aussi