-
PREAMBULE
Une variable de type Variant peut prendre n'importe quel
type. Cela constitue un moyen commode de manipuler une variable sans
connaître son type au moment de la compilation. ATTENTION, cette
commodité se paie au détriment d'un code plus volumineux et plus
lent. Toutes les opérations sont autorisées par le compilateur,
ce qui fait que la majorité des erreurs apparaissent en
exécution. Il faut donc les utiliser avec parcimonie.
Cet article se propose de présenter les
nouveautés introduites dans Delphi 6 en particulier la
possibilité de définir ses propres types de variables.
L'exemple est donné dans le paragraphe 4.
-
RAPPEL SOUS DELPHI 5
D'abord quelques exemples
Le
compilateur détecte une incompatibilité en '12' et 1 |
var I : integer ;
begin I := '12' + 1 ; end ; |
Ce code
est accepté par le compilateur et il fonctionne correctement. En effet,
'12' peut être transformé en 12 et ajouté à 1 pour
être stocké dans I. |
var I : integer ;
V1, V2 : Variant ; begin V1 := '12' ; V2 := 1 ; I := V1
+ V2 ; end ; |
Ce code
est accepté par le compilateur et il fonctionne correctement. En effet,
1 peut être transformé en '1' et ajouté à '12' pour
être stocké dans I. |
var I : String ;
V1, V2 : Variant ; begin V1 := '12' ; V2 := 1 ; I := V1 + V2
; end ; |
A
l'inverse de l'exemple 2, le code bien qu'accepté par le compilateur
provoque une exception. En effet, '12' + 1.2 donne 13.2 qui ne peut pas
être stocké dans un I qui est un entier. La solution pour faire
fonctionner ce code est de transformer I en Real. |
var I : integer ;
V1, V2 : Variant ; begin V1 := '12' ; V2 := 1.2 ; I := V1 +
V2 ; end ; |
Le
compilateur détecte en ligne une incompatibilité en I et la
valeur attendue par ShowMessage |
var I : Integer ;
begin I := 1 ; ShowMessage (I) ; end : |
Ce code
est accepté par le compilateur et il fonctionne correctement. |
var V1 : Variant ;
begin V1 := 1.235 ; ShowMessage (V1) ; end : |
Ces quelques exemples nous permettent ces quelques
constations :
- Les variants sont automatiquement transtypés
dans le type qui convient le mieux au moment de l'exécution. C'est par
exemple ce qui s'est passé dans les exemples 2, 3, 4 et 6.
- Dans l'exemple 2,
l'opérateur + a donné lieu à une addition d'entiers, dans
l'exemple 3, l'opérateur + a donné lieu
à une addition (concaténation) de chaînes. Le compilateur a
donc implémenté toutes les possibilités d'addition car
rappelons le, le compilateur ne savait pas l'avance quel type d'addition il
allait avoir à faire.
-
NOUVEAUTES DE DELPHI 6
Cette partie ne va pas évoquer toutes les
nouvelles fonctionnalités concernant les variants dans Delphi 6 mais
uniquement la possibilité de créer des nouveaux types.
-
Mécanismes d'évaluation
d'expression
Tout le problème lors de la création
d'un nouveau type est de définir le comportement du type au sein d'une
expression arithmétique. Par exemple
- 1+2.3 est une addition de réels
- alors que 1+2 est une addition d'entiers
Deux constations :
- Dans ces deux expressions, l'opérateur +
a donné lieu a deux sortes d'addition.
- Dans la première expression, le 1 a
été transformé en réel.
Note : Pour évaluer une
expression, il est classique de parler d'opérateur unaire (not, -
(changement de signe)), d'opérateur binaire (+, -, *, /, div,
mod, shl, shr, and, or, xor) et aussi d'opérateur de comparaison
(=, <, <=, =>, >, <>). Pour un opérateur binaire, on
distingue l'opérande gauche de celle de droite.
D'une manière générale, les
opérateurs binaires ne sont pas symétriques ; ainsi A op B
n'est pas forçément équivalent à B op A.
Pour comprendre comment une expression de Variant
est codé en Delphi, examinons le code suivant : var
V1, V2 : MonTypeVariant ;
S : string ;
begin
S := V1 + V2 ;
end ;
En suivant pas à pas, le code on fait
apparaître les étapes suivantes :
- V1 est copié dans VT (objet temporaire
alloué par le compilateur) par appel à _VarCopy. Avant
d'être copié, VT est vidé par appel à
_VarClear
- Appel de _VarAdd pour additionner la
partie gauche et droite, ie, VT et V2
- Enfin, VT est copié dans S après
transtypage.
A chaque opération, le code a trouvé
comment vider, copier, ajouter et même transtyper ces variants.
On le voit, pour que le nouveau type MonTypeVariant
puissent être opérationnel comme n'importe quel autre type
variant, il faut implémenté un certains nombre de routines de bas
niveaux :
- initialisation
- copie
- opérateurs unaires
- opérateurs binaires
- opérateurs de comparaisons
- transtypage
Remarque Les variants permettent aussi de
référencer des objets automation. Sur ce type de variant, il est
possible de désigner des propriétés ou des méthodes
en utilisant la notion pointée classique : var
Word: Variant;
begin
Word := CreateOleObject('Word.Basic');
Word.FileNew('Normal');
Word.FileSaveAs(Password := 'secret', Name := 'test.doc');
Word.Visible := False ;
end ;
Lors de l'implémentation d'un nouveau type
Variant, il sera donc possible de décrire, en plus des
opérateurs, les méthodes, fonctions ou propriétés
accessible sur ce nouveau type. Le type se comportera alors comme une classe
!
Conclusion Les possibilités
offertes par Delphi 6 pour définir un nouveau type sont
extrêmement importantes. Elles permettent de se rapprocher des
possibilités du C++ où la surcharge d'opérateur existe
depuis bien longtemps sur les classes.
-
Implémentation dans Delphi 6
- Nouvelles classes de Delphi 6
L'implémentation dans Delphi d'un nouveau
type fait intervenir les classes suivantes :
TCustomVariantType C'est la classe de base pour
définir un nouveau type. Tous les mécanismes de copie,
d'initialisation, et de transtypage sont déclarées sous forme de
méthodes virtuelles, voire abstraites. De même, les
opérateurs unaires, binaires et de comparaison apparaissent sous forme
de méthodes. La création d'un objet de cette classe
conduit à un recensement du nouveau type dans la liste
LVarTypeSync. Cette liste sera utilisée dans toutes les
méthodes _VarXXX pour traiter les nouveaux types.
TInvokeableVariantType Cette classe implémente
les Variants avec méthodes et propriétés.
TPublishableVariantType Cette classe implémente
les Variants qui peuvent être stockés sous forme de classe en
rendant transparent l'accès des propriétés
PUBLIEES de cet objet.
On utilisera une des trois classes suivant le
nouveau type de Variant que l'on souhaite définir.
- Exemple fourni avec Delphi 6 : Les nombres
complexes
L'unité VarCmplx fournie
l'implémentation des nombres complexes sous forme de Variant.
Ainsi le code suivant devient valide : var
FA, FB: Variant;
begin
FA := VarComplexCreate (1, 2);
FB := VarComplexCreate ('4+0.5i');
ShowMessage (FA+FB);
end;
Avec cette implémentation, les nombres
complexes deviennent des types comme les autres. L'opérateur + est par
exemple autorisé et le transtypage en chaîne est automatique.
Cette implémentation fournie une
très bonne base de départ pour implémenter ses propres
types.
-
EXEMPLE D'UN VARIANT
"TStringList"
Avec notre variant, le code suivant devra être
valide : var
FA, FB, FC: Variant ;
b: Boolean ;
begin
FA := VarStringListCreate ; // Création d'un variant FA
FA.Duplicates := True ;
FA := FA + 1 ; // Ajout de la chaîne '1' à la liste FA
FA := FA + '5' ; // Ajout de la chaîne '5' à la liste FA
FB := VarStringListCreate ;
FB := FA ; // Recopie de FA dans FB
FB := FB - 1 ; // Suppression de la chaîne '1' dans FB
b := FA < FB ; // Comparaison de deux listes
ShowMessage (FA+FB); // Concaténation des listes et affichage sous forme
de chaîne
end;
L'objectif de ce paragraphe est donc de définir
la classe TStringListVariantType qui va prendre en charge notre nouveau
variant. Comme le contenu de ce nouveau variant sera stocké une
TStringList, on définira la classe TStringListVariantType
à partir de la classe TPublishableVariantType.
Dans le cadre de cette présentation,
l'exemple qui sera donné se limitera à quelques
fonctionnalités.
Fonctionnalités
attendues |
Implémentation |
Toutes les propriétés publiques d'un
TStringList seront accessibles. |
Définition de la classe
TVStringList. |
L'opérateur + (resp. -) permettra d'ajouter
(resp. de supprimer) un élément ou une liste. |
Surcharde des méthodes
BinaryOp (pour réaliser l'opération) et
RightPromotion (pour vérifier l'opérande à droite
de l'opérateur). |
Les opérateurs de comparaison (<, <=,
= >=, >) permettront de comparer la longueur de deux listes. |
Surcharge de la méthode
CompareOp. |
Transtypage en chaîne. |
Surcharge de la méhodeCastTo
(utilisation de la propriété CommaText). |
Copie entre listes |
Surcharge de la méthode Clear. |
Suppression, tests |
Surcharge des méthodes Clear et
IsClear. |
Stockage du variant |
Définition d'un structure
TStringListVarData de type TVarData : TStringListVarData
= packed record VType: TVarType; Reserved1, Reserved2, Reserved3:
Word; VStringList: TVStringList; Reserved4: LongInt;
end; |
Création d'un variant liste
vide. |
Création de la fonction
VarStringListCreate. |
L'exemple complet est donné dans le fichier
uVStringList.pas.
Pour être complet, il faudrait surcharger la
méthode LeftPromotion pour valider les opérandes à
gauche, StreamIn et StreamOut pour pouvoir lire et écrire
le variant depuis un flux, etc...
Bon amusement...
Laurent Pelou |