 |
|
Marco Cantù
L'essentiel sur Pascal
Traduit de l'anglais par Iannis Papageorgiadis [email protected] |
Chapitre 11
Programme et unités
|
Les applications Delphi font un usage intensif d'unités, ou modules
de programme. Avant l'introduction des classes, les unités constituaient
en fait la base de la modularité dans le langage. Dans une application
Delphi, chaque fiche est accompagnée de son unité. Lorsqu'on
ajoute une nouvelle fiche à un projet (à l'aide du bouton
correspondant de la barre d'outils ou à l'aide de la commande Nouvelle
fiche du menu Fichier), Delphi ajoute en réalité
une nouvelle unité qui définit la classe de la nouvelle fiche.
Les unités
Bien que chaque fiche soit définie dans une unité, le contraire
n'est pas vrai. Les unités ne doivent pas définir des fiches;
elles peuvent simplement définir et rendre disponibles un ensemble
de routines. En sélectionnant la commande Nouveau ... du
menu Fichier et ensuite l'icône Unité dans la
page Nouveaux éléments du Référentiel
d'objets, on peut ajouter une nouvelle unité vide au projet
courant. Cette unité vide contient le code suivant qui délimite
les sections de l'unité :
unit Unit1;
interface
implementation
end.
Le concept d'unité est simple. Une unité possède un
nom unique correspondant à son nom de fichier, une section interface,
dans laquelle on déclare ce qui est visible aux autres unités,
et une section implementation contenant le code effectif et d'autres
déclarations cachées. Enfin, l'unité peut comporter
une section facultative initialization avec du code de démarrage
qui sera exécuté lors du chargement du programme en mémoire;
elle peut également comporter une section facultative finalization
qui sera exécutée à l'arrêt du programme.
La structure générale d'une unité, avec toutes
ses parties possibles, est la suivante :
unit unitName;
interface
// les autres unités auxquelles on doit faire référence
uses
A, B, C;
// définition des types exportés
type
newType = TypeDefinition;
// constantes exportées
const
Zero = 0;
// variables globales
var
Total: Integer;
// listes des fonctions et des procédures exportées
procedure MyProc;
implementation
uses
D, E;
// variables globales cachées
var
PartialTotal: Integer;
// le code de toutes les fonctions exportées doit être écrit
procedure MyProc;
begin
// ... code de la procédure MyProc
end;
initialization
// partie initialisation facultative
finalization
// code de nettoyage facultatif
end.
La clause uses au début de la section interface indique
à quelles autres unités on doit avoir accès dans la
partie interface de l'unité. Y sont comprises les unités
qui définissent les types de données auxquels on se réfère
dans la définition d'autres types de données, tels que les
composants utilisés dans la fiche que l'on est en train de définir.
La seconde clause uses, au début de la section
implémentation, indique les unités auxquelles on doit accéder
uniquement dans le code de l'implémentation. Si l'on doit se référer
à d'autres unités à partir du code des routines et
des méthodes, on doit ajouter ces unités à cette seconde
clause uses plutôt qu'à la première. Toutes
les unités auxquelles on fait référence doivent être
présentes dans le répertoire du projet ou dans un répertoire
du chemin de recherche (on peut définir le chemin de recherche pour
un projet dans la page Répertoires / Conditions de la boîte
de dialogue Options de projet de l'option Options... du menu
Projet).
Les programmeurs C++ doivent savoir que l'instruction
uses ne correspond pas à une directive include. L'effet
d'une instruction uses est seulement d'importer la portion interface
précompilée des unités listées. La portion
implémentation de l'unité est considérée uniquement
lorsque cette unité est compilée. Les unités auxquelles
on se réfère peuvent se trouver soit sous forme de code source
(PAS) soit sous forme compilée (DCU), mais la compilation doit avoir
été effectuée avec la même version Delphi.
L'interface d'une unité peut déclarer un nombre d'éléments
différents, y compris des procédures, des fonctions, des
variables globales et des types de données. Dans les applications
Delphi, les types de données sont probablement les éléments
le plus souvent utilisés. Delphi place automatiquement un nouveau
type de données class dans une unité à chaque
création d'une fiche. Mais contenir les définitions de fiche
n'est certes pas la seule utilisation des unités dans Delphi. On
peut encore disposer d'unités traditionnelles, avec fonctions et
procédures, et on peut avoir des unités avec des classes
qui ne se réfèrent pas à des fiches ou à d'autres
éléments visuels.
Unités et portée
En Pascal, les unités sont la clé de l'encapsulation et de
la visibilité, et sont probablement encore plus importantes que
les mots réservés private et public d'une classe.
(En fait, comme on le verra au prochain chapitre, l'effet du mot réservé
private est relatif à la portée de l'unité
qui contient la classe). La portée d'un identificateur (comme une
variable, une procédure, une fonction, ou un type de données)
est la portion de code dans lequel l'identificateur est accessible. La
règle générale est qu'un identificateur est significatif
uniquement à l'intérieur de sa portée -- c'est-à-dire,
uniquement dans le bloc dans lequel il est déclaré. On ne
peut pas utiliser un identificateur à l'extérieur de sa portée.
Voici quelques exemples :
-
Les variables locales : Si on déclare une variable à l'intérieur
d'un bloc qui définit une routine ou une méthode, on ne peut
pas utiliser cette variable à l'extérieur de cette procédure.
La portée de l'identificateur englobe toute la procédure,
y compris les routines imbriquées (à moins qu'un autre identificateur
avec le même nom dans la routine imbriquée ne cache la définition
extérieure). La mémoire pour cette variable est allouée
sur la pile pendant que le programme exécute la routine qui la définit.
Dès que la routine a fini, la mémoire sur la pile est automatiquement
libérée.
-
Les variables globales cachées : Si on déclare un identificateur
dans la partie implémentation d'une unité, on ne peut pas
l'utiliser à l'extérieur de l'unité, mais on peut
l'utiliser dans chaque bloc et chaque procédure définis à
l'intérieur de l'unité. La mémoire pour cette variable
est allouée dès le début du programme et elle existe
jusqu'à la fin. On peut utiliser la partie initialisation de l'unité
pour initialiser la variable avec une valeur spécifique.
-
Les variables globales : Si on déclare un identificateur dans la
partie interface de l'unité, sa portée s'étend à
toute unité qui utilise l'unité qui la déclare. Cette
variable utilise de la mémoire et a la même durée de
vie que celle du groupe précédent; la seule différence
réside dans sa visibilité.
Toutes les déclarations dans la partie interface d'une unité
sont accessibles à partir de toute partie du programme qui comprend
l'unité dans sa clause uses. Les variables des classes fiche
sont déclarées de la même façon; on peut ainsi
faire référence à une fiche (et à ses champs
publics, méthodes, propriétés et composants) à
partir du code de toute autre fiche. Déclarer tout en global est,
bien sûr, une pratique de programmation pauvre. En bref, on devrait
utiliser le plus petit nombre possible de variables globales.
Unités et homonymie
L'instruction uses constitue la technique standard pour accéder
à la portée d'une autre unité. On peut ainsi accéder
aux définitions de l'unité. Mais il se pourrait que deux
unités auxquelles on se réfère déclarent le
même identificateur; ce qui revient à dire qu'on pourrait
avoir deux classes ou deux routines portant le même nom.
Dans ce cas, on peut simplement utiliser le nom de l'unité pour
préfixer le nom du type ou de la routine définis dans l'unité.
On peut, par exemple, faire référence à la procédure
ComputeTotal définie dans l'unité Totals en
écrivant Totals.ComputeTotal. Cela ne devrait pas souvent
être nécessaire, puisqu'on est sérieusement mis en
garde contre le fait d'utiliser dans un programme le même nom pour
deux objets différents.
Cependant, si on regarde dans la bibliothèque VCL et dans les
fichiers Windows, on verra que quelques fonctions Delphi ont le même
nom (mais généralement plutôt des paramètres
différents) que quelques fonctions API Windows disponibles dans
Delphi même. Un exemple est constitué par la simple procédure
Beep.
Si on crée un nouveau programme Delphi, on ajoute un bouton et
on écrit le code suivant :
procedure TForm1.Button1Click(Sender: TObject);
begin
Beep;
end;
dès que l'on presse le bouton, on entend un son bref.
Modifions maintenant l'instruction uses de l'unité :
uses
Windows, Messages, SysUtils, Classes, ...
pour qu'elle prenne cette version fort semblable (déplacer simplement
l'unité SysUtils avant l'unité Windows) :
uses
SysUtils, Windows, Messages, Classes, ...
Si on essaie de recompiler ce code, on obtiendra une erreur de compilation
: "Pas assez de paramètres originaux". Le problème provient
du fait que Windows définit une autre fonction Beep comportant deux
paramètres. Généralement les définitions des
premières unités que l'on inclut dans la clause uses
sont masquées par les définitions correspondantes des unités
indiquées plus loin. La solution sûre et réellement
très simple est la suivante:
procedure TForm1.Button1Click(Sender: TObject);
begin
SysUtils.Beep;
end;
Ce code sera compilé sans se soucier de l'ordre des unités
dans les instructions uses. Quelques autres collisions ont lieu
dans Delphi pour la simple raison que le code Delphi comporte généralement
des méthodes de classes. Avoir deux méthodes avec le même
nom dans deux classes différentes ne pose aucun problème.
Les problèmes surviennent uniquement avec les routines globales.
Unités et programmes
Une application Delphi est composée de deux sortes de fichiers de
code source : une ou plusieurs unités, et un fichier programme.
On peut considérer les unités comme des fichiers secondaires
auxquels fait référence la partie principale de l'application,
le programme. Théoriquement, ceci est vrai. Pratiquement, le fichier
programme est un fichier généré automatiquement et
il joue un rôle limité. Il sert uniquement à démarrer
le programme en faisant tourner la fiche principale. Le code du fichier
programme, ou fichier projet Delphi (DPR), peut être constitué
soit manuellement, soit en utilisant le Gestionnaire de projet (N.D.T.
: menu Voir) et quelques Options de Projet (menu Projet
| Options ...) relatives à l'objet application et
aux fiches.
La structure du fichier programme est habituellement plus simple que
la structure des unités. Voici le code source d'un fichier programme
simple :
program Project1;
uses
Forms,
Unit1 in ‘Unit1.PAS’ {Form1DateForm};
begin
Application.Initialize;
Application.CreateForm (TForm1, Form1);
Application.Run;
end.
Comme on le voit, il y a simplement une section uses et le code
principal de l'application entre les mots réservés begin
et end. L'instruction uses du programme est très importante
parce qu'elle servira à la gestion de la compilation et à
la création des liens de l'application.
Conclusion
Pour le moment du moins, ce chapitre sur la structure d'une application
Pascal, écrite en Delphi ou à l'aide d'une des dernières
versions de Turbo Pascal, est le dernier de l'ouvrage. Vous pouvez m'envoyer
par e-mail vos commentaires et vos questions.
Si après cette introduction au langage Pascal, vous souhaitez
approfondir les éléments de l'orienté-objet
de Pascal, vous pouvez vous référer à mon livre
Mastering Delphi 5 (Sybex, 1999). Vous trouverez plus d'informations
sur celui-ci et sur mes autres livres plus avancés (ainsi que sur
ceux d'autres auteurs) sur mon site www.marcocantu.com.
Le même site héberge des versions mises à jour de ce
livre, et ses exemples.
© Copyright Marco Cantù, Wintech Italia
Srl 1995-99