FAQ DelphiConsultez toutes les FAQ
Nombre d'auteurs : 124, nombre de questions : 934, dernière mise à jour : 23 octobre 2024 Ajouter une question
Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums Delphi et Delphi et bases de données de www.developpez.com et de l'expérience personnelle des auteurs.
Nous tenons à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose soient correctes. Les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous souhaitez y apporter des corrections ou la compléter, contactez un responsable (lien au bas de cette page).
Nous espérons que cette FAQ saura répondre à vos attentes. Nous vous en souhaitons une bonne lecture.
L'équipe Delphi de Developpez.com.
- Comment remplacer l'instruction for each de VB ?
- Qu'est-ce que la VMT ou TMV ?
- Qu'est-ce que la DMT ?
- Comment dupliquer un objet ?
- Comment appeler une procédure en connaissant son nom ?
- Comment appeler une méthode en connaissant son nom ?
- Comment affecter la même valeur à plusieurs variables ?
- Comment lister et appeller les méthodes d'une Form ?
- Pourquoi un paramètre const change-t-il mystérieusement de valeur ?
- Vérifier si un entier fait partie d'un tableau
L'instruction For Each permettant l'énumération d'une collection n'est pas disponible en Delphi Win32. Si dans la plupart des cas les collections sont indexables via un ordinal de 1 à Count, certaines ne le sont pas. Il est alors indispensable d'utiliser un énumérateur de collection.
Les collections disposent d'une méthode retournant un objet d'énumération transtypable en IEnumVariant. Cet objet dispose d'une méthode Next permettant d'extraire un certain nombre d'éléments de la collection. Next avance automatiquement dans la collection jusqu'à la fin. Il faut tester la valeur retournée par Next afin de savoir si la fin est atteinte.
La déclaration de IEnumVariant est la suivante :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 | IEnumVariant = interface(IUnknown) ['{00020404-0000-0000-C000-000000000046}'] function Next(celt: LongWord; var rgvar : OleVariant; out pceltFetched: LongWord): HResult; stdcall; function Skip(celt: LongWord): HResult; stdcall; function Reset: HResult; stdcall; function Clone(out Enum: IEnumVariant): HResult; stdcall; end; |
À titre d'exemple, le code suivant permet d'extraire la liste des langues de Word définies dans la collection Languages.
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // Obtention de la liste des langues Langs:=Word.Languages; // On va utiliser l'énumérateur de collection fourni IEnum:=Langs._NewEnum as IEnumVariant; While IEnum.Next(1,Element,Nombre)=S_OK Do Begin // Indispensable ici de passer par un IDisp intermédiaire... IDisp := Element; Langue := IDisp As Language; // MAJ de la liste With Liste.Items.Add Do Begin Caption := IntToStr(Langue.ID); SubItems.Add(Langue.Name); SubItems.Add(Langue.NameLocal); If Langue.ActiveSpellingDictionary=Nil Then SubItems.Add('Non') Else SubItems.Add('Oui'); If Langue.ActiveGrammarDictionary=Nil Then SubItems.Add('Non') Else SubItems.Add('Oui'); End; End; |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | procedure WordlLangage; var Word : Variant; Element: OleVariant; IEnum : IEnumVariant; Langue : Variant; Nombre : LongWord; begin Word := CreateOleObject('Word.Application'); // Obtention de la liste des langues IEnum:=IUnKnown(Word.Languages._NewEnum) as IEnumVariant; // Puis utilisation de l'énumérateur de collection fourni While IEnum.Next(1,Element,Nombre)=S_OK Do Begin // Il est indispensable de transtyper Element Langue := IUnknown(Element) As Language; Writeln(IntToStr(Langue.ID),' ',Langue.Name,' ',Langue.NameLocal); End; Word.Quit; Word := unassigned; end; |
L'interface IEnumVariant (uniquement Delphi 2005 et >), par Laurent Dardenne
Afin de pouvoir appeler la méthode appropriée au moment souhaité, un objet doit s'appuyer sur une liste de ses méthodes virtuelles : la VMT ou Virtual Methods Table (Table des Méthodes Virtuelles). Cette table est mise en place par le constructeur d'un objet dans le cas où il déclare une méthode virtuelle.
Après l'appel du constructeur l'instance reçue est en fait un pointeur vers une zone mémoire qui contient d'abord l'adresse de la VMT de la classe, puis les adresses des champs de sa classe de base, stockées dans l'ordre de leur déclaration comme une suite contiguë de variables.
Les champs sont toujours alignés et correspondent à un type unpacked record. Tous les champs hérités d'une classe ancêtre sont stockés avant les nouveaux champs définis dans la classe descendante, la dernière adresse contient donc le dernier champ déclaré dans la classe.
Aux offsets positifs une VMT se compose d'une liste de pointeurs de méthodes, un par méthode virtuelle définie par l'utilisateur, dans l'ordre de déclaration du type de la classe.
Aux offsets négatifs, une VMT contient un certain nombre de champs utilisés en internes.
On peut donc dire que le type d'un objet est un pointeur sur la VMT.
Toute classe possède sa propre VMT, conservant toujours un lien avec la VMT de son ancêtre (hiérarchie de classe). Il y a donc une copie de la VMT chaque fois que vous héritez d'une classe.
La classe TObject déclare plusieurs méthodes et une propriété cachée pour stocker une référence à la classe de l'objet. Cette propriété contient un pointeur vers la VMT de la classe. Chaque classe a une VMT unique et tous les objets de cette classe partagent la VMT de la classe.
C'est-à-dire que l'héritage d'une classe à partir d'une autre implique l'héritage de la VMT complète de cette dernière, les nouvelles méthodes étant ajoutées en fin de table.
La VMT d'un objet contient toutes les méthodes virtuelles de ses ancêtres comme celles qu'il déclare, les méthodes virtuelles emploient donc plus de mémoire que des méthodes dynamiques, bien qu'elles s'exécutent plus rapidement.
Lorsqu'un appel à une méthode virtuelle est effectué, l'objet recherche dans sa VMT s'il trouve la méthode recherchée. Si c'est le cas, alors il utilise l'adresse enregistrée et exécute la méthode. Sinon, il parcourt la VMT de son ancêtre direct et ainsi de suite jusqu'à l'ancêtre le plus éloigné dans la hiérarchie.
De même, lorsqu'une méthode surchargée fait appel à la méthode ancêtre, une recherche est effectuée en partant cette fois-ci de la VMT du premier ancêtre.
Ceci est dû au fait qu'une méthode virtuelle est liée au moment de son exécution et pas au moment de la compilation.
La VMT est détruite par le destructeur lorsque celle-ci n'a plus lieu d'exister.
Une référence de classe est implémentée comme un pointeur vers la VMT de la classe.
Traduction d'un tableau extrait d'un article de Brian Long :
Offset | Nom de constante | Description |
-76 | vmtSelfPtr | Adresse de la 1ère entrée de la VMT si elle existe sinon du nom de classe * |
-72 | vmtIntfTable | Adresse de la table des interfaces implémentées * |
-68 | vmtAutoTable | Adresse de la section des 'class automated' (Delphi 2) * |
-64 | vmtInitTable | Adresse de la table des champs requérant une initialisation * |
-60 | vmtTypeInfo | Adresse des informations RTTI * |
-56 | vmtFieldTable | Adresse de la table des champs publiés (published) * |
-52 | vmtMethodTable | Adresse de la table des méthodes publiées (published) * |
-48 | vmtDynamicTable | Adresse de la DMT (table des méthodes dynamic) * |
-44 | vmtClassName | Adresse du nom de classe, chaîne de caractères/string |
-40 | vmtInstanceSize | Taille en octet d'une instance d'objet |
-36 | vmtParent | .Adresse de la VMT de la classe ancêtre * |
-32 | vmtSafeCallException | Adresse de la méthode virtuelle, SafeCallException * |
-28 | vmtAfterConstruction | Adresse de la méthode virtuelle, AfterConstruction |
-24 | vmtBeforeDestruction | Adresse de la méthode virtuelle, BeforeDestruction |
-20 | vmtDispatch | Adresse de la méthode virtuelle, Dispatch |
-16 | vmtDefaultHandler | Adresse de la méthode virtuelle, DefaultHandler |
-12 | vmtNewInstance | Adresse de la méthode virtuelle, NewInstance |
-8 | vmtFreeInstance | Adresse de la méthode virtuelle, FreeInstance |
-4 | vmtDestroy | Adresse du destructeur virtuel, Destroy |
La VMT contient l'adresse des méthodes virtuelles (déclarées avec la directive virtual ou override) mais elle n'inclut pas les adresses définies dans TObject (SafeCallException, AfterConstruction, BeforeDestruction, expédition, DefaultHandler, NewInstance, FreeInstance et le destructeur destroy) qui sont renseignées avant la VMT.
Au lieu d'employer la directive virtual, vous pouvez également employer la directive dynamic. La sémantique est identique, mais l'implémentation est différente. La recherche d'une méthode virtuelle dans une VMT est rapide car le compilateur génére un index directement dans la VMT.
Pour terminer sachez que la directive override remplace, dans la VMT, l'adresse originale de la méthode concernée par l'adresse de cette nouvelle méthode.
Voir aussi le source de l'unité System.
Puisque toutes les VMT doivent être stockées dans le même segment de données, les objets créés dans des DLL sont accompagnés de leur propre VMT mais elles sont stockées dans le segment de données de la DLL.
Les méthodes dynamiques sont fondamentalement des méthodes virtuelles mais avec un système de répartition (dispatching system) différent.
La différence est que la DMT (Dynamic Method Table ou Table dynamique de méthode) n'est pas complètement copiée à chaque fois que vous instanciez un objet.
Le compilateur affecte un nombre unique à chaque méthode dynamique et associe ces nombres avec des adresses de méthodes afin de construire un tableau dynamique de méthodes (DMT).
À la différence de la VMT, la DMT d'un objet contient seulement les méthodes dynamiques qu'il déclare, et cette méthode se fonde sur la DMT de son ancêtre pour le reste de ses méthodes dynamiques.
Cette approche fait que les DMT sont plus lentes que les VMT mais réduit leur occupation mémoire en évitant de recopier la VMT dans son intégralité à chaque fois que vous héritez d'un objet.
Elles prennent plus de temps lors de l'appel parce qu'il se peut que vous deviez parcourir les DMT de plusieurs ancêtres avant de trouver l'adresse d'une méthode dynamique particulière.
Quand une méthode dynamique est appelée elle est d'abord recherchée dans la DMT de l'objet lui-même. Si elle n'est pas trouvée elle est recherchée dans la DMT de son ancêtre direct et ainsi de suite.
L'utilisation de méthodes dynamiques ne prend son sens que dans quelques cas spéciaux (objets avec beaucoup de méthodes dynamiques/virtuelles, par exemple une centaine, et ayant de nombreux héritages).
En général vous devriez vous en tenir à l'utilisation de méthodes virtuelles.
Citation de Holger Lembke :
Les DMT (Dynamic Methods Table) sont en ceci différentes qu'elles ne référencent plus toutes les méthodes des ancêtres. La DMT est incluse dans la VMT (voir le champ "Adresse de la DMT" dans la structure de la VMT)
La DMT se présente ainsi :
- Pointeur sur la DMT précédente (longint)
- index (longint)
- offset (longint)
- Nombre de méthodes dans la DMT (word)
- 1. Index méthode (word)
- …
- …
- n. Index méthode (word)
- 1. Pointeur méhode (longint)
- …
- …
- n. Pointeur méthode (longint)
Pour le stockage d'une méthode on utilise en gros la procédure suivante :
Chaque méthode reçoit un numéro. Dans la DMT apparaît d'abord le nombre de méthodes stockées, puis leurs index et enfin leurs pointeurs.
Ainsi, si l'on a besoin d'une méthode, on connait son numéro. Avec celui-ci on cherche l'index de la méthode dans la DMT et ensuite on trouve son pointeur.
Exemple : le numéro de la méthode a été trouvé à l'index 3 -> Son pointeur se trouve en "3. Pointeur méthode"
En Delphi, il n'existe pas de moyen natif pour dupliquer un objet. Pour remédier à ce problème, il existe une classe, TPersistent, dont descendent toutes les classes susceptibles de devoir être dupliquées.
TPersistent est une classe abstraite enfant de TObject et parente de TComponent, TStrings et autres classes bien connues de la VCL.
Cette classe déclare une méthode Assign virtuelle ( virtual) publique et une méthode AssignTo protégée ( protected). L'implémentation de Assign dans TPersistent appelle la méthode AssignTo de l'objet en paramètre avec Self en paramètre. La méthode AssignTo déclenche une exception EConvertError avec comme message "Impossible d'assigner %s à %s" (avec les %s les noms des classes de source et de destination).
À partir de là, il est possible, lorsqu'on crée une classe descendante de TPersistent, de sucharger (override) ces méthodes afin d'assigner correctement un objet à un autre.
Par exemple, si vous voulez créer une classe TVoiture dont les objets puissent être dupliqués, il faut la faire hériter de TPersistent, et surcharger la méthode Assign afin de faire l'assignation :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 | procedure TVoiture.Assign(Source : TPersistent); begin if Source is TVoiture then with TVoiture(Source) do begin Self.FMarque := FMarque; Self.FCouleur := FCouleur; end else inherited; end; |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | procedure TAutreVoiture.Assign(Source : TPersistent); begin inherited; if Source is TAutreVoiture then with TAutreVoiture(Source) do begin Self.FCylindree := FCylindree; Self.FCarburant := FCarburant; end; end; |
Les procédures n'étant pas des méthodes d'objet il n'est pas possible d'utiliser RTTI pour en trouver les adresses. Cependant il existe une ruse pour forcer le compilateur à garder une trace des noms des procédures et fonctions voulues afin que l'on puisse les appeler par leur nom.
Cette méthode consiste à exporter les noms de procédure comme on le ferait dans une dll. Une fois les fonctions exportées on utilise la même méthode pour les appeler que la méthode utilisée pour l'appel d'une fonction de dll.
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | Function Additionner(x, y: Integer): Integer; Begin Result:=x+y; End; Function Soustraire(x, y: Integer): Integer; Begin Result:=x-y; End; Function Multiplier(x, y: Integer): Integer; Begin Result:=x*y; End; Exports Additionner,SousTraire,Multiplier; procedure TForm1.Button1Click(Sender: TObject); var H: HModule; F: Function(X,Y:Integer):Integer; begin H := GetModuleHandle(nil); if H <> 0 then begin F := GetProcAddress(H, PChar(Edit1.Text)); if Assigned(F) then ShowMessage(IntToStr(F(SpinEdit1.Value,SpinEdit2.Value))) Else ShowMessage('Fonction non trouvée !'); End; // SetWindowLong(Memo1.Handle,GWL_STYLE,GetWindowLong(Memo1.Handle,GWL_STYLE)Or ES_UPPERCASE); end; |
Lorsque le code source est compilé, chaque méthode créée occupe un emplacement bien précis en mémoire. Pour appeler une méthode donnée, il faut donc pouvoir récupérer l'adresse de cette dernière en mémoire : ceci se fait à l'aide de MethodAddress.
Cependant, la signature de la fonction est requise pour pouvoir l'appeler (dans le cas des surcharges, il ne sera pas possible par exemple de définir quelle est la fonction à appeler).
Le code suivant nous permet d'appeler une méthode (NouvelleMethode) qui reçoit deux entiers en paramètre et retourne un entier par son nom :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Var Meth : Function(x,y:Integer):Integer Of Object; //on définit le prototype de la fonction à appeler Begin // La méthode est cherchée par son nom @Meth :=MethodAddress('NouvelleMethode'); If @Meth <> Nil Then Begin // Si l'adresse est trouvée elle est appelée normalement Resultat.Caption := (IntToStr(Meth(A.Value,B.Value))); End Else Begin // Sinon erreur ShowMessage('Nom de méthode introuvable !'); End; End; |
Afin d'affecter la même valeur à plusieurs variables, nous allons utiliser la notion de tableau ouverts.
Un tableau ouvert nous permet de construire un tableau d'élément sans spécifier ses dimensions. Nous utilisons les primitives Low et High pour connaître ses dimensions.
Nous utiliserons une procédure d'affectation par type de variable; c'est à dire une procédure qui prendra en paramètre la valeur à affecter ainsi qu'un tableau de variables pouvant accepter cette valeur.
Pour les entiers, ce sera par exemple :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 | procedure SetMultiValueInteger(AValue:integer;T:Array of Integer); var i:Integer; begin for i:=Low(T) to High(T) do T[i]:=AValue; end; |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 | procedure SetMultiValueString(AValue:string;T:Array of string); var i:Integer; begin for i:=Low(T) to High(T) do T[i]:=AValue; end; |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 | procedure SetMultiValueBoolean(AValue:boolean;T:Array of string); var i:Integer; begin for i:=Low(T) to High(T) do T[i]:=AValue; end; |
Pour tester notre code, il nous suffira alors d'écrire quelque chose comme :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 | var i1,i2,i3:Integer; s1,s2,s3,s4,s5:string; b1,b2:boolean; begin SetMultiValueInteger(5,[i1,i2,i3]); SetMultiValueString('Toto',[s1,s2,s3,s4,s5]); SetMultiValueBoolean(True,[b1,b2]); end; |
Il est possible de retrouver une méthode ou une procédure en fonction de son nom, mais parfois on aimerait retrouver l'ensemble des méthodes d'une fiche. À l'aide des RTTI il est possible de récupérer des informations d'une classe.
Voici comment procéder, le résultat est mis dans une TStringGrid.
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | procedure TForm1.GetObjectInfo(Objet: TObject); var PData : PTypeData; PListe : PPropList; NbProps, I : Integer; Method : TMethod; iPosGrid : integer; begin PData := GetTypeData(PTypeInfo(Objet.ClassInfo)); NbProps := PData^.PropCount; New(PListe); GetPropInfos(PTypeInfo(Objet.ClassInfo), PListe); SortPropList(PListe,NbProps); with StringGrid1 do begin ColCount := 3; RowCount := NbProps + 1; for i := 0 to RowCount -1 do Cells[1,i] := ''; iPosGrid := 1; for I := 0 to NbProps -1 do begin Cells[0,iPosGrid] := PListe^[I]^.Name; Cells[2,IposGrid] := PListe^[I]^.PropType^.Name; case PListe^[I]^.PropType^.Kind of tkInteger: begin if UpperCase(PListe^[I]^.PropType^.Name) = 'TCOLOR' then Cells[1,iPosGrid] := IntToHex(GetOrdProp(Objet, PListe^[I]),6) else Cells[1,iPosGrid] := IntToStr(GetOrdProp(Objet, PListe^[I])); end; tkString,tkWString,tkLString : Cells[1,iPosGrid] := GetStrProp(Objet, PListe^[I]); tkFloat : Cells[1,iPosGrid] := FormatFloat('0.0000000',GetFloatProp(Objet, PListe^[I])); tkEnumeration : begin if UpperCase(PListe^[I]^.PropType^.Name) = 'BOOLEAN' then Case GetOrdProp(Objet, PListe^[I]) of 0: Cells[1,iPosGrid] := 'False'; 1: Cells[1,iPosGrid] := 'True'; else Cells[1,iPosGrid] := 'Inconnue'; end; end; tkMethod : begin Method := GetMethodProp(Self, PListe^[I]); if Method.Code <> NIL then Cells[1,iPosGrid] := ' (' + TObject(Method.Data).MethodName(Method.Code) + ')'; end; tkUnknown: Cells[1,iPosGrid] := 'Inconnue'; end; // case inc(iPosGrid); end; end; Dispose(PListe); end; |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 | tkMethod : begin Method := GetMethodProp(Self, PListe^[i]); With Method begin if Code <> NIL then Cells[1,iPosGrid] := ' (' +TObject(Data).MethodName(Code) + ')'; end; end; |
Dans le cas d'un gestionnaire d'événement on doit transtyper le résultat obtenu avec le prototype de la méthode à appeler :
Code delphi : | Sélectionner tout |
1 2 | type TTraitement= procedure (S:String) of object; |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | procedure AfficheDetails(Informations : TPropInfo); var TypePropriete : String; DonneePropriete: PTypeData; Method : TMethod; Resultat : String; ... //Exécute le code associé à la propriété Method := GetMethodProp(UnObjet, Informations.Name); if Method.Code <> NIL then //UnObjet.OnTraitement:=Nil begin Resultat:=''; TTraitement(Method)(Resultat); Writeln(Resultat); end; |
Les RTTI ne fonctionnent complètement que sur les classes dérivées de TPersistent.
Voici la situation : vous avez implémenté une routine ou méthode avec un paramètre (appelé Param) déclaré const, et vous remarquez en pas-à-pas la chose suivante. Un appel à une autre routine/méthode, sans même passer Param en paramètre, modifie Param !
L'explication rationnelle du problème (si si, il y en a une) : voir lien ci-dessous.
Pour savoir si une chaîne de caractères fait partie d'un tableau, on utilise la fonction MatchStr ou MatchText fournies par l'unité System.StrUtils.
Voici une fonction très courte pour faire l'équivalent sur un tableau d'entiers :
Code Delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 | uses System.Generics.Collections; function MatchInt(Valeur: NativeInt; Tableau: array of NativeInt; out Idx: Integer): Boolean; begin TArray.Sort<NativeInt>(Tableau); Result := TArray.BinarySearch<NativeInt>(Tableau, Valeur, Idx); end; |
Exemple :
Code Delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | var Idx: Integer; I: array of NativeInt; begin SetLength(I, 8); I := [25, 14, 1, 2, 3, 23, 45, 78]; if MatchInt(78, I, Idx) then ShowMessage(Idx.ToString); |
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.