Section courante

A propos

Section administrative du site

 Langage  Installation  Elément  Tutoriel  Programmation  Bibliothèque  Cadre d'application  SDK  Composante  Projet  .NET  Annexe  Aide 
ABAP/4
Ada
Assembleur
Assembly & bytecode
ASP (Active Server Pages)
Basic
C
C++
C# (C Sharp)
Cobol
ColdFusion
Fortran
HTML
Java
JavaScript
LISP
Logo
LotusScript
Oberon
Pascal
Perl
PHP
PL/1
Prolog
Python
Rebol
REXX
Ruby
Rust
SAS
NoSQL
SQL
Swift
X++ (Axapta)
GNAT
SMALLAda
VHDL
Assembleur 370
Assembleur 1802
Assembleur 4004
Assembleur 6502
Assembleur 6800
Assembleur 68000
Assembleur 8080 et 8085
Assembleur 8089
Assembleur 80x86
Assembleur AGC4
Assembleur ARM
Assembleur DPS 8000
Assembleur i860
Assembleur Itanium
Assembleur MIPS
Assembleur PDP-11
Assembleur PowerPC
Assembleur RISC-V
Assembleur SPARC
Assembleur SuperH
Assembleur UNIVAC I
Assembleur VAX
Assembleur Z80
Assembleur Z8000
Assembleur z/Architecture
ASSEMBLER/MONITOR 64
Micol Assembler
GFA Assembler
A86
MASM (Macro Assembler)
TASM (Turbo Assembler)
CIL
Jasmin
LLVM
MSIL
Parrot
P-Code (PCode)
SWEET16
G-Pascal
ASP 1.0
ASP 2.0
ASP 3.0
ASP.NET
ASP.NET Core
ABasiC (Amiga)
Adam SmartBASIC
Altair BASIC
AmigaBASIC (Amiga)
AMOS Basic (Amiga)
Atari Basic (Atari 400, 600 XL, 800, 800XL)
Basic Apple II (Integer BASIC/APPLESOFT)
Basic Commodore 64 (CBM-BASIC)
Basic Commodore 128 (BASIC 7.0)
Basic Commodore VIC-20 (CBM-BASIC 2.0)
Basic Coco 1 (Color Basic)
Basic Coco 2 (Extended Color Basic)
Basic Coco 3 (Extended Color Basic 2.0)
BASICA (PC DOS)
Basic Pro
BBC BASIC
Blitz BASIC (Amiga)
DarkBASIC
Dartmouth BASIC
GFA-Basic (Atari ST/Amiga)
GWBASIC (MS-DOS)
Liberty BASIC
Locomotive BASIC (Amstrad CPC)
MSX-Basic
Omikron Basic (Atari ST)
Oric Extended Basic
Power Basic
Quick Basic/QBasic (MS-DOS)
Sinclair BASIC (ZX80, ZX81, ZX Spectrum)
ST BASIC (Atari ST)
Turbo Basic
Vintage BASIC
VBScript
Visual Basic (VB)
Visual Basic .NET (VB .NET)
Visual Basic pour DOS
Yabasic
BeckerBASIC
SIMONS' BASIC
Basic09 d'OS-9
Disk Extended Color Basic
Basic09 d'OS-9
Disk Extended Color Basic
Access
Excel
Visual Basic pour Windows
Visual Basic .NET pour Windows
C Shell Unix (csh)
C pour Amiga
C pour Atari ST
C pour DOS
C pour Falcon030
C pour GEMDOS (Atari ST)
C pour Linux
C pour PowerTV OS
C pour OS/2
C pour Unix
C pour Windows
Aztec C
CoCo-C
GNU C
HiSoft C
IBM C/2
Introl-C
Lattice C
Microsoft C
MinGW C
MSX-C
Open Watcom C
OS-9 C Compiler
Pure C
Quick C
Turbo C
HiSoft C for Atari ST
HiSoft C for CP/M (Amstrad CPC)
C++ pour OS/2
C++ pour Windows
Borland C++
C++Builder
IBM VisualAge C++
Intel C++
MinGW C++
Open Watcom C++
Symantec C++
Turbo C++
Visual C++
Visual C++ .NET
Watcom C++
Zortech C++
C# (C Sharp) pour Windows
Apple III Cobol
Microsoft Cobol
BlueDragon
Lucee
OpenBD
Railo
Smith Project
Microsoft Fortran
WATFOR-77
CSS
FBML
Open Graph
SVG
XML
XSL/XSLT
LESS
SASS
GCJ (GNU)
JSP
Jython
Visual J++
Node.js
TypeScript
AutoLISP
ACSLogo
LotusScript pour Windows
Amiga Oberon
Oberon .NET
Apple Pascal
Delphi/Kylix/Lazarus
Free Pascal
GNU Pascal
HighSpeed Pascal
IBM Personal Computer Pascal
Lisa Pascal
Maxon Pascal
MPW Pascal
OS-9 Pascal
OSS Personal Pascal
Pascal-86
Pascal du Cray Research
Pascal/VS
Pascal-XT
PURE Pascal
QuickPascal
RemObjets Chrome
Sun Pascal
THINK Pascal
Tiny Pascal (TRS-80)
Turbo Pascal
UCSD Pascal
VAX Pascal
Virtual Pascal
Turbo Pascal for CP/M-80
Turbo Pascal for DOS
Turbo Pascal for Macintosh
Turbo Pascal for Windows
CodeIgniter (Cadre d'application)
Drupal (Projet)
Joomla! (Projet)
Phalanger (PHP .NET)
phpBB (Projet)
Smarty (balise)
Twig (balise)
Symfony (Cadre d'application)
WordPress (Projet)
Zend (Cadre d'application)
PL360
PL/M-80
PL/M-86
Turbo Prolog
CPython
IronPython
Jython
PyPy
AREXX
Regina REXX
JMP
Btrieve
Cassandra
Clipper
CouchDB
dBASE
Hbase
Hypertable
MongoDB
Redis
Access
BigQuery
DB2
H2
Interbase
MySQL
Oracle
PostgreSQL
SAP HANA
SQL Server
Sybase
U-SQL
Delphi 1
Delphi Professionnel 3.0
Introduction
Les remarques
Les oérateurs
Les instructions conditionnelles
Les instructions de boucles
Les instructions d'exceptions
Type de données élémentaires
Référence de mots réservés (mots clefs)
Référence des unités
Définition de procédures et de fonctions
Référence de procédures et fonctions
Référence des classes
Référence de constantes
Référence de variables
Référence des directives de compilation
Entier
Réel
Chaine de caractères
Pointeur
Détaillé
CLASSES
COMCTRLS
CONTROLS
DBTABLES
DIALOGS
FILECTRL
FORMS
LZEXPAND
MATH
PRINTERS
STDCTRLS
SYSTEM
SYSUTILS
Catégorie
Prototype
ANSI
ASCIZ
Base de registres
Boite de dialogue
Comptabilité
Colorimétrie
Date
Ensemble
Fichier
Imprimante
Mémoire
Monétaire
Police de caractères
Processus
Statistique
Temps
Trigonométrie
Web
Les premiers pas
Le langage Object Pascal : classes et objets
La bibliothèque d'exécution
Les classes de base de la bibliothèque
Bonjour
Astronomie
Biochimie
Conversion
Courriel
Électricité
Fichiers
Finance
Géographie
Géométrie
Histoire
Jeux & stratégies
Mathématique
Médicale
Météorologie
Océanographie
Sport
Système d'exploitation
Temps
Tri
Trigonométrie
Validation
Phase lunaire
Calcul du calcium corrigé
Calcul le taux d'alcoolémie
Bin,Hex,Base62,...
Validation
Tarif d'une piscine pour 1 mois
Texte séquentiel
IPaymt/Interet
NPer
PPaymt/Principal
Distance en Km entre deux longitudes et latitudes
Aire d'un cercle
Aire d'une surface de prisme rectangulaire
Aire d'un triangle
Distance entre deux points
Chiffre romain
Tetris
Ackermann
Exp
Factoriel
Fibonacci
Log
Nombre premier
Odd
Random
Sqrt
Triangle Pascal
Hauteur utérine
Unité de mesure
Fréquence des vagues
Hockey
Créer un raccourci sur le bureau
Visualiser les variables d'environnement
Année bissextile
Calendrier
Date de la Pâque
Heure courante
FirstDayOfMonth
Tri à bulle (Bubble Sort)
Tri Shell Sort
ArcCos
ArcSin
Atn/ATan/ArcTan
Cos
Sin
Courriel
Extension de nom de domaine
Alcinoe
ALGLIB
Ararat Synapse
CryptoLib4Pascal
Delphi JOSE and JWT Library
Delphi-OpenSSL
Delphi Redis Client
FBLib (Firebird Pascal Library)
FireDAC
INDY (Internet Direct)
Kastri
Mars-Curiosity
QuickLib
QuickLogger
RESTRequest4Delphi
Skia4Delphi
SynPDF
UniDAC
WinGraph
ZeosLib
Daraja (cadre d'application pour Web)
DelphiMVCFramework
DORM (Delphi ORM)
FireMonkey
Horse
LoggerPro
Router4Delphi
Spring4D
Synopse mORMot
tiOPF
TMS WEB Core
uniGUI
AWS SDK for Delphi
Remoting SDK
Python for Delphi (P4D)
VCL (Visual Component Library)
HeidiSQL
Inno Setup
JEDI
Delphi for .NET
RemObjects Chrome
Delphi versus VB
Bibliographie
Index
Préface
Notes légal
Dictionnaire
Recherche

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 :

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 :

  1. Program Exemple;
  2.  
  3. Uses unitA;
  4.  
  5. {$AppType Console}
  6.  
  7. BEGIN
  8.  WriteLn('Exemple de programme principal');
  9. END.

Voici maintenant l'unité A :

  1. Unit unitA;
  2.  
  3. INTERFACE
  4.  
  5. Uses unitB;
  6.  
  7. IMPLEMENTATION
  8.  
  9. INITIALIZATION
  10.   WriteLn('Initialisation de l''unité A');
  11. FINALIZATION
  12.   WriteLn('Finalisation de l''unité A');
  13. END.

Et finalement l'unité B :

  1. Unit unitB;
  2.  
  3. INTERFACE
  4.  
  5. IMPLEMENTATION
  6.  
  7. INITIALIZATION
  8.  WriteLn('Initialisation de l''unité B');
  9. FINALIZATION
  10.  WriteLn('Finalisation de l''unité B');
  11. 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é B
Initialisation 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 :

  1. Program ProgrammeTypique;
  2.  
  3. Uses
  4.  Forms,
  5.  Main in 'Main.pas' {MainForm},
  6.  PlusDeTrucs in 'PlusDeTrucs.pas' {Form2},
  7.  Utils in 'Utils.pas';
  8.  
  9. {$R *.RES}
  10.  
  11. BEGIN
  12.  Application.Initialize;
  13.  Application.CreateForm(TMainForm, MainForm);
  14.  Application.CreateForm(TForm2, Form2);
  15.  Application.Run;
  16. END.

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 :

  1. Library Attachement;
  2.  
  3. Uses Windows;
  4.  
  5. Procedure Log(Const Msg:String);Begin
  6.  MessageBox(0, PChar(Msg), 'Attachement', Mb_IconInformation + Mb_OK);
  7. End;
  8.  
  9. Procedure AttachDetachProc(Reason:Integer);Begin
  10.  Case Reason of
  11.   Dll_Process_Detach: Log('Processus de détachement');
  12.   Dll_Thread_Attach:  Log('Attacher le fil');
  13.   Dll_Thread_Detach:  Log('Détacher le fil');
  14.   Else                Log('Raison inconnue !');
  15.  End;
  16. End;
  17.  
  18. BEGIN
  19.  { Ce code s'exécute chaque fois que la DLL est chargée dans un nouveau processus. }
  20.  Log('Processus de fixation');
  21.  DllProc:=@AttachDetachProc;
  22. 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 :

  1. Package Sample;
  2. {$R 'COMP.DCR'}
  3. {$IMAGEBASE $09400000}
  4. {$DESCRIPTION 'Exemples de composantes'}
  5.  
  6. Requires
  7.  vcl50;
  8.  
  9. Contains
  10.  Comp in 'Comp.pas';
  11.  
  12. END.

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 :

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 :

  1. ResourceString
  2.   sInvalidCurrency = 'Chaîne de caractères devise non valide: ''%s''';
  3.   
  4. Const
  5.   Currency64Decimals=4;   { Nombre de décimales fixes }
  6.   Currency64Scale=10000;  { 10**Decimal64Decimals }
  7.   
  8. Type
  9.   Currency64=Type Int64;
  10.  
  11. Function StrToCurr64(Const S:String):Currency64;
  12. Var
  13.  Code:Integer;
  14.  Fraction:Integer;
  15.  FractionString:String[Currency64Decimals];
  16.  I:Integer;
  17. Begin
  18.   { Convertir la partie entière et l'échelle par Currency64Scale }
  19.  Val(S, Result, Code);
  20.  Result:=Result*Currency64Scale;
  21.  If Code=0 Then Exit Else { partie entière uniquement dans S }
  22.  If S[Code]=DecimalSeparator Then Begin
  23.    { L'utilisateur peut spécifier plus ou moins de 4 décimales, mais au plus 4 décimales sont significatives. }
  24.   FractionString:=Copy(S,Code+1,Currency64Decimals);
  25.    { Remplissez les chiffres manquants avec des zéros. }
  26.   For I:=Length(FractionString)+1 to Currency64Decimals do FractionString[I]:='0';
  27.   SetLength(FractionString, Currency64Decimals);
  28.    { Convertissez la partie fractionnaire et ajoutez-la au résultat. }
  29.   Val(FractionString, Fraction, Code);
  30.   If Code=0 Then Begin
  31.    If Result<0 Then Result:=Result-Fraction
  32.                Else Result:=Result+Fraction;
  33.    Exit;
  34.   End;
  35.  End;
  36.   { La chaîne de caractères n'est pas une chaîne de caractères devise valide (signée, nombre à virgule fixe). }
  37.  Raise EConvertError.CreateFmt(sInvalidCurrency, [S]);
  38. End;
  39.  
  40. Function Curr64ToStr(Value:Currency64):String;Begin
  41.  Result:=Format('%d%s%.4d',[Value div Currency64Scale,DecimalSeparator,Abs(Value mod Currency64Scale)]);
  42. 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 :

  1. Var
  2.  I:Integer;
  3.  Data:Array of Double; { Tableau dynamique entreposant des valeurs doubles }
  4.  F:TextFile;           { Lire les données de ce fichier }
  5.  Value:Double;
  6. Begin
  7.  AssignFile(F,'Truc.dat');
  8.  Reset(F);
  9.  While Not Eof(F) do Begin
  10.   ReadLn(F, Value);
  11.    { Méthode inefficace mais simple pour développer un tableau dynamique. Dans un programme réel, }
  12.    { vous devez augmenter la taille du tableau par gros morceaux, et non un élément à la fois. }
  13.   SetLength(Data, Length(Data) + 1);
  14.   Data[High(Data)] := Value;
  15.  End;
  16.  CloseFile(F);
  17. 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 :

  1. Procedure CantGrow(Var Data:Array of Integer);Begin
  2.  { Les données sont un tableau ouvert, elles ne peuvent donc pas changer de taille. }
  3. End;
  4.  
  5. Type
  6.   TArrayOfInteger=Array of Integer; { Type de tableau dynamique }
  7.   
  8. Procedure Grow(Var Data:TArrayOfInteger);Begin
  9.   { Les données sont un tableau dynamique, elles peuvent donc changer de taille. }
  10.  SetLength(Data, Length(Data)+1);
  11. 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 :

  1. 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 :

  1. Function AsString(Const Args:Array of Const):String;
  2. Var
  3.  I:Integer;
  4.  S:String;
  5. Begin
  6.  Result:='';
  7.  For I:=Low(Args) to High(Args) do Begin
  8.   Case Args[I].VType of
  9.    vtAnsiString: S:=PChar(Args[I].VAnsiString);
  10.    vtBoolean:    If Args[I].VBoolean Then S:='True'
  11.                                      Else S:='False';
  12.    vtChar:       S:=Args[I].VChar;
  13.    vtClass:      S:=Args[I].VClass.ClassName;
  14.    vtCurrency:   S:=FloatToStr(Args[I].VCurrency^);
  15.    vtExtended:   S:=FloatToStr(Args[I].VExtended^);
  16.    vtInt64:      S:=IntToStr(Args[I].VInt64^);
  17.    vtInteger:    S:=IntToStr(Args[I].VInteger);
  18.    vtInterface:  S:=Format('%p', [Args[I].VInterface]);
  19.    vtObject:     S:=Args[I].VObject.ClassName;
  20.    vtPChar:      S:=Args[I].VPChar;
  21.    vtPointer:    S:=Format('%p', [Args[I].VPointer]);
  22.    vtPWideChar:  S:=Args[I].VPWideChar;
  23.    vtString:     S:=Args[I].VString^;
  24.    vtVariant:    S:=Args[I].VVariant^;
  25.    vtWideChar:   S:=Args[I].VWideChar;
  26.    vtWideString: S:=WideString(Args[I].VWideString);
  27.    Else          Raise Exception.CreateFmt('Non-supporté VType=%d', [Args[I].VType]);
  28.   End;
  29.   Result:=Result+S;
  30.  End;
  31. 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 :

  1. '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 :

  1. Function QuelquesCFonc:WordBool; External 'LaDllC.dll';
  2. ...
  3. If QuelquesCFonc Then ...

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.

  1. Var
  2.  WordAppli:Variant;
  3. Begin
  4.  Try
  5.   WordAppli:=CreateOleObject('Word.Application');
  6.   WriteLn(WordAppli.Version);
  7.  Except
  8.   WriteLn('Word n''est pas installé');
  9.  End;
  10. End;

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 :

  1. Var
  2.  IntPtr:^Integer;
  3. Begin
  4.  ...
  5.  Inc(IntPtr);    { Faire pointer IntPtr vers le prochain entier, 4 octets plus tard }
  6.  Inc(IntPtr, 3); { Augmenter IntPtr de 12 octets = 3 * SizeOf(Integer) }

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 :

  1.  { Créez une palette de niveaux de gris avec des entrées NumColors. }
  2. Type
  3.  TNumColors = 1..256;
  4.  
  5. Function BuildGrayPalette(NumColors:TNumColors):HPalette;
  6. Var
  7.  Palette: PLogPalette;  { Pointeur vers un enregistrement TLogPalette }
  8.  I:TNumColors;
  9.  Gray:Byte;
  10. Begin
  11.   { 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. }
  12.  GetMem(Palette, SizeOf(TLogPalette)+(NumColors-1)*SizeOf(TPaletteEntry));
  13.  Try
  14.    { En Pascal standard, vous devez écrire Palette^.palVersion, mais Delphi déréférence automatiquement le pointeur. }
  15.   Palette.palVersion:=$300;
  16.   Palette.palNumEntries:=NumColors;
  17.   For I:=1 to NumColors do Begin
  18.     { Utilisez une échelle linéaire pour plus de simplicité, même si une échelle logarithmique donne de meilleurs résultats. }
  19.    Gray:=I*255 div NumColors;
  20.     { Désactivez la vérification de plage pour accéder aux entrées de palette au-delà de la première. }
  21.    {$R-}
  22.    Palette.palPalEntry[I-1].peRed   := Gray;
  23.    Palette.palPalEntry[I-1].peGreen := Gray;
  24.    Palette.palPalEntry[I-1].peBlue  := Gray;
  25.    Palette.palPalEntry[I-1].peFlags := 0;
  26.    {$R+}
  27.   End;
  28.    { Delphi ne déréférence pas automatiquement les pointeurs lorsqu'ils sont utilisés seuls, comme dans le cas suivant : }
  29.   Result := CreatePalette(Palette^);
  30.  Finally
  31.   FreeMem(Palette);
  32.  End;
  33. 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 :

  1. Type
  2.  TProcedureType=Procedure(Arg:Integer);
  3.  TFunctionType=Function(Arg:Integer):String;
  4. Var
  5.  Proced:TProcedureType;
  6.  Fonction:TFunctionType;
  7. Begin
  8.  Proced:=ProcedureQuelconque;
  9.  Proced(42); { Appelez Proced comme s'il s'agissait d'une procédure ordinaire }
  10. End;

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 :

  1. If Assigned(Proced)Then Proced(42);

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.

  1.  { Renvoie un numéro unique à chaque appel de la fonction. }
  2. Function Counter:Integer;
  3. Const
  4.  I:Integer=0;
  5. Begin
  6.  Inc(I);
  7.  Result:=I;
  8. End;

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 :

  1. Var
  2.  I:Integer = 0;
  3.  
  4. Function Counter:Integer;Begin
  5.  Inc(I);
  6.  Result:=I;
  7. End;

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 :

  1. Function CalculQuelqueChose:Begin
  2.  Try
  3.   EffectuerUnCalculDifficile;
  4.  Except
  5.   On Ex: EDivideByZero do WriteLn('Erreur de division par zéro');
  6.   On Ex: EOverflow do WriteLn('Erreur de débordement');
  7.   Else Raise; { relancer la même exception, à traiter ailleurs }
  8.  End;
  9. End;

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 :

  1. Var
  2.  NomDuFichierJournal: string = 'C:&#92;journal.txt';
  3.  
  4. Procedure JournalExceptProc(ExceptObject:TObject;ErrorAddr:Pointer);
  5. Const
  6.  Size=1024;
  7. ResourceString
  8.  Title='Erreur interne : veuillez la signaler au support technique';
  9. Var
  10.  Buffer:PChar[0..Size-1];
  11.  F:TextFile;
  12. Begin
  13.  ExceptionErrorMessage(ExceptObject,ExceptAddr,Buffer,Size);
  14.  AssignFile(F,NomDuFichierJournal);
  15.  If FileExists(NomDuFichierJournal)Then AppendFile(F)
  16.                                    Else Rewrite(F);
  17.  WriteLn(F, Buffer);
  18.  CloseFile(F);
  19.  MessageBox(0,Buffer,Title,Mb_IconStop);
  20. End;
  21. ...
  22.  { Dites à Delphi d'utiliser votre procédure d'exception.}
  23. 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 :

  1. Procedure SortieErrorProc(ErrorCode: Integer; ErrorAddr: Pointer);Begin
  2.  ShowMessage(Format('Erreur d''exécution %d à %p', [ErrorCode, ErrorAddr]));
  3. End;
  4. ...
  5. ErrorProc := @SortieErrorProc;

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 :

  1.  { Copier un fichier. Si le fichier source ne peut pas être ouvert ou si le fichier de destination ne peut pas être créé, }
  2.  { déclenchez EFileCopyError et incluez le message d'erreur d'origine dans le nouveau message d'exception. Le nouveau message } 
  3.  { donne un peu plus d'informations que le message d'origine. }
  4.  
  5. Type
  6.  EFileCopyError=Class(EStreamError);
  7.  
  8. Procedure CopyFile(Const SourceFile,TargetFile:String);
  9. Var
  10.  FromStream,ToStream:TFileStream;
  11. ResourceString
  12.  sCannotRead='mpossible de lire le fichier :%s';
  13.  sCannotCreate='Impossible de créer le fichier : %s';
  14. Begin
  15.  ToStream:=NIL;
  16.  FromStream:=NIL;
  17.  Try
  18.   Try
  19.    FromStream:=TFileStream.Create(TargetFile,fmOpenRead);
  20.   Except
  21.     { Gérer les exceptions EFopenError, mais aucun autre type d'exception. }
  22.    On Ex: EFOpenError do Raise EFileCopyError.CreateFmt(sCannotRead, [Ex.Message]); { Lever une nouvelle exception. }
  23.   End;
  24.   Try
  25.    ToStream:=TFileStream.Create(SourceFile,fmCreate);
  26.   Except
  27.    On Ex:EFCreateError do Raise EFileCopyError.CreateFmt(sCannotCreate, [Ex.Message]);
  28.   End;
  29.    { Copiez maintenant le fichier. }
  30.   ToStream.CopyFrom(FromStream, 0);
  31.  Finally
  32.    { C'est fait. Fermez les fichiers, même si une exception a été levée. }
  33.   ToStream.Free;
  34.   FromStream.Free;
  35.  End;
  36. 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 :

  1. Function AsString(I:Integer):String; Overload;
  2. Function AsString(Float:Extended):String; Overload;
  3. Function AsString(Float:Extended;MinWidth:Integer):String; Overload;
  4. Function AsString(Bool:Boolean):String; Overload; 

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 :

  1. S:=AsString(42);       { Appel AsString(Integer) }
  2. S:=AsString(42.0);     { Appel AsString(Extended) }
  3. 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 :

  1. Function AsString(Float:Extended):String; Overload;
  2. Function AsString(Float:Extended; MinWidth:Integer):String; Overload;

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 :

  1. Function AsString(Float:Extended):String;Begin
  2.  Result := AsString(Float, 1)
  3. End;

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 :

  1. Function AsString(Float:Extended; MinWidth:Integer=1):String;

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 :

  1.  { Calcul d'une factorielle en Delphi. }
  2. Function Factorial(Number:Cardinal):Int64;
  3. Var
  4.  I:Cardinal;
  5. Begin
  6.  Result:=1;
  7.  For I:=2 to Number do Result:=Result*I;
  8. end;
  9.  
  10.  { Calcul d'une factorielle en Pascal standard. }
  11. Function Factorial(Number:Integer):Integer;
  12. Var
  13.  I,Result:Integer;
  14. Begin
  15.  Result := 1;
  16.  For I:=2 to Number do Result:=Result*I;
  17.  Factorial:=Result;
  18. 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.



PARTAGER CETTE PAGE SUR
Dernière mise à jour : Mardi, le 14 janvier 2025