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.
- 4.1.1. Nombres (10)
- 4.1.2. Chaînes de caractères (16)
- 4.1.3. La classe TStream (1)
- Comment obtenir l'adresse d'un objet ?
- Qu'est-ce qu'un pointeur et comment l'utiliser ?
- Comment désactiver l'affichage des exceptions dans Delphi ?
- Comment utiliser les exceptions ?
- Comment optimiser le redimensionnement d'un tableau ?
- Comment récupérer une référence d'objet dans un bloc With ?
- Quid du transtypage et de l'opérateur as ?
- Comment récupérer des informations sur les types génériques ?
L'opérateur @ permet d'obtenir l'adresse de l'objet.
Il suffit de transtyper l'adresse en Cardinal pour afficher le résultat en hexadécimal :
Code delphi : | Sélectionner tout |
IntToHex(Cardinal(@MonObjet),6);
La notion de base de pointeur est assez simple : il s'agit d'une variable qui stocke l'emplacement en mémoire d'un autre élément (qui est le plus souvent une variable).
La mémoire :
Tout d'abord, la mémoire de l'ordinateur peut être représentée comme un nombre très important de cases d'une certaine
taille que l'on appelle un octet. Ces cases, pour que l'ordinateur puisse y accéder, sont repérées par un numéro : leur Adresse.
La mémoire d'un ordinateur est donc en quelque sorte un grand tableau de cases numérotées, ces cases contenant chacune un octet.
Les variables :
Le tableau qu'est la mémoire est rempli par des données qui, parfois, ont besoin d'un octet, de deux, de quatre ou plus, pour pouvoir être représentées : ces données sont appelées variables. Elles peuvent être des caractères, des nombres…
Imaginons une variable déclarée dans un code delphi :
Code Delphi : | Sélectionner tout |
var UnNombre: Cardinal;
Par exemple, les cases n°205, 206, 207 et 208.
Pour obtenir le numéro de la case (donc l'adresse) dans lequel elle se trouve, il existe deux possibilités : @UnNombre ou bien Addr(UnNombre).
L'adresse de UnNombre sera le numéro de la première case qu'il occupe, c'est-à-dire 205.
Exemple :
Code Delphi : | Sélectionner tout |
1 2 3 4 5 6 7 | var UneAdresse: Pointer; UnNombre: Cardinal; begin UneAdresse := @UnNombre; //ou bien UneAdresse := Addr(UnNombre); end; |
Donc, @X signifie Donne-moi l'adresse de X.
Les pointeurs :
Premièrement, un pointeur est une variable, un peu similaire à un entier (c'est criant en C++, mais pas en Delphi). C'est une variable, sur 32 bits (4 octets), qui contient l'adresse d'une case de la mémoire.
Comme c'est similaire à un nombre entier, on peut tout-à-fait imaginer (pour l'instant, on ne fait qu'imaginer !) des
additions et des soustractions et même afficher son contenu (ça marche très bien en c++, mais pas en Delphi, qui "protège" ce genre de choses pour éviter des "accidents").
On dit qu'un pointeur, qui contient en général l'adresse d'une autre variable, "pointe" sur cette autre variable.
Quand la valeur de l'adresse contenue dans le pointeur est égale à zéro, alors ça se traduit en Delphi par UnPointeur = nil.
Maintenant, il peut être intéressant, connaissant une adresse, de pouvoir accéder à son contenu.
En supposant que UnPointeur = 205, UnPointeur^ nous donnera le contenu de ce qui se trouve à la case n°205, c'est-à-dire la valeur de UnNombre.
Donc, X^ signifie Donne-moi la valeur de ce qui se trouve en X.
Variables avancées :
Normalement, une variable d'un certain type (un byte, par exemple, qui prend un octet, donc une case), ne peut cohabiter avec une variable d'un autre type (un char, qui tient lui aussi sur un octet).
Cela peut pourtant être pratique pour transformer, sans faire de calcul, une variable UnByte contenant 65, directement en caractère UnChar qui vaudrait 'A'.
Delphi le permet, tout comme le C++ le permet avec son instruction Union.
La méthode, mal expliquée dans l'aide Delphi, est de déclarer un enregistrement avec une partie variable, comme ceci :
Code Delphi : | Sélectionner tout |
1 2 3 4 5 6 7 | Type TSuperVariable = Record Case b: Boolean of True: (UnByte: Byte); False: (UneChar: Char); end; |
Ensuite, on peut travailler "avec la mémoire" comme suit :
Code Delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | var SuperVariable: TSuperVariable UnMessage: String; begin SuperVariable.AByte := 65; //est équivalent à //SuperVariable.AChar := 'A'; UnMessage := 'Valeur de AByte au format caractère : ' + SuperVariable.AChar); ShowMessage(UnMessage); end; |
Rien n'empêche de faire des SuperVariables plus complexes, comme ceci :
Code Delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | Type TSuperVariable1 = Record Case i: SmallInt of 1: (UnPetitReel: Single); 2: (UnEntierSigne: Integer); 3: (UnEntierNonSigne: Cardinal); 4: (UnPointeur: Pointer); 5: (UnPointeurSurUnEntier: ^Integer); end; |
On remarquera juste que les cinq types (Single, Integer, Cardinal, Pointer, ^Integer), proposent tous des variables de 4 octets. De là, les possibilités sont multiples (et dangereuses, donc attention…).
Addition et soustraction sur des pointeurs :
Pour ce qui est des additions et soustractions avec des pointeurs, c'est possible en déclarant un enregistrement avec une partie variable :
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 | Type TAccesMemoire = Record Case i: Smallint of 1: (UnPointeur: Pointer); 2: (UneAdresse: Cardinal); 3: (UnPointeurSurCaractere: ^Char); 4: (UnPointeurSurByte: ^Byte); end; var Acces: TAccesMemoire; UneChaine: String; i: Integer; UnMessageBizarre: String; begin //Initialisation des données UneChaine := 'bonjour les Delphinautes, comment ça va ? :)'; i := 66; UnMessageBizarre := ''; //on se place, sur le premier caractère de la chaîne : Acces.UnPointeurSurCharactère := @UneChaine[1]; //on remplace le b minuscule par l'équivalent ASCII de ce qu'il y a dans i; Acces.UnPointeurSurByte^ := i; //Affichage ShowMessage(UneChaine); //deux pas en avant, un pas en arrière… (5 fois) for i := 1 to 5 do begin //2 pas en avant Acces.UneAdresse := Acces.UneAdresse + 2; //on récupère le caractère pointé UnMessageBizarre := UnMessageBizarre + Acces.UnPointeurSurCaractere^; //1 pas en arrière Acces.UneAdresse := Acces.UneAdresse-1; //on récupère le caractère pointé UnMessageBizarre := UnMessageBizarre + Acces.UnPointeurSurCaractere^; end; ShowMessage(UnMessageBizarre); end; |
Dans le menu Outils | Options du débogueur, sélectionnez l'onglet Exceptions du langage, puis dans la liste Type d'exceptions à ignorer désactiver la ou les exceptions que vous souhaitez ignorer lors d'une phase de débogage.
Décochez la case Arrêter sur les exceptions Delphi si vous voulez que le débogueur interrompe l'exécution de votre programme lorsque celui-ci déclenche une exception Delphi.
Vous trouverez, sur les URL suivantes, des tutoriels sur la gestion des exceptions :
Les exceptions et la gestion des erreurs sous Delphi, par Alexandre le Grand
La gestion des exceptions et le contrôle d'erreurs sous Delphi, par Tony Baheux
Lorsqu'un tableau dynamique a besoin d'être redimensionné de manière fréquente (par exemple, dans une boucle), il vaut mieux effectuer cette opération de manière « intelligente » pour gagner du temps et éviter de fragmenter la mémoire.
Voilà un code qui récupère tous les handles des composants du projet de type TWinControl pour les placer dans un tableau dynamique :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | procedure TForm1.Button1Click(Sender: TObject); var LArray: array of HWND; I: Integer; begin SetLength(LArray, 0); for I := 0 to Pred(ComponentCount) do if Components[I] is TWinControl then begin SetLength(LArray, Length(LArray) + 1); LArray[Length(LArray) + 1] := TWinControl(Components[I]).Handle; end; end; |
Code delphi : | Sélectionner tout |
1 2 | SetLength(LArray, Length(LArray) + 1); LArray[Length(LArray) + 1] := TWinControl(Components[I]).Handle; |
Pour y remédier, il vaut mieux lorsqu'on connaît la taille que prendra le tableau, effectuer l'allocation une seule fois en début de boucle :
Code delphi : | Sélectionner tout |
1 2 3 4 | L := MyList.Count() ; SetLength(LArray, L) ; For I := 0 to Pred(L) do LArray[I] := ... |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | procedure TForm1.Button1Click(Sender: TObject); var LArray: array of HWND; I, J: Integer; begin J := 0; SetLength(LArray, ComponentCount); for I := 0 to Pred(ComponentCount) do if Components[I] is TWinControl then begin LArray[I] := TWinControl(Components[I]).Handle; Inc(J); end; SetLength(LArray, J); 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 25 | procedure TFoo.Bar const cstGrowBy: Integer = 100; var LList: array of Int64; I, J: Integer; begin SetLength(LList, 0); I := 0; AcquireDriver; try SetSQL('SELECT ID FROM ...); //Requête SQL donnant un nombre N de résultats ExecSelect; while not QueryEOF do //Notre boucle try //Motif d'allocation évoqué dans le paragraphe ci dessus if (I + 1 > Length(LList)) then //Si le nombre actuel d'éléments atteint la taille actuelle SetLength(LList, Length(LList) + cstGrowBy); //On alloue un nouveau bloc LList[I] := GetFieldAsInt64('ID'); //On affecte une valeur au tableau finally Inc(I); QueryNext; end; SetLength(LList, I) ; //On peut exploiter le tableau... |
Cette approche n'est possible qu'à partir de Delphi 2005.
Lors de la construction d'un objet de la manière suivante :
Code delphi : | Sélectionner tout |
1 2 3 4 | with TMOnObjet.Create do begin end; |
Sous Delphi 2005 l'utilisation de Class Helper peut être une solution.
Code delphi : | Sélectionner tout |
1 2 3 4 5 | Type GetterClassHelper=Class Helper For TObject Function WriteClassName:ShortString; Function GetInstance:TObject; end; |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 | begin With TStringList.Create do begin ... Traitement(GetInstance); // récupére une référence sur l'instance ... end; end. |
PS : Sous Delphi 2005 cette évolution du langage n'est pas documentée pour Win32 mais l'est pour Delphi .NET.
Sous Delphi 2006 les assistants de classe sont bien implémentés.
Voici un programme illustrant son utilisation:
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 | program classhelper; {$APPTYPE CONSOLE} uses SysUtils; Type TTest=Class Nom: String; end; GetterClassHelper=Class Helper For TObject Function WriteClassName:ShortString; Function GetInstance:TObject; end; Function GetterClassHelper.WriteClassName:ShortString; begin // Self fait référence au type Test, et non à GetterClassHelper. Result:=Self.ClassName; end; function GetterClassHelper.GetInstance:TObject; begin // Self fait référence à la variable en cours, // et non à une possible instance de type GetterClassHelper. Result:=Self; end; Procedure Visualise(AInstance :TObject; NomVaraible:String); begin Writeln('Nom de la variable : ',NomVaraible); Writeln; Writeln('Instance de la classe '+ TTest(AInstance).WriteClassName+' dans la methode visualise : '+TTest(AInstance).Nom); Writeln; end; Var Premier, Second : TTest; begin Premier:=TTest.Create; Premier.Nom:='Je reference l''instance Premier'; visualise(Premier,'Premier'); // Teste si on récupére bien une référence sur l'instance Premier // équivaut à : // Second:=Premier Second:=TTest(Premier.GetInstance); visualise(Second,'Second'); Readln; Premier.Free; Second:=Nil; // Objectif final With TTest.Create do Try Nom:='Je reference l''instance transitoire dans le bloc With-do'; visualise(GetInstance,' ici elle n''a pas de nom'); Finally Free; end; Readln; end. |
Code delphi : | Sélectionner tout |
1 2 3 4 5 | With TTest.Create do begin Nom:='Je reference l''instance transitoire dans le bloc With-do'; visualise(self,' Dans notre cas self n''existe pas'); end; |
Le transtypage est trop souvent confondu avec l'opérateur as. Régulièrement, on utilise l'un ou l'autre sans trop y faire attention.
Pourtant, chacun a son utilisation propre.
Le transtypage
Pour commencer, le transtypage (du latin trans : à travers), c'est simplement considérer une valeur d'un type donné comme une valeur d'un autre type. La seule contrainte est que ces deux types soient stockés sur le même nombre d'octets en mémoire. Ainsi, n'importe quel TObject peut être transtypé en Single et vice versa, car ils sont tous deux stockés sur 4 octets ; par contre, un TObject ne peut être transtypé en Double (il y aura transtypage incorrect), car Double est stocké sur 8 octets.
Dans le code exécutable produit par le compilateur, il n'y a aucune différence entre une affectation d'un Single à un autre et l'affectation d'un TObject transtypé en Single à un Single. Plus exactement, il n'y a aucune différence entre un Single et un TObject transtypé en Single, tout simplement.
Le seul effet du transtypage est d'empêcher le compilateur de signaler une erreur d'incompatibilité de types.
NB : il possible de "transtyper" un integer en un Byte, ou même un TObject en un Char, alors qu'ils ne sont pas stockés sur le même nombre d'octets. En réalité, ce n'est pas un réel transtypage : les transtypages entre types scalaires entiers (entiers, pointeurs, objets et caractères), ou entre nombres (entiers et floats), de même que les transtypages entre types chaînes, sont compilés spécialement de façon à effectuer une véritable conversion de format. N'oubliez pas que dans le cas où un pointeur est impliqué, cette conversion n'a toutefois aucun sens.
L'opérateur as avec des classes
D'autre part, l'opérateur as. Celui-ci fonctionne différemment selon qu'il est utilisé avec des classes ou des interfaces.
Dans le cas des classes, l'opérateur [as] n'est pas différent du transtypage, il ajoute simplement un test pour vérifier si ce transtypage est valide. Ce test est effectué à l'aide de l'opérateur is. Puis, si le transtypage est valide, il est appliqué, sinon une exception EInvalidCast est générée.
En fait, le code suivant :
Code delphi : | Sélectionner tout |
ClasseEnfant := ClasseParent as TClasseEnfant;
Code delphi : | Sélectionner tout |
1 2 3 4 | if ClasseParent is TClasseEnfant then ClasseEnfant := TClasseEnfant(ClasseParent) else EInvalidCast.Create(ClasseParent.ClassName + ' n''est pas un descendant de '+TClasseEnfant.ClassName); |
Notez aussi que l'opérateur as, avec des classes, peut être utilisé uniquement si ClasseParent est déclarée comme une classe dans la généalogie directe de TClasseEnfant. Par généalogie directe, j'entends les parents, grands-parents, enfants, petits-enfants etc. Mais pas les cousins ou les oncles par exemple…
Utiliser is ou as ?
L'utilisation de l'un ou l'autre amène à des modèles de programmation différents. Avec l'opérateur is, vous devrez utiliser des tests de type if. Avec l'opérateur as, vous devrez encadrer votre code de blocs try et gérer les exceptions qui peuvent être générées.
Dans le cas contraire, vous vous exposez à des risques de plantage brutal de votre programme.
L'opérateur as avec des interfaces
Finalement, l'opérateur as, utilisé avec les interfaces, est l'appel de la méthode QueryInterface avec génération
d'une exception EIntfCastError si la référence ne supporte pas l'interface demandée.
Ainsi, le code suivant :
Code delphi : | Sélectionner tout |
UneInterface := UneAutreInterface as IUneInterface;
Code delphi : | Sélectionner tout |
1 2 | if UneAutreInterface.QueryInterface(GUID_de_IUneInterface, UneInterface) <> 0 then raise EIntfCastError.Create('La référence n''implémente pas l'interface demandée'); |
Contrairement à l'utilisation sur ces classes, l'opérateur as appliqué à des interfaces ne demande pas des interfaces de la même branche généalogique. C'est normal, c'est la philosophie des interfaces : un objet peut implémenter deux interfaces n'ayant aucun rapport l'une avec l'autre.
À propos du portage sous Delphi .NET
Si votre code est destiné à être porté sous .NET, n'utilisez plus de transtypages dits sauvages, tel que integer(UnObjet), car ils ne sont pas autorisés en .NET. Utilisez à la place l'opérateur as.
Avec Delphi 2009 et l'apparition des types génériques (classe ou enregistrement), il faut bien sûr pouvoir avoir des informations sur les types.
Delphi nous fournit donc quelques fonctions utiles par le biais de quelques unités :
- Genericfaults.Des ;
- TypInfo ;
- IntfInfo.
Les fonctions :
- SizeOf : vous la connaissez sûrement, elle permet de récupérer la taille en octets d'un type.
- TypeInfo : permet de récupérer des informations sur le type (type entier, flottant, ordinal, enregistrement, classe, interface...) ;
- GetTypeData : permet de récupérer des informations sur la donnée du type (type byte, word, single, char, string, widechar...) ;
- GetTypeName : permet de récupérer le nom du type (integer, byte, string...) ;
- Default : met la donnée à sa valeur nulle (zero, nil).
Mise en place : au moment de la déclaration de votre classe ou record générique, vous pouvez imaginer cette déclaration :
Code Delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | type TGenericPoint<T> = record public X, Y : T; public function _Size: integer; // renvois la taille du type T function _Info: pTypeInfo; // renvois les infos du type T function _Data: pTypeData; // renvois les infos de la données du type T function _Name: string; // renvois le nom du type T procedure _Zero; // mets à zéro X et Y peu importe le type de T end; TSmallPoint = TGenericPoint<SmallInt>; TPoint = TGenericPoint<Integer>; TPointF = TGenericPoint<Single>; TBigPoint = TGenericPoint<Int64>; TBigPointF = TGenericPoint<Double>; |
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 | function TGenericPoint<T>._Name: string; begin result := GetTypeName(TypeInfo(T)); end; function TGenericPoint<T>._Size: integer; begin result := SizeOf(T) shl 1; end; function TGenericPoint<T>._Info: pTypeInfo; begin result := pTypeInfo(TypeInfo(T)); end; function TGenericPoint<T>._Data: pTypeData; begin result := pTypeData(GetTypeData(_Info)); end; procedure TGenericPoint<T>._Zero; begin X := Default(T); Y := Default(T); end; |
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.