Les premiers pas
Le Delphi est une évolution moderne et orientée objet du Pascal traditionnel. Bien qu'il ne constitue pas un sur-ensemble parfaitement fidèle du Pascal standard défini par l'ISO, il conserve une forte compatibilité avec les bases du langage de programmation. Si vous avez étudié le Pascal pendant vos années scolaires, la transition vers Delphi vous semblera naturelle, car les extensions qu'il propose s'intègrent harmonieusement à la structure initiale du langage de programmation. Cependant, Delphi ne se limite pas à être une version «améliorée» du langage de programmation Pascal ; il s'agit d'un environnement puissant introduisant des fonctionnalités avancées, tout en maintenant une courbe d'apprentissage raisonnable.
Le Delphi se distingue par son intégration d'outils orientés objet, enrichissant considérablement les capacités du langage de programmation. Parmi ces ajouts, on trouve le support des classes et des objets, une gestion robuste des exceptions, et des outils pour la programmation multi-processus léger, permettant une exploitation plus efficace des ressources matérielles. En outre, Delphi prend en charge la programmation modulaire, ainsi que des mécanismes de liaison dynamique et statique. Il s'avère également idéal pour automatiser des tâches via OLE, facilitant ainsi l'interopérabilité entre différentes applications Windows. Ces fonctionnalités ne rendent pas le langage inutilement complexe, mais élargissent ses possibilités, en le rendant apte à gérer des projets modernes et sophistiqués.
Cette page s'adresse principalement à ceux maîtrisant déjà le Pascal traditionnel ou ses dérivés populaires, comme Object Pascal. Si vous avez une expérience préalable avec Object Pascal dans des environnements comme Turbo Pascal de Borland, il sera nécessaire d'adopter un nouveau modèle d'objet et de découvrir les nombreuses extensions proposées par Delphi. Ces nouvelles capacités permettent une programmation plus intuitive et productive, tout en ouvrant la voie à des approches modernes, telles que la conception orientée objet et la gestion modulaire des projets.
Il est important de noter que Borland utilise le terme «Object Pascal» pour désigner le langage utilisé dans Delphi. Cependant, ce terme n'est pas exclusif à Borland, ce qui peut prêter à confusion. De nombreux autres langages de programmation revendiquent également l'appellation «Object Pascal», bien qu'ils présentent des caractéristiques différentes. Cela crée parfois des malentendus, mais Delphi reste un choix unique et puissant, intégrant son propre modèle objet et ses extensions distinctes pour répondre aux besoins des développeurs modernes.
Les unités
Le Delphi est un langage de programmation modulaire et le module de base est appelé unité. Pour compiler et lier un programme Delphi, vous avez besoin d'un fichier source de programme et d'un nombre quelconque d'unités supplémentaires sous forme source ou objet. Le fichier source du programme est généralement appelé fichier source de projet car le projet peut être un programme ou une bibliothèque, c'est-à-dire une bibliothèque liée dynamiquement (DLL).
Lorsque Delphi lie un programme ou une bibliothèque, il peut lier statiquement toutes les unités dans un seul fichier .exe ou .dll, ou il peut lier dynamiquement les unités se trouvant dans des paquets. Un paquet est un type spécial de DLL contenant une ou plusieurs unités et une logique supplémentaire permettant à Delphi de masquer les différences entre une unité liée statiquement et une unité liée dynamiquement dans un paquet.
Formulaires et fichiers
Certaines unités représentent des formulaires. Un formulaire est le terme utilisé par Delphi pour désigner une fenêtre que vous pouvez modifier avec le générateur d'interface utilisateur graphique de Delphi. La description d'un formulaire est entreposée dans un fichier .dfm, contenant la mise en page, le contenu et les propriétés du formulaire.
Chaque fichier .dfm est associé à un fichier .pas, contenant le code de ce formulaire. Les formulaires et les fichiers .dfm font partie de l'environnement de développement intégré (IDE) de Delphi, mais ne font pas partie du langage formel Delphi. Néanmoins, le langage comprend plusieurs fonctionnalités existant uniquement pour prendre en charge l'IDE de Delphi et les descriptions de formulaire.
Un fichier binaire .dfm est en fait un fichier .res (ressource Windows) 16 bits, conservant la compatibilité avec la première version de Delphi. Les versions 2 et ultérieures ne produisent que des programmes 32 bits, donc l'éditeur de liens de Delphi convertit automatiquement la ressource .dfm en ressource 32 bits. Ainsi, les fichiers binaires .dfm sont généralement compatibles avec toutes les versions de Delphi. A partir de Delphi 5, il prend également en charge les fichiers texte .dfm. Ces fichiers sont du texte brut et ne sont pas compatibles avec les versions antérieures de Delphi, du moins pas sans reconversion au format binaire. La seule façon de savoir si un fichier .dfm est binaire ou texte est d'ouvrir le fichier et d'en vérifier le contenu. Un moyen simple de le faire par programmation est de tester les trois premiers octets, étant toujours $FF $0A $00 dans un fichier binaire .dfm.
Le tableau suivant décrit brièvement les fichiers que vous êtes susceptible de trouver dans Delphi et à quoi ils servent. Les fichiers marqués «(IDE)» ne font pas partie de Delphi, mais sont utilisés par l'IDE.
Extension | Description |
---|---|
.bpg | Groupe de projet (IDE) |
.bpl | Paquet compilé (type spécial de DLL) |
.cfg | Options pour le compilateur de ligne de commande |
.dcp | Informations compilées sur le paquet, nécessaires pour établir un lien avec un paquet |
.dcr | Ressource bitmap de composant (IDE) |
.dcu | Code objet de l'unité |
.dfm | Description du formulaire (IDE) |
.dof | Fichier d'options du projet (IDE) |
.dpk | Fichier source pour la construction d'un paquet |
.dpr | Fichier source principal d'un programme ou d'une bibliothèque |
.drc | Script de ressources pour les déclarations de chaînes de ressources |
.dsk | Disposition du bureau (IDE) |
.pas | Code source de l'unité |
.res | Ressource Windows (chaque .dpr a un fichier .res associé) |
Séparer l'interface de l'implémentation
Une unité est composée de deux parties : l'interface et l'implémentation. La partie interface déclare les types, les variables, les constantes et les routines visibles par les autres unités. La section implémentation fournit les éléments essentiels des routines déclarées dans la section interface. La section implémentation peut contenir des déclarations supplémentaires qui sont privées pour l'implémentation de l'unité. Ainsi, les unités sont le principal moyen de dissimulation d'informations de Delphi.
Une unité peut utiliser une autre unité, c'est-à-dire importer les déclarations de cette autre unité. Une modification de l'interface d'une unité nécessite une recompilation de toutes les unités utilisant la déclaration modifiée dans l'unité modifiée. Le compilateur de Delphi gère cela automatiquement, vous n'avez donc pas besoin d'utiliser des makefiles pour compiler des projets Delphi.
Vous pouvez utiliser une unité dans la section interface ou implémentation, et le choix est important lors de la création d'un projet :
- Si l'unité A utilise l'unité B dans sa section interface, les modifications apportées à l'interface de l'unité B sont propagées en tant que modifications apportées à l'interface de l'unité A. Delphi doit recompiler toutes les unités utilisant l'unité A.
- Si l'unité A utilise l'unité B dans sa section d'implémentation, seule l'unité A doit être recompilée pour utiliser les nouvelles déclarations dans l'unité B.
Les unités ne peuvent pas avoir de références circulaires dans leurs sections d'interface. Parfois, vous rencontrerez deux déclarations de classe contenant des déclarations mutuellement dépendantes. La solution la plus simple est d'utiliser une seule unité, mais si vous avez des raisons de déclarer les classes dans des unités séparées, vous pouvez utiliser une classe de base abstraite dans l'une ou les deux unités pour éliminer la dépendance circulaire.
Initialisation et finalisation
Chaque unité peut avoir une section d'initialisation et une section de finalisation. Le code de chaque section d'initialisation s'exécute avant le bloc de début/fin principal du programme ou de la bibliothèque. Le code de la section de finalisation s'exécute après la fin du programme ou lorsque la bibliothèque est déchargée. Le Delphi exécute les sections d'initialisation à l'aide d'une traversée en profondeur de l'arbre de dépendances de l'unité. En d'autres termes, avant l'exécution du code d'initialisation d'une unité, Delphi exécute la section d'initialisation de chaque unité qu'elle utilise. Une unité n'est initialisée qu'une seule fois. L'exemple suivant montre comment Delphi initialise et finalise les unités. Tout d'abord le programme principal :
Voici maintenant l'unité A :
- Unit unitA;
-
- INTERFACE
-
- Uses unitB;
-
- IMPLEMENTATION
-
- INITIALIZATION
- WriteLn('Initialisation de l''unité A');
- FINALIZATION
- WriteLn('Finalisation de l''unité A');
- END.
Et finalement l'unité B :
- Unit unitB;
-
- INTERFACE
-
- IMPLEMENTATION
-
- INITIALIZATION
- WriteLn('Initialisation de l''unité B');
- FINALIZATION
- WriteLn('Finalisation de l''unité B');
- END.
Lorsque vous compilez et exécutez l'exemple précédent, assurez-vous de l'exécuter à partir d'un prompt de commande et non de l'IDE, sinon la console apparaîtra et disparaîtra avant que vous ne puissiez voir la sortie, étant présentée dans l'exemple :
exemple |
on obtiendra le résultat suivant :
Initialisation de l'unité BInitialisation de l'unité A
Exemple de programme principal
Finalisation de l'unité A
Finalisation de l'unité B
Les unités System et SysInit
Les unités System et SysInit sont automatiquement incluses dans chaque unité, de sorte que toutes les déclarations de ces unités font effectivement partie du langage Delphi, et le compilateur possède des connaissances particulières sur de nombreuses fonctions et procédures des unités System et SysInit.
Les programmes
Un programme Delphi ressemble à un programme Pascal traditionnel, commençant par le mot-clef Program et utilisant un bloc de BEGIN-END pour le programme principal. Les programmes Delphi sont généralement courts, cependant, car le travail réel se déroule dans une ou plusieurs unités distinctes. Dans une application GUI, par exemple, le programme principal appelle généralement une procédure d'initialisation, crée un ou plusieurs formulaires (fenêtres) et appelle une procédure pour la boucle d'événements Windows.
Pour la compatibilité avec le Pascal standard, Delphi autorise une liste de paramètres après le nom du programme, mais, comme la plupart des compilateurs Pascal modernes, il ignore les identifiants y étant répertoriés. Dans une application GUI, vous ne pouvez pas utiliser les procédures d'entrée/sortie Pascal standard car il n'y a pas de périphérique d'entrée à partir duquel lire et aucun périphérique de sortie sur lequel écrire. Au lieu de cela, vous pouvez compiler une application console, pouvant lire et écrire à l'aide des routines d'entrée/sortie Pascal standard.
La déclaration uses d'un programme répertorie les unités composant le programme. Chaque nom d'unité peut être suivi d'une directive in spécifiant un nom de fichier. L'IDE et le compilateur utilisent le nom de fichier pour localiser les unités composant le projet. Les unités sans directive in sont généralement des unités de bibliothèque et ne font pas partie du code source du projet. Si une unité est associée à un formulaire, l'IDE entrepose également le nom du formulaire dans un commentaire. L'exemple suivant montre un fichier source de programme typique :
L'unité Forms fait partie de la bibliothèque Delphi standard. Elle ne possède donc pas de directive in ni de fichier source. Les autres unités ont des noms de fichiers sources. L'IDE de Delphi gère donc ces fichiers dans le cadre du projet. Pour en savoir plus sur la directive du compilateur $R. L'objet Application fait partie de la bibliothèque de composantes visuelles de Delphi.
Les bibliothèques
Une bibliothèque Delphi compile en une DLL Windows standard. Un fichier source de bibliothèque ressemble à un fichier source de programme, sauf qu'il utilise le mot clef library au lieu de program. Une bibliothèque possède généralement une déclaration d'exportation, répertoriant les routines exportées par la DLL. La déclaration d'exportation est facultative, et si vous avez l'intention d'utiliser une unité dans une bibliothèque, il est généralement préférable de placer la déclaration d'exportation dans l'unité, à proximité de la sous-routine que vous exportez. Si vous n'utilisez pas l'unité dans une bibliothèque, la déclaration d'exportation n'a aucun impact.
Le corps principal de la bibliothèque (son bloc de début et de fin) s'exécute à chaque fois que la bibliothèque est chargée dans une application. Ainsi, vous n'avez pas besoin d'écrire une procédure DLL pour gérer l'événement DLL_PROCESS_ATTACH. Cependant, pour les événements de détachement de processus et de processus léger, vous devez écrire un gestionnaire. Affectez le gestionnaire à la variable DllProc. Delphi se charge d'enregistrer la procédure auprès de Windows, et Windows appelle la procédure lorsqu'un processus se détache ou lorsqu'un processus léger se connecte ou se détache. L'exemple suivant montre une procédure DLL simple :
- Library Attachement;
-
- Uses Windows;
-
- Procedure Log(Const Msg:String);Begin
- MessageBox(0, PChar(Msg), 'Attachement', Mb_IconInformation + Mb_OK);
- End;
-
- Procedure AttachDetachProc(Reason:Integer);Begin
- Case Reason of
- Dll_Process_Detach: Log('Processus de détachement');
- Dll_Thread_Attach: Log('Attacher le fil');
- Dll_Thread_Detach: Log('Détacher le fil');
- Else Log('Raison inconnue !');
- End;
- End;
-
- BEGIN
- { Ce code s'exécute chaque fois que la DLL est chargée dans un nouveau processus. }
- Log('Processus de fixation');
- DllProc:=@AttachDetachProc;
- END.
Utilisation de la mémoire dynamique
Lorsque vous utilisez une DLL, vous devez faire attention à la mémoire dynamique. Toute mémoire allouée par une DLL est libérée lorsque la DLL est déchargée. Votre application peut toutefois conserver des pointeurs vers cette mémoire, ce qui peut entraîner des violations d'accès ou des problèmes plus graves si vous ne faites pas attention. La solution la plus simple consiste à utiliser l'unité ShareMem comme première unité de votre application et dans chaque bibliothèque chargée par l'application. L'unité ShareMem redirige toutes les demandes de mémoire vers une seule DLL (BorlndMM.dll), étant chargée tant que l'application est en cours d'exécution. Vous pouvez charger et décharger des DLL sans vous soucier des pointeurs suspendus.
Partage d'objets
ShareMem résout un type de problème de mémoire, mais pas un autre : l'identité de classe. Si la classe A est utilisée dans l'application et dans une DLL, Delphi ne peut pas savoir si les deux modules utilisent la même classe. Bien que les deux modules utilisent le même nom de classe, cela ne signifie pas que les classes sont identiques. Delphi prend la voie la plus sûre et suppose que les classes sont différentes; si vous savez mieux, vous n'avez aucun moyen simple d'informer Delphi.
Parfois, le fait d'avoir des identités de classe distinctes ne pose aucun problème, mais si votre programme essaie d'utiliser une référence d'objet au-delà d'une limite de DLL, les opérateurs is et as ne fonctionneront pas comme vous l'attendez. Étant donné que la DLL pense que la classe A est différente de la classe A de l'application, l'opérateur is renvoie toujours False.
Une façon de contourner ce problème consiste à ne pas transmettre d'objets au-delà des limites de la DLL. Si vous avez un objet graphique, par exemple, ne transmettez pas un objet TBitmap, mais transmettez plutôt un descripteur Windows (HBITMAP). Une autre solution consiste à utiliser des paquets. Delphi gère automatiquement les identités de classe dans les paquets pour éviter ce problème.
Définition de la base d'image
Lorsque vous créez une bibliothèque, veillez à définir l'option Base d'image. Windows doit charger chaque module (DLL et application) à une adresse de base d'image unique. La valeur par défaut de Delphi est $00400000, mais Windows utilise cette adresse pour l'application, il ne peut donc pas charger une DLL à la même adresse. Lorsque Windows doit déplacer une DLL vers une adresse différente, vous subissez une baisse de performances, car Windows doit réécrire une table de relocalisation pour refléter les nouvelles adresses. Vous ne pouvez pas garantir que chaque DLL aura une adresse unique car vous ne pouvez pas contrôler les adresses utilisées par les autres auteurs de DLL, mais vous pouvez faire mieux que la valeur par défaut. Vous devez au moins vous assurer que vos DLL utilisent une base d'image différente de celle des paquets Delphi standard et des DLL Windows. Utilisez Windows Quick View pour vérifier la base d'image d'un fichier.
Paquets
Le Delphi peut lier une unité de manière statique à un programme ou à une bibliothèque, ou de manière dynamique. Pour lier dynamiquement une ou plusieurs unités, vous devez placer ces unités dans un paquet, étant un type spécial de DLL. Lorsque vous écrivez un programme ou une bibliothèque, vous n'avez pas à vous soucier de la manière dont les unités seront liées. Si vous décidez d'utiliser un paquet, les unités du paquet ne sont pas liées à votre fichier .exe ou .dll, mais Delphi compile une référence à la DLL du paquet (portant l'extension .bpl pour Borland Package Library).
Les paquets évitent les problèmes des DLL, à savoir la gestion de la mémoire et des identités de classe. Delphi garde une trace des classes définies dans chaque unité et s'assure que l'application et tous les paquets associés utilisent la même identité de classe pour la même classe, de sorte que les opérateurs is et as fonctionnent correctement.
Conception et exécution
L'IDE de Delphi utilise des paquets pour charger des composantes, des formulaires personnalisés et d'autres unités de conception, telles que des éditeurs de propriétés. Lorsque vous écrivez des composantes, conservez leur code de conception dans un paquet de conception et placez la classe de composante réelle dans un paquet d'exécution. Les applications utilisant votre composante peuvent établir un lien statique avec le fichier .dcu du composante ou un lien dynamique avec le paquet d'exécution contenant votre composante. En conservant le code de conception dans un paquet séparé, vous évitez de lier du code étranger à une application.
Notez que le paquet de conception nécessite le paquet d'exécution, car vous ne pouvez pas lier une unité à plusieurs paquets. Considérez une application ou une bibliothèque comme une collection d'unités. Vous ne pouvez pas inclure une unité plus d'une fois dans un seul programme, peu importe que les unités soient liées de manière statique ou dynamique. Ainsi, si une application utilise deux paquets, la même unité ne peut pas être contenue dans les deux paquets. Cela équivaudrait à lier l'unité deux fois.
Création d'un paquet
Pour créer un paquet, vous devez créer un fichier .dpk ou un fichier source de paquet. Le fichier .dpk répertorie les unités contenues dans le paquet, ainsi que les autres paquets requis par le nouveau paquet. L'IDE comprend un éditeur de paquet pratique, ou vous pouvez modifier le fichier .dpk à la main, en utilisant le format indiqué dans l'exemple suivant :
Comme pour toute DLL, assurez-vous que vos paquets utilisent des adresses uniques pour leurs options Image Base. Les autres options sont explicites. Vous pouvez inclure des options sous forme de directives de compilation dans le fichier .dpk, ou vous pouvez laisser l'éditeur de paquets dans l'IDE écrire les options pour vous.
Types de données
Delphi prend en charge plusieurs extensions des types de données Pascal standard. Comme tout langage de programmation Pascal, Delphi prend en charge les énumérations, les ensembles, les tableaux, les sous-intervalles entières et énumérées, les enregistrements et les enregistrements variants. Si vous êtes habitué au C ou au C++, assurez-vous de bien comprendre ces types Pascal standard, car ils peuvent vous faire gagner du temps et vous éviter des maux de tête. Les différences sont les suivantes :
- Au lieu de masques de bits, les ensembles sont généralement plus faciles à lire.
- Vous pouvez utiliser des pointeurs à la place des tableaux, mais les tableaux sont plus simples et offrent une vérification des limites.
- Les enregistrements sont l'équivalent des structures, et les enregistrements variants sont comme des unions.
Types d'entiers
Le type d'entier de base est Integer. Le type Integer représente la taille naturelle d'un entier, compte tenu du système d'exploitation et de la plateforme. Actuellement, Integer représente un entier 32 bits, mais vous ne devez pas vous y fier. A partir de Delphi XE2, lorsque le système d'exploitation 64 bits fonctionnant sur du matériel 64 bits, il nécessite un type Integer de 64 bits. Pour faire face aux changements futurs, Delphi définit certains types dont la taille dépend de la taille de l'entier naturel et d'autres types dont les tailles sont fixes pour toutes les futures versions de Delphi. Le tableau suivant répertorie les types d'entiers standard. Les types marqués d'une taille naturelle peuvent changer dans les futures versions de Delphi, ce qui signifie que la plage changera également. Les autres types auront toujours la taille et l'intervalle affichées.
Type | Taille | Intervalle |
---|---|---|
Integer | naturel | -2 147 483 648 .. 2 147 483 647 ou A partir de Delphi XE2 si plateforme 64 bits -9 223 372 036 854 775 808 à 9 223 372 036 854 775 807 (soit -263 à 263-1). |
Cardinal | naturel | 0 .. 4 294 967 295 |
ShortInt | 8 bits | -128 .. 127 |
Byte | 8 bits | 0 .. 255 |
SmallInt | 16 bits | -32 768 .. 32 767 |
Word | 16 bits | 0 .. 65 535 |
LongInt | 32 bits | -2 147 483 648 .. 2 147 483 647 |
LongWord | 32 bits | 0 .. 4 294 967 295 |
Int64 | 64 bits | -9 223 372 036 854 775 808 .. 9 223 372 036 854 775 807 |
Types réels
Delphi possède plusieurs types de virgule flottante. Les types de base sont Single, Double et Extended. Single et Double correspondent aux tailles standard de la norme IEEE-754, constituant la base du matériel à virgule flottante sur les plateformes Intel et sous Windows. Extended est le format de précision étendue d'Intel, étant conforme aux exigences minimales de la norme IEEE-754 pour la double précision étendue. Delphi définit le type Real Pascal standard comme synonyme de Double.
Le matériel à virgule flottante utilise toute la précision du type Extended pour ses calculs, mais cela ne signifie pas que vous devez utiliser Extended pour entreposer des nombres. Extended occupe 10 octets, mais le type Double ne fait que 8 octets et est plus efficace pour entrer et sortir de l'unité à virgule flottante. Dans la plupart des cas, vous obtiendrez de meilleures performances et une précision adéquate en utilisant Double.
Les erreurs d'arithmétique à virgule flottante, comme la division par zéro, entraînent des erreurs d'exécution. La plupart des applications Delphi utilisent l'unité SysUtils, cartographiant les erreurs d'exécution en exceptions, de sorte que vous recevrez généralement une exception à virgule flottante pour de telles erreurs.
Les types à virgule flottante ont également des représentations pour l'infini et le non-nombre (NaN). Ces valeurs spéciales n'apparaissent normalement que si vous définissez le mot de contrôle à virgule flottante.
Delphi possède également un type à virgule fixe, Currency. Ce type représente des nombres à quatre décimales dans l'intervalle -922 337 203 685 477,5808 à 922 337 203 685 477,5807, ce qui est suffisant pour stocker le revenu brut de la planète entière, avec une précision d'un centième de cent. Le type Currency utilise le processeur à virgule flottante, utilisant 64 bits de précision sous forme de complément à deux. Étant donné que Currency est un type à virgule flottante, vous ne pouvez pas utiliser d'opérateurs entiers (tels que le décalage de bits ou le masquage).
Avertissement : l'unité à virgule flottante (FPU) peut effectuer des calculs en mode simple précision, double précision ou précision étendue. Delphi définit la FPU sur une précision étendue, ce qui fournit une prise en charge complète des types Extended et Currency. Certaines fonctions de l'API Windows, cependant, modifient la FPU en double précision. En double précision, le FPU ne conserve que 53 bits de précision au lieu de 64.
Lorsque le FPU utilise la double précision, vous n'avez aucune raison d'utiliser des valeurs étendues, ce qui constitue une autre raison d'utiliser Double pour la plupart des calculs. Le type Currency constitue un problème plus important. Vous pouvez essayer de déterminer exactement quelles fonctions modifient le mot de contrôle du FPU et réinitialisent la précision à la précision étendue après le retour des fonctions erronées. (Voir la fonction Set8087CW). Une autre solution consiste à utiliser le type Int64 au lieu de Currency et à implémenter votre propre mise à l'échelle à virgule fixe de la manière indiquée dans l'exemple suivant :
- ResourceString
- sInvalidCurrency = 'Chaîne de caractères devise non valide: ''%s''';
-
- Const
- Currency64Decimals=4; { Nombre de décimales fixes }
- Currency64Scale=10000; { 10**Decimal64Decimals }
-
- Type
- Currency64=Type Int64;
-
- Function StrToCurr64(Const S:String):Currency64;
- Var
- Code:Integer;
- Fraction:Integer;
- FractionString:String[Currency64Decimals];
- I:Integer;
- Begin
- { Convertir la partie entière et l'échelle par Currency64Scale }
- Val(S, Result, Code);
- Result:=Result*Currency64Scale;
- If Code=0 Then Exit Else { partie entière uniquement dans S }
- If S[Code]=DecimalSeparator Then Begin
- { L'utilisateur peut spécifier plus ou moins de 4 décimales, mais au plus 4 décimales sont significatives. }
- FractionString:=Copy(S,Code+1,Currency64Decimals);
- { Remplissez les chiffres manquants avec des zéros. }
- For I:=Length(FractionString)+1 to Currency64Decimals do FractionString[I]:='0';
- SetLength(FractionString, Currency64Decimals);
- { Convertissez la partie fractionnaire et ajoutez-la au résultat. }
- Val(FractionString, Fraction, Code);
- If Code=0 Then Begin
- If Result<0 Then Result:=Result-Fraction
- Else Result:=Result+Fraction;
- Exit;
- End;
- End;
- { La chaîne de caractères n'est pas une chaîne de caractères devise valide (signée, nombre à virgule fixe). }
- Raise EConvertError.CreateFmt(sInvalidCurrency, [S]);
- End;
-
- Function Curr64ToStr(Value:Currency64):String;Begin
- Result:=Format('%d%s%.4d',[Value div Currency64Scale,DecimalSeparator,Abs(Value mod Currency64Scale)]);
- End;
Les tableaux
En plus des tableaux Pascal standard, Delphi définit plusieurs extensions à utiliser dans des circonstances particulières. Les tableaux dynamiques sont des tableaux dont la taille peut changer au moment de l'exécution. Les tableaux ouverts sont des paramètres de tableau pouvant accepter des tableaux de n'importe quelle taille comme paramètres réels. Un cas particulier de tableaux ouverts vous permet de passer un tableau de types hétérogènes comme paramètre à une routine. Delphi ne prend pas en charge les tableaux conformes, comme ceux trouvés dans la norme ISO Pascal, mais les tableaux ouverts offrent la même fonctionnalité.
Les tableaux dynamiques
Un tableau dynamique est un tableau dont la taille est déterminée au moment de l'exécution. Vous pouvez faire croître ou réduire un tableau dynamique pendant l'exécution du programme. Déclarez un tableau dynamique sans type d'index. L'index est toujours un entier et commence toujours à zéro. Au moment de l'exécution, vous pouvez modifier la taille d'un tableau dynamique avec la procédure SetLength. L'affectation d'un tableau dynamique attribue une référence au même tableau. Contrairement aux chaînes de caractères, les tableaux dynamiques n'utilisent pas la copie sur écriture, donc la modification d'un élément d'un tableau dynamique affecte toutes les références à ce tableau. Delphi gère les tableaux dynamiques à l'aide du comptage de références, de sorte que lorsqu'un tableau sort de la portée, sa mémoire est automatiquement libérée. L'exemple suivant montre comment déclarer et utiliser un tableau dynamique :
- Var
- I:Integer;
- Data:Array of Double; { Tableau dynamique entreposant des valeurs doubles }
- F:TextFile; { Lire les données de ce fichier }
- Value:Double;
- Begin
- AssignFile(F,'Truc.dat');
- Reset(F);
- While Not Eof(F) do Begin
- ReadLn(F, Value);
- { Méthode inefficace mais simple pour développer un tableau dynamique. Dans un programme réel, }
- { vous devez augmenter la taille du tableau par gros morceaux, et non un élément à la fois. }
- SetLength(Data, Length(Data) + 1);
- Data[High(Data)] := Value;
- End;
- CloseFile(F);
- End;
Delphi vérifie les index des tableaux pour s'assurer qu'ils sont dans les limites. (En supposant que vous n'ayez pas désactivé les vérifications d'intervalle; voir la directive $R) Les tableaux dynamiques vides sont une exception. Delphi représente un tableau dynamique vide comme un pointeur nul. Si vous tentez d'accéder à un élément d'un tableau dynamique vide, Delphi déréférence le pointeur nul, ce qui entraîne une violation d'accès et non une erreur de vérification d'intervalle.
Les tableaux ouverts
Vous pouvez déclarer un paramètre d'une fonction ou d'une procédure sous forme de tableau ouvert. Lorsque vous appelez la routine, vous pouvez passer un tableau de n'importe quelle taille (avec le même type de base) comme paramètre. La routine doit utiliser les fonctions Low et High pour déterminer les limites du tableau. (Delphi utilise toujours zéro comme limite inférieure, mais les fonctions Low et High indiquent au mainteneur de votre code exactement ce que fait le code. Le codage en dur de 0 est moins clair.) Assurez-vous de déclarer le paramètre comme Const si la routine n'a pas besoin de modifier le tableau, ou comme Var si la routine modifie le contenu du tableau.
La déclaration d'un paramètre de tableau ouvert ressemble à la déclaration d'un tableau dynamique, ce qui peut prêter à confusion. Lorsqu'elle est utilisée comme paramètre, une déclaration de tableau sans type d'index est un tableau ouvert. Lorsqu'elle est utilisée pour déclarer une variable locale ou globale, un champ dans une classe ou un nouveau type, une déclaration de tableau sans index signifie un tableau dynamique.
Vous pouvez transmettre un tableau dynamique à une routine qui déclare son argument comme un tableau ouvert. La routine peut alors accéder aux éléments du tableau dynamique, mais ne peut pas modifier la taille du tableau. Étant donné que les tableaux ouverts et les tableaux dynamiques sont déclarés de manière identique, la seule façon de déclarer un paramètre comme un tableau dynamique consiste à déclarer un nouvel identifiant de type pour le type de tableau dynamique, comme indiqué ci-dessous :
- Procedure CantGrow(Var Data:Array of Integer);Begin
- { Les données sont un tableau ouvert, elles ne peuvent donc pas changer de taille. }
- End;
-
- Type
- TArrayOfInteger=Array of Integer; { Type de tableau dynamique }
-
- Procedure Grow(Var Data:TArrayOfInteger);Begin
- { Les données sont un tableau dynamique, elles peuvent donc changer de taille. }
- SetLength(Data, Length(Data)+1);
- End;
Vous pouvez transmettre un tableau dynamique à la procédure CantGrow, mais le tableau est transmis en tant que tableau ouvert, et non en tant que tableau dynamique. La procédure peut accéder aux éléments du tableau ou les modifier, mais elle ne peut pas modifier la taille du tableau.
Si vous devez appeler une fonction Delphi à partir d'un autre langage, vous pouvez transmettre un paramètre de tableau ouvert en tant que pointeur vers le premier élément du tableau et la longueur du tableau moins un en tant que paramètre entier 32 bits distinct. En d'autres termes, la limite inférieure de l'index du tableau est toujours zéro et le deuxième paramètre est la limite supérieure.
Vous pouvez également créer un paramètre de tableau ouvert en enfermant une série de valeurs entre crochets. L'expression de tableau ouvert ne peut être utilisée que comme argument de tableau ouvert, vous ne pouvez donc pas affecter une telle valeur à une variable de type tableau. Vous ne pouvez pas utiliser cette construction pour un tableau ouvert var. La création d'un tableau ouvert à la volée est un raccourci pratique, évitant la nécessité de déclarer un tableau Const :
- Avg := ComputeAverage([1, 3, 7, 47, 10, -17]);
La fonction Slice est une autre façon de passer un tableau à une fonction ou une procédure. Slice vous permet de passer une partie d'un tableau à une routine.
Tableau ouvert à variantes de type
Un autre type de paramètre de tableau ouvert est le tableau ouvert à variantes de type, ou tableau de const. Un tableau ouvert à variantes vous permet de transmettre un tableau hétérogène, c'est-à-dire un tableau dans lequel chaque élément du tableau peut avoir un type différent. Pour chaque élément du tableau, Delphi crée un enregistrement TVarRec, entreposant le type et la valeur de l'élément. Le tableau d'enregistrements TVarRec est transmis à la routine sous forme de tableau ouvert Const. La routine peut examiner le type de chaque élément du tableau en vérifiant le membre VType de chaque enregistrement TVarRec. Les tableaux ouverts à variantes de type vous permettent de transmettre une liste de paramètres de taille variable à une routine de manière sûre en termes de type.
Le TVarRec est un enregistrement de variante similaire à un Variant, mais implémenté différemment. Contrairement à un Variant, vous pouvez transmettre une référence d'objet à l'aide de TVarRec. L'exemple suivant montre un exemple simple d'une routine convertissant un tableau ouvert de variantes de type en chaîne de caractères :
- Function AsString(Const Args:Array of Const):String;
- Var
- I:Integer;
- S:String;
- Begin
- Result:='';
- For I:=Low(Args) to High(Args) do Begin
- Case Args[I].VType of
- vtAnsiString: S:=PChar(Args[I].VAnsiString);
- vtBoolean: If Args[I].VBoolean Then S:='True'
- Else S:='False';
- vtChar: S:=Args[I].VChar;
- vtClass: S:=Args[I].VClass.ClassName;
- vtCurrency: S:=FloatToStr(Args[I].VCurrency^);
- vtExtended: S:=FloatToStr(Args[I].VExtended^);
- vtInt64: S:=IntToStr(Args[I].VInt64^);
- vtInteger: S:=IntToStr(Args[I].VInteger);
- vtInterface: S:=Format('%p', [Args[I].VInterface]);
- vtObject: S:=Args[I].VObject.ClassName;
- vtPChar: S:=Args[I].VPChar;
- vtPointer: S:=Format('%p', [Args[I].VPointer]);
- vtPWideChar: S:=Args[I].VPWideChar;
- vtString: S:=Args[I].VString^;
- vtVariant: S:=Args[I].VVariant^;
- vtWideChar: S:=Args[I].VWideChar;
- vtWideString: S:=WideString(Args[I].VWideString);
- Else Raise Exception.CreateFmt('Non-supporté VType=%d', [Args[I].VType]);
- End;
- Result:=Result+S;
- End;
- End;
Chaînes de caractères
Delphi possède quatre types de chaînes de caractères : courtes, longues, larges et terminées par zéro. Une chaîne de caractères courte est un tableau de caractères compté, avec jusqu'à 255 caractères dans la chaîne. Les chaînes de caractères courtes ne sont pas beaucoup utilisées dans les programmes Delphi, mais si vous savez qu'une chaîne de caractères aura moins de 255 caractères, les chaînes courtes entraînent moins de surcharge que les chaînes de caractères longues.
Les chaînes de caractères longues peuvent avoir n'importe quelle taille et la taille peut changer au moment de l'exécution. Delphi utilise un système de copie sur écriture pour minimiser la copie lorsque vous transmettez des chaînes comme arguments à des routines ou que vous les affectez à des variables. Delphi conserve un décompte de références pour libérer automatiquement la mémoire d'une chaîne de caractères lorsque celle-ci n'est plus utilisée.
Les chaînes de caractères larges sont également allouées et gérées de manière dynamique, mais elles n'utilisent pas le décompte de références. Lorsque vous affectez une chaîne de caractères large à une variable WideString, Delphi copie la chaîne de caractères entière.
Delphi vérifie les références de chaîne de caractères de la même manière qu'il vérifie les références de tableau dynamique, c'est-à-dire que Delphi vérifie les index pour voir s'ils sont dans l'intervalle, mais une chaîne de caractères longue ou large vide est représentée par un pointeur nul. Le test des limites d'une chaîne de caractères longue ou large vide entraîne donc une violation d'accès au lieu d'une erreur de vérification d'intervalle.
Une chaîne de caractères terminée par zéro est un tableau de caractères, indexé par un entier commençant à zéro. La chaîne de caractères n'entrepose pas de taille, mais utilise un caractère de valeur zéro pour marquer la fin de la chaîne de caractères. L'API Windows utilise des chaînes de caractères terminées par zéro, mais vous ne devez pas les utiliser à d'autres fins. Sans une taille explicite, vous perdez l'avantage de la vérification des limites et les performances en pâtissent car certaines opérations nécessitent deux passages sur le contenu de la chaîne de caractères ou doivent traiter le contenu de la chaîne de caractères plus lentement, en vérifiant toujours la valeur zéro de fin. Delphi traitera également un pointeur vers un tel tableau comme une chaîne de caractères.
Pour votre commodité, Delphi entrepose une valeur zéro à la fin des chaînes de caractères longues et larges, vous pouvez donc facilement convertir une chaîne de caractères longue en type PAnsiChar, PChar ou PWideChar pour obtenir un pointeur vers une chaîne de caractères terminée par zéro. Le type PChar de Delphi est l'équivalent de char* en C ou C++.
Littéraux de chaîne de caractères
Vous pouvez écrire un littéral de chaîne de caractères de la manière standard de Pascal, ou utiliser un signe dièse (#) suivi d'un entier pour spécifier un caractère par valeur, ou utiliser un accent circonflexe (^) suivi d'une lettre pour spécifier un caractère de contrôle. Vous pouvez mélanger n'importe quel type de chaîne de caractères pour former un seul littéral, par exemple :
- 'Chaîne de caractères normale: '#13#10'Ligne suivante (après CR-LF)'^I'C''était un ''TAB'''
Le caractère caret (^) bascule le sixième bit ($40) de la valeur du caractère, ce qui transforme une lettre majuscule en son équivalent de caractère de contrôle. Si le caractère est en minuscule, le caret efface les cinquième et sixième bits ($60). Cela signifie que vous pouvez appliquer le caret à des caractères non alphabétiques. Par exemple, ^2 est identique à «r» car «2» a la valeur ordinale $32, et le basculement du bit $40 le transforme en $72, étant la valeur ordinale de «r». Delphi applique les mêmes règles à chaque caractère, vous pouvez donc utiliser le caret avant un espace, une tabulation ou un retour, ce qui rend votre code complètement illisible.
Mélanger les types de chaînes de caractères
Vous pouvez mélanger librement tous les types de chaînes de caractères, et Delphi fait de son mieux pour donner un sens à ce que vous essayez de faire. Vous pouvez concaténer différents types de chaînes de caractères, et Delphi rétrécira une chaîne de caractères large ou élargira une chaîne de caractères étroite selon les besoins. Pour passer une chaîne de caractères à une fonction attendant un paramètre PChar, il suffit de convertir une longue chaîne de caractères en PChar. Une chaîne de caractères courte n'a pas automatiquement un octet zéro à la fin, vous devez donc faire une copie temporaire, ajouter un octet #0 et prendre l'adresse du premier caractère pour obtenir une valeur PChar.
Chaînes de caractères Unicode et multioctets
Delphi prend en charge Unicode avec ses types WideChar, WideString et PWideChar. Toutes les opérations de chaîne de caractères habituelles fonctionnent pour les chaînes de caractères larges et les de caractères chaînes étroites (longues ou courtes). Vous pouvez affecter une chaîne de caractères étroite à une variable WideString et Delphi convertit automatiquement la chaîne de caractères en Unicode. Lorsque vous affectez une chaîne de caractères large à une chaîne de caractères longue (étroite), Delphi utilise la page de codes ANSI pour cartographier les caractères Unicode aux caractères multioctets.
Une chaîne de caractères multioctet est une chaîne dans laquelle un seul caractère peut occuper plus d'un octet. (Le terme Windows pour un ensemble de caractères multioctets est ensemble de caractères à deux octets.) Certaines langues nationales (par exemple, le japonais et le chinois) utilisent des ensembles de caractères bien plus grands que les 256 caractères de l'ensemble de caractères ANSI. Les ensembles de caractères multioctets utilisent un ou deux octets pour représenter un caractère, ce qui permet de représenter beaucoup plus de caractères. Dans une chaîne de caractères multi-octets, un octet peut être un caractère unique, un octet de début (c'est-à-dire le premier octet d'un caractère multi-octets) ou un octet de fin (le deuxième octet d'un caractère multi-octets). Chaque fois que vous examinez une chaîne un caractère à la fois, vous devez vous assurer de tester les caractères multi-octets, car le caractère qui ressemble, par exemple, à la lettre «A» peut en fait être l'octet de fin d'un caractère entièrement différent.
Ironiquement, certaines fonctions de gestion de chaînes de caractères de Delphi ne gèrent pas correctement les chaînes de caractères multi-octets. Au lieu de cela, l'unité SysUtils dispose de nombreuses fonctions de chaîne de caractères fonctionnant correctement avec les chaînes de caractères multi-octets. La gestion des chaînes de caractères multi-octets est particulièrement importante pour les noms de fichiers, et l'unité SysUtils dispose de fonctions spéciales pour travailler avec des caractères multi-octets dans les noms de fichiers.
Windows NT et Windows 2000 prennent en charge les versions étroites et larges de la plupart des fonctions API. Delphi utilise par défaut les versions étroites, mais vous pouvez appeler les fonctions larges tout aussi facilement. Par exemple, vous pouvez appeler CreateFileW pour créer un fichier avec un nom de fichier Unicode, ou vous pouvez appeler CreateFileA pour créer un fichier avec un nom de fichier ANSI. CreateFile est identique à CreateFileA. La VCL de Delphi utilise les versions étroites des contrôles Windows, pour maintenir la compatibilité avec toutes les versions de Windows. (Windows 95 et Windows 98 ne prennent pas en charge la plupart des contrôles Unicode.)
Types booléens
Delphi possède le type booléen Pascal habituel, mais il possède également plusieurs autres types facilitant l'utilisation de l'API Windows. De nombreuses API et autres fonctions écrites en C ou C++ renvoient des valeurs de nature booléenne, mais sont documentées comme renvoyant un entier. En C et C++, toute valeur différente de zéro est considérée comme True, donc Delphi définit les valeurs LongBool, WordBool et ByteBool avec la même sémantique.
Par exemple, si vous devez appeler une fonction écrite en C et que la fonction renvoie un résultat booléen sous la forme d'un entier court, vous pouvez déclarer la fonction avec le type de retour WordBool et appeler la fonction comme vous le feriez pour toute autre fonction de type booléen en Pascal :
Peu importe la valeur numérique renvoyée par QuelquesCFonc; Delphi traitera zéro comme False et toute autre valeur comme True. Vous pouvez utiliser n'importe quel type logique de type C de la même manière que vous utiliseriez le type booléen Delphi natif. La sémantique est identique. Pour un code Delphi pur, vous devez toujours utiliser Boolean.
Variantes
Delphi prend en charge les types de variantes OLE, ce qui facilite l'écriture d'un client ou d'un serveur d'automatisation OLE. Vous pouvez utiliser des variantes dans toute autre situation où vous souhaitez une variable dont le type peut changer au moment de l'exécution. Une variante peut être un tableau, une chaîne de caractères, un nombre ou même une interface IDispatch. Vous pouvez utiliser le type Variant ou le type OleVariant. La différence est qu'un OleVariant n'accepte que les types compatibles COM, en particulier, toutes les chaînes sont converties en chaînes de caractères larges. À moins que la distinction ne soit importante, cette page utilise le terme Variant pour désigner les deux types.
Une variable Variant est toujours initialisée sur Unassigned. Vous pouvez attribuer presque n'importe quel type de valeur à la variable, et elle gardera une trace du type et de la valeur. Pour connaître le type d'un Variant, appelez la fonction VarType. Vous pouvez également accéder à l'implémentation de bas niveau de Delphi des Variants en convertissant un Variant en type d'enregistrement TVarData.
Lorsque vous utilisez un Variant dans une expression, Delphi convertit automatiquement l'autre valeur de l'expression en Variant et renvoie un résultat Variant. Vous pouvez attribuer ce résultat à une variable typée statiquement, à condition que le type du Variant soit compatible avec la variable de destination.
L'utilisation la plus courante des Variants consiste à écrire un client d'automatisation OLE. Vous pouvez attribuer une interface IDispatch à une variable Variant et utiliser cette variable pour appeler les fonctions déclarées par l'interface. Le compilateur ne connaît pas ces fonctions, de sorte que l'exactitude des appels de fonction n'est pas vérifiée avant l'exécution. Par exemple, vous pouvez créer un client OLE pour imprimer la version de Microsoft Word installée sur votre système, comme illustré dans le code suivant. Delphi ne connaît rien de la propriété Version ni d'aucune autre méthode ou propriété du client OLE Word. Au lieu de cela, Delphi compile vos références de propriété et de méthode dans des appels à l'interface IDispatch. Vous perdez l'avantage des vérifications au moment de la compilation, mais vous gagnez la flexibilité de la liaison au moment de l'exécution. (Si vous souhaitez conserver les avantages de la sécurité des types, vous aurez besoin d'une bibliothèque de types du fournisseur du serveur d'automatisation OLE. Utilisez l'éditeur de bibliothèque de types de l'IDE pour extraire les interfaces COM définies par la bibliothèque de types du serveur.
Les pointeurs
Les pointeurs ne sont pas aussi importants dans Delphi qu'ils le sont dans C ou C++. Delphi possède de vrais tableaux, il n'est donc pas nécessaire de simuler des tableaux à l'aide de pointeurs. Les objets Delphi utilisent leur propre syntaxe, il n'est donc pas nécessaire d'utiliser des pointeurs pour faire référence à des objets. Pascal possède également de véritables paramètres de passage par référence. L'utilisation la plus courante des pointeurs est l'interfaçage avec du code C et C++, y compris l'API Windows.
Les programmeurs C et C++ seront heureux que les règles de Delphi pour l'utilisation des pointeurs soient plus proches de celles de C que de celles de Pascal. En particulier, la vérification de type est considérablement plus souple pour les pointeurs que pour les autres types. (Mais consultez les directives $T et $TypedAddress, renforçant les règles souples.)
Le type Pointer est un type de pointeur générique, équivalent à void* en C ou C++. Lorsque vous attribuez un pointeur à une variable de type Pointer, ou attribuez une expression de type Pointer à une variable de pointeur, vous n'avez pas besoin d'utiliser un transtypage de type. Pour prendre l'adresse d'une variable ou d'une routine, utilisez Addr ou @ (équivalent à & en C ou C++). Lorsque vous utilisez un pointeur pour accéder à un élément d'un enregistrement ou d'un tableau, vous pouvez omettre l'opérateur de déréférencement (^). Delphi peut savoir que la référence utilise un pointeur et fournit automatiquement l'opérateur ^.
Vous pouvez effectuer des opérations arithmétiques sur les pointeurs d'une manière légèrement plus restreinte qu'en C ou C++. Utilisez les instructions Inc ou Dec pour avancer ou reculer une valeur de pointeur d'un certain nombre d'éléments de type de base. La valeur réelle du pointeur change en fonction de la taille du type de base du pointeur. Par exemple, l'incrémentation d'un pointeur vers un entier fait avancer le pointeur de 4 octets :
Les programmes s'interfaçant directement avec l'API Windows doivent souvent utiliser explicitement des pointeurs. Par exemple, si vous devez créer une palette logique, la définition de type de TLogPalette nécessite une allocation de mémoire dynamique et une manipulation de pointeur, à l'aide d'un hack C courant consistant à déclarer un tableau d'un élément. Pour utiliser TLogPalette dans Delphi, vous devez écrire votre code Delphi en utilisant un style de type C, comme indiqué dans l'exemple suivant :
- { Créez une palette de niveaux de gris avec des entrées NumColors. }
- Type
- TNumColors = 1..256;
-
- Function BuildGrayPalette(NumColors:TNumColors):HPalette;
- Var
- Palette: PLogPalette; { Pointeur vers un enregistrement TLogPalette }
- I:TNumColors;
- Gray:Byte;
- Begin
- { TLogPalette possède un tableau de palettes d'un seul élément. Pour allouer de la mémoire à l'ensemble de la palette, ajoutez la taille des entrées de palette NumColors-1. }
- GetMem(Palette, SizeOf(TLogPalette)+(NumColors-1)*SizeOf(TPaletteEntry));
- Try
- { En Pascal standard, vous devez écrire Palette^.palVersion, mais Delphi déréférence automatiquement le pointeur. }
- Palette.palVersion:=$300;
- Palette.palNumEntries:=NumColors;
- For I:=1 to NumColors do Begin
- { Utilisez une échelle linéaire pour plus de simplicité, même si une échelle logarithmique donne de meilleurs résultats. }
- Gray:=I*255 div NumColors;
- { Désactivez la vérification de plage pour accéder aux entrées de palette au-delà de la première. }
- {$R-}
- Palette.palPalEntry[I-1].peRed := Gray;
- Palette.palPalEntry[I-1].peGreen := Gray;
- Palette.palPalEntry[I-1].peBlue := Gray;
- Palette.palPalEntry[I-1].peFlags := 0;
- {$R+}
- End;
- { Delphi ne déréférence pas automatiquement les pointeurs lorsqu'ils sont utilisés seuls, comme dans le cas suivant : }
- Result := CreatePalette(Palette^);
- Finally
- FreeMem(Palette);
- End;
- End;
Pointeurs de fonction et de méthode
Delphi vous permet de prendre l'adresse d'une fonction, d'une procédure ou d'une méthode et d'utiliser cette adresse pour appeler la routine. Par souci de simplicité, les trois types de pointeurs sont appelés pointeurs de procédure.
Un pointeur de procédure possède un type spécifiant le type de retour d'une fonction, les paramètres et si le pointeur est un pointeur de méthode ou un pointeur de procédure simple. Le code source est plus facile à lire si vous déclarez un type de procédure, puis une variable de ce type, par exemple :
En règle générale, vous pouvez affecter directement une procédure à une variable de procédure. Delphi peut savoir à partir du contexte que vous n'appelez pas la procédure, mais que vous attribuez son adresse. (Une conséquence étrange de cette règle simple est qu'une fonction sans paramètre dont le type de retour est une fonction ne peut pas être appelée de la manière Pascal habituelle. Sans aucun paramètre, Delphi pense que vous essayez de prendre l'adresse de la fonction. Au lieu de cela, appelez la fonction avec des parenthèses vides, de la même manière que C appelle des fonctions sans paramètre.)
Vous pouvez également utiliser les opérateurs @ ou Addr pour obtenir l'adresse d'une routine. L'utilisation explicite de @ ou Addr fournit un index à la personne devant lire et maintenir votre logiciel.
Utilisez un pointeur NIL pour les pointeurs de procédure de la même manière que vous le feriez pour tout autre pointeur. Une façon courante de tester une variable de procédure pour un pointeur NIL est d'utiliser la fonction Assigned :
Déclarations de type
Delphi suit les règles de base de compatibilité de type que le Pascal ordinaire suit pour l'arithmétique, le passage de paramètres,... Les déclarations de type ont cependant une nouvelle astuce pour prendre en charge l'IDE. Si une déclaration de type commence par le mot-clef type, Delphi crée des informations de type d'exécution distinctes pour ce type et traite le nouveau type comme un type distinct pour les paramètres var et out. Si la déclaration de type est simplement un synonyme d'un autre type, Delphi ne crée généralement pas de RTTI distinct pour le synonyme de type. Avec le mot-clef type supplémentaire, cependant, des tables RTTI distinctes permettent à l'IDE de faire la distinction entre les deux types.
Les variables et les constantes
Contrairement au Pascal standard, Delphi vous permet de déclarer le type d'une constante et d'initialiser une variable globale à une valeur constante. Delphi prend également en charge les applications multi-processus léger en vous permettant de déclarer des variables ayant des valeurs distinctes dans chaque processus léger de votre application.
Constantes typées
Lorsque vous déclarez le type d'une constante, Delphi réserve de la mémoire pour cette constante et la traite comme une variable. Vous pouvez attribuer une nouvelle valeur à la «constante» et elle conserve cette valeur. En C et C++, cette entité est appelée une variable statique.
Au niveau de l'unité, une variable conserve sa valeur de la même manière, vous pouvez donc la déclarer comme constante ou comme variable. Une autre façon d'écrire la même fonction est la suivante :
Le terme «constante typée» est clairement une erreur de dénomination, et au niveau de l'unité, vous devez toujours utiliser une déclaration var initialisée au lieu d'une constante typée. Vous pouvez vous forcer à suivre cette bonne habitude en désactivant la directive de compilation $J ou $WriteableConst, indiquant à Delphi de traiter toutes les constantes comme des constantes. La valeur par défaut, cependant, est de maintenir la compatibilité ascendante et de vous permettre de modifier la valeur d'une constante typée.
Pour les variables locales dans une procédure ou une fonction, vous ne pouvez pas initialiser de variables, et les constantes typées sont le seul moyen de conserver des valeurs qui persistent entre différents appels à la routine. Vous devez décider ce qui est le pire : utiliser une constante typée ou déclarer la variable persistante au niveau de l'unité.
Variables de processus léger
Delphi possède un type de variable unique, déclaré avec ThreadVar au lieu de Var. La différence est qu'une variable ThreadVar a une valeur distincte dans chaque processus léger d'une application multi-processus léger. Une variable ordinaire a une valeur unique étant partagée entre tous les processus légers. Une variable ThreadVar doit être déclarée au niveau de l'unité.
Delphi implémente les variables ThreadVar à l'aide de l'entreposage local des processus léger (TLS) dans l'API Windows. L'avantage d'utiliser ThreadVar au lieu d'utiliser directement TLS est que Windows dispose d'un petit nombre d'emplacements TLS disponibles, mais vous pouvez déclarer n'importe quel nombre et taille de variables ThreadVar. Plus important encore, vous pouvez utiliser les variables ThreadVar comme vous le feriez avec n'importe quelle autre variable, ce qui est beaucoup plus simple que de jouer avec TLS.
Gestion des exceptions
Les exceptions vous permettent d'interrompre le flux de contrôle normal d'un programme. Vous pouvez déclencher une exception dans n'importe quelle fonction, procédure ou méthode. L'exception fait passer le contrôle à un point antérieur dans la même routine ou dans une routine plus loin dans la pile d'appels. Quelque part dans la pile doit se trouver une routine utilisant une instruction try-except-end pour intercepter l'exception, sinon Delphi appelle ExceptProc pour gérer l'exception.
Delphi a deux instructions liées pour gérer les exceptions. L'instruction try-except définit un gestionnaire d'exceptions prenant le contrôle en cas de problème. L'instruction try-finally ne gère pas explicitement les exceptions, mais garantit que le code de la partie finally de l'instruction s'exécute toujours, même si une exception est déclenchée. Utilisez try-except pour gérer les erreurs. Utilisez try-finally lorsque vous avez une ressource (comme de la mémoire allouée) devant être nettoyée correctement, quoi qu'il arrive. L'instruction try-except est similaire à try-catch en C++ ou Java. Le C++ standard n'a pas de finally, mais Java en a. Certains compilateurs C++, dont celui de Borland, étendent la norme C++ pour ajouter la même fonctionnalité, par exemple avec le mot-clef __finally.
Comme C++ et Java, l'instruction try-except de Delphi peut gérer toutes les exceptions ou seulement les exceptions d'un certain type. Chaque instruction try-except peut déclarer plusieurs sections on, où chaque section déclare une classe d'exception. Delphi recherche les sections on dans l'ordre, en essayant de trouver une classe d'exception qui correspond à la classe de l'objet exception ou qui est une superclasse de celle-ci. L'exemple suivant montre un exemple d'utilisation de try-except :
Dans une application multi-processus léger, chaque processus léger peut conserver ses propres informations d'exception et peut déclencher des exceptions indépendamment des autres processus légers.
Lorsque votre code déclenche une exception, il doit transmettre un objet à l'instruction Raise. En général, un programme crée un nouvel objet d'exception dans le cadre de l'instruction Raise, mais dans de rares circonstances, vous souhaiterez peut-être déclencher un objet existant déjà. Delphi recherche dans la pile d'appels les instructions Try. Lorsqu'il trouve un Try-Finally, il exécute le code dans la partie Finally de l'instruction, puis continue de rechercher dans la pile un gestionnaire d'exception. Lorsque la pile se déroule dans un bloc Try-Except, Delphi recherche dans les sections on pour en trouver une correspondance à l'objet d'exception. S'il n'y a pas de sections on, Delphi exécute le code dans la partie Except de l'instruction. S'il y a des sections on, Delphi essaie de trouver une correspondance ou exécute le code dans la partie Else du bloc Except.
La variable déclarée dans l'instruction on contient une référence à l'objet d'exception. Delphi libère automatiquement l'objet une fois le gestionnaire d'exception terminé.
Si Delphi atteint la fin de la pile d'appels sans trouver de gestionnaire d'exceptions correspondant, il appelle ExceptProc. ExceptProc est en fait une variable pointeur, pointant vers une procédure de deux paramètres : l'objet d'exception et l'adresse où l'exception s'est produite. Par exemple, vous souhaiterez peut-être enregistrer les exceptions non gérées dans un fichier journal spécial, comme indiqué dans l'exemple suivant :
- Var
- NomDuFichierJournal: string = 'C:\journal.txt';
-
- Procedure JournalExceptProc(ExceptObject:TObject;ErrorAddr:Pointer);
- Const
- Size=1024;
- ResourceString
- Title='Erreur interne : veuillez la signaler au support technique';
- Var
- Buffer:PChar[0..Size-1];
- F:TextFile;
- Begin
- ExceptionErrorMessage(ExceptObject,ExceptAddr,Buffer,Size);
- AssignFile(F,NomDuFichierJournal);
- If FileExists(NomDuFichierJournal)Then AppendFile(F)
- Else Rewrite(F);
- WriteLn(F, Buffer);
- CloseFile(F);
- MessageBox(0,Buffer,Title,Mb_IconStop);
- End;
- ...
- { Dites à Delphi d'utiliser votre procédure d'exception.}
- ExceptProc := @JournalExceptProc;
Delphi détecte également les erreurs d'exécution, comme le dépassement de pile, et appelle ErrorProc pour chacune d'elles. Notez qu'ErrorProc est en fait une variable pointeur dont la valeur est un pointeur de procédure. Pour configurer un gestionnaire d'erreurs, déclarez une procédure et attribuez son adresse à ErrorProc.
L'unité System gère deux types de codes d'erreur : internes et externes. Si vous écrivez une procédure ErrorProc, elle doit gérer les codes d'erreur internes. Il s'agit de petits nombres, où chaque nombre indique un type d'erreur. La procédure ErrorProc par défaut de Delphi cartographie les codes d'erreur internes aux codes d'erreur externes. Les codes d'erreur externes sont documentés dans les fichiers d'aide de Delphi et sont visibles par l'utilisateur.
Lorsque Delphi appelle ErrorProc, il transmet deux paramètres : le code d'erreur et l'adresse de l'instruction où l'erreur s'est produite. Votre gestionnaire d'erreurs peut ressembler à ce qui suit, par exemple :
L'unité SysUtils fournit une aide supplémentaire pour travailler avec les exceptions et les erreurs d'exécution. En particulier, elle définit les procédures ErrorProc et ExceptProc. ErrorProc transforme une erreur d'exécution en une exception, comme EStackOverflow pour une erreur de dépassement de pile. La routine ExceptProc affiche le message d'exception, puis arrête le programme. Dans une application console, le message d'exception est écrit sur la sortie standard et dans les applications GUI, il s'affiche dans une boîte de dialogue. L'unité SysUtils configure les routines ErrorProc et ExceptProc dans sa section d'initialisation. Si votre application génère une exception ou une erreur d'exécution avant l'initialisation de l'unité SysUtils, vous ne bénéficierez pas de ses routines et de ses gestionnaires d'exceptions. Par conséquent, lorsque votre application signale une erreur d'exécution brute, non encapsulée sous forme d'exception, votre problème réside probablement dans une section d'initialisation ou de finalisation.
Pour générer une exception, utilisez l'instruction Raise, suivie d'une référence d'objet. En général, l'instruction Raise crée un tout nouvel objet. Vous pouvez créer un objet de n'importe quelle classe à utiliser comme objet d'exception, bien que la plupart des programmes utilisent SysUtils.Exception ou l'une de ses classes dérivées.
Delphi conserve une trace des informations sur une exception, l'endroit où elle a été déclenchée, le contexte du programme au moment où elle a été déclenchée,... Vous pouvez accéder à ces informations à partir de diverses variables de l'unité System. Le tableau suivant présente un aperçu des variables pertinentes :
Déclaration | Description |
---|---|
AbstractErrorProc | Gestionnaire d'erreurs de méthode abstraite. |
AssertErrorProc | Gestionnaire d'erreurs d'assertion. |
ErrorAddr | Adresse de l'erreur d'exécution. |
ErrorProc | Procédure de gestion des erreurs. |
ExceptClsProc | Cartographier une exception Windows à une classe Delphi. |
ExceptionClass | Classe de base d'exception. |
ExceptObjProc | Cartographier une exception Windows à un objet Delphi. |
ExceptProc | Gestionnaire d'exceptions non géré. |
SafeCallErrorProc | Gestionnaire d'erreurs Safecall. |
Lorsqu'une exception déroule la pile d'appels, Delphi appelle le code de la partie finally de chaque bloc try-finally englobant. Delphi nettoie également la mémoire des tableaux dynamiques, des chaînes de caractères longues, des chaînes de caractères larges, des interfaces et des variantes étant sorties de la portée. (À proprement parler, cela diminue le nombre de références, de sorte que la mémoire réelle n'est libérée que s'il n'y a pas d'autres références à la chaîne de caractères ou au tableau.)
Si un bloc finally déclenche une exception, l'ancien objet d'exception est libéré et Delphi gère la nouvelle exception.
L'utilisation la plus courante d'une instruction try-finally est de libérer des objets et de libérer d'autres ressources. Si une routine a plusieurs objets à libérer, il est généralement plus simple d'initialiser toutes les variables à NIL et d'utiliser un seul bloc try-finally pour libérer tous les objets en même temps. Cependant, si le destructeur d'un objet est susceptible de déclencher une exception, vous devez utiliser des instructions try-finally imbriquées, mais dans la plupart des cas, la technique présentée dans l'exemple suivant fonctionne bien :
- { Copier un fichier. Si le fichier source ne peut pas être ouvert ou si le fichier de destination ne peut pas être créé, }
- { déclenchez EFileCopyError et incluez le message d'erreur d'origine dans le nouveau message d'exception. Le nouveau message }
- { donne un peu plus d'informations que le message d'origine. }
-
- Type
- EFileCopyError=Class(EStreamError);
-
- Procedure CopyFile(Const SourceFile,TargetFile:String);
- Var
- FromStream,ToStream:TFileStream;
- ResourceString
- sCannotRead='mpossible de lire le fichier :%s';
- sCannotCreate='Impossible de créer le fichier : %s';
- Begin
- ToStream:=NIL;
- FromStream:=NIL;
- Try
- Try
- FromStream:=TFileStream.Create(TargetFile,fmOpenRead);
- Except
- { Gérer les exceptions EFopenError, mais aucun autre type d'exception. }
- On Ex: EFOpenError do Raise EFileCopyError.CreateFmt(sCannotRead, [Ex.Message]); { Lever une nouvelle exception. }
- End;
- Try
- ToStream:=TFileStream.Create(SourceFile,fmCreate);
- Except
- On Ex:EFCreateError do Raise EFileCopyError.CreateFmt(sCannotCreate, [Ex.Message]);
- End;
- { Copiez maintenant le fichier. }
- ToStream.CopyFrom(FromStream, 0);
- Finally
- { C'est fait. Fermez les fichiers, même si une exception a été levée. }
- ToStream.Free;
- FromStream.Free;
- End;
- End;
Les entrées/sorties de fichier
Les entrées/sorties de fichier Pascal traditionnelles fonctionnent dans Delphi, mais vous ne pouvez pas utiliser les fichiers d'entrée et de sortie standard dans une application GUI. Pour attribuer un nom de fichier à une variable File ou TextFile, utilisez AssignFile. Reset et Rewrite fonctionnent comme dans Pascal standard, ou vous pouvez utiliser Append pour ouvrir un fichier et l'ajouter à sa fin. Le fichier doit déjà exister. Pour fermer le fichier, utilisez CloseFile. Le tableau suivant répertorie les procédures d'entrée/sorties fournies par Delphi :
Routine | Description |
---|---|
Append | Ouvrir un fichier existant pour l'ajouter. |
AssignFile ou Assign | Attribuer un nom de fichier à une variable File ou TextFile. |
BlockRead | Lire les données d'un fichier. |
BlockWrite | Écrire des données dans un fichier. |
CloseFile ou Close | Fermer un fichier ouvert. |
Eof | Renvoie True pour la fin du fichier. |
Erase | Supprimer un fichier. |
FilePos | Renvoie la position actuelle du fichier. |
FileSize | Renvoie la taille d'un fichier, en enregistrements. |
Read | Lire des données formatées à partir d'un fichier ou d'un fichier texte. |
ReadLn | Lire une ligne de données à partir d'un fichier texte. |
Rename | Renommer un fichier. |
Reset | Ouvrir un fichier pour le lire. |
Rewrite | Ouvrir un fichier en écriture, en effaçant le contenu précédent. |
Seek | Changer la position du fichier. |
Write | Écrire des données formatées. |
WriteLn | Écrivez une ligne de texte. |
Lorsque vous ouvrez un fichier avec Reset, la variable FileMode dicte le mode d'ouverture du fichier. Par défaut, FileMode est 2, ce qui permet l'accès en lecture et en écriture. Si vous souhaitez simplement lire un fichier, vous devez définir FileMode sur avant d'appeler Reset. (Définissez FileMode sur 1 pour un accès en écriture seule.)
La bibliothèque d'exécution de Delphi propose une meilleure façon d'effectuer des entrées/sorties de fichiers à l'aide de flux de données. Les flux sont orientés objet et offrent beaucoup plus de flexibilité et de puissance que les entrées/sorties Pascal traditionnelles. Le seul moment où il ne faut pas utiliser les flux de données est lorsque vous ne pouvez pas utiliser la bibliothèque et devez vous en tenir au langage de programmation Delphi uniquement.
Avertissement : Delphi ne prend pas en charge les procédures Pascal standard Get et Put.
Les fonctions et les procédures
Delphi prend en charge plusieurs extensions des fonctions et procédures Pascal standard. Vous pouvez surcharger les routines en déclarant plusieurs routines portant le même nom, mais avec des nombres ou des types de paramètres différents. Vous pouvez déclarer des valeurs par défaut pour les paramètres, rendant ainsi les paramètres facultatifs. Presque tout ce qui figure dans cette section s'applique également aux fonctions et aux procédures, c'est pourquoi le terme routine est utilisé pour les deux.
Surcharge
Vous pouvez surcharger un nom de routine en déclarant plusieurs routines portant le même nom, mais avec des arguments différents. Pour déclarer des routines surchargées, utilisez la directive de surcharge, par exemple :
Lorsque vous appelez une routine surchargée, le compilateur doit être capable de déterminer quelle routine vous souhaitez appeler. Par conséquent, les routines surchargées doivent accepter des nombres ou des types d'arguments différents. Par exemple, en utilisant les déclarations ci-dessus, vous pouvez déterminer quelle fonction appeler simplement en comparant les types de paramètres :
- S:=AsString(42); { Appel AsString(Integer) }
- S:=AsString(42.0); { Appel AsString(Extended) }
- S:=AsString(42.0, 8); { Appel AsString(Extended, Integer) }
Parfois, l'unité A déclare une routine et l'unité B utilise l'unité A, mais déclare également une routine portant le même nom. La déclaration dans l'unité B n'a pas besoin de la directive de surcharge, mais vous devrez peut-être utiliser le nom de l'unité A pour qualifier les appels à la version de la routine de A à partir de l'unité B. Une classe dérivée qui surcharge une méthode d'une classe ancêtre doit utiliser la directive de surcharge.
Paramètres par défaut
Parfois, vous pouvez utiliser des paramètres par défaut au lieu de routines surchargées. Par exemple, considérez les routines surchargées suivantes :
Il est fort probable que la première routine surchargée convertisse son paramètre à virgule flottante en une chaîne de caractères utilisant une largeur minimale prédéfinie, par exemple 1. En fait, vous pourriez même écrire la première fonction AsString pour qu'elle appelle la seconde, par exemple :
Vous pouvez vous épargner quelques tracas et du code supplémentaire en écrivant une seule routine prenant un paramètre facultatif. Si l'appelant ne fournit pas de paramètre actuel, Delphi remplace une valeur par défaut :
L'utilisation judicieuse des paramètres par défaut peut vous éviter d'écrire des routines surchargées supplémentaires. Soyez toutefois prudent lorsque vous utilisez des paramètres de type chaîne de caractères. Delphi doit compiler la chaîne partout où la routine est appelée avec le paramètre par défaut. Ce n'est pas un problème si la chaîne de caractères est vide (car Delphi représente une chaîne de caractères vide avec un pointeur NIL), mais si la chaîne de caractères n'est pas vide, vous devez utiliser une variable initialisée (ou une constante typée). De cette façon, Delphi peut entreposer une référence à la variable lorsqu'il doit utiliser le paramètre par défaut. L'alternative est de laisser Delphi gaspiller de l'espace pour entreposer des copies supplémentaires de la chaîne de caractères et perdre du temps à créer une nouvelle instance de la chaîne de caractères pour chaque appel de fonction.
Variable de résultat
Delphi emprunte une fonctionnalité au langage Eiffel, à savoir la variable Result. Chaque fonction déclare implicitement une variable, nommée Result, dont le type est le type de retour de la fonction. Vous pouvez utiliser cette variable comme une variable ordinaire, et lorsque la fonction retourne, elle renvoie la valeur de la variable Result. L'utilisation de Result est plus pratique que l'attribution d'une valeur au nom de la fonction, ce qui est la manière standard de Pascal de renvoyer un résultat de fonction. Comme Result est une variable, vous pouvez obtenir et utiliser sa valeur à plusieurs reprises. En Pascal standard, vous pouvez faire la même chose en déclarant explicitement une variable de résultat, à condition de penser à affecter le résultat au nom de la fonction. Cela ne fait pas une grande différence, mais les petites subtilités peuvent s'accumuler dans un grand projet. Delphi prend en charge l'ancienne méthode de renvoi d'un résultat de fonction, vous avez donc le choix. Quelle que soit l'approche que vous choisissez, soyez cohérent. L'exemple suivant montre deux manières différentes de calculer une factorielle : la méthode Delphi et la méthode à l'ancienne :
- { Calcul d'une factorielle en Delphi. }
- Function Factorial(Number:Cardinal):Int64;
- Var
- I:Cardinal;
- Begin
- Result:=1;
- For I:=2 to Number do Result:=Result*I;
- end;
-
- { Calcul d'une factorielle en Pascal standard. }
- Function Factorial(Number:Integer):Integer;
- Var
- I,Result:Integer;
- Begin
- Result := 1;
- For I:=2 to Number do Result:=Result*I;
- Factorial:=Result;
- End;
Avertissement : Delphi initialise généralement les variables de type chaîne de caractères et tableau dynamique, mais Result est spécial. Ce n'est pas vraiment une variable locale, mais plutôt un paramètre var caché. En d'autres termes, l'appelant doit l'initialiser. Le problème est que Delphi n'initialise pas toujours Result. Pour plus de sécurité, si votre fonction renvoie une chaîne, une interface, un tableau dynamique ou un type Variant, initialisez la variable Result sur une chaîne de caractères vide, un tableau ou Unassigned.