Packages Delphi - John COLIBRI. |
- résumé : les packages Delphi: exemple, structure, type de packages, chargement statique et dynamique
- mots clé : Package Delphi, Editeur de Package, Runtime Package, Design Time Package, Editeur d'Options, LoadPackage
- logiciel utilisé : Windows XP personnel, Delphi 6.0
- matériel utilisé : Pentium 2.800 Mhz, 512 Meg de mémoire, 250 Giga disque dur
- champ d'application : Delphi 5, Delphi 6, Delphi 7, Delphi 2005, Delphi
2006, Turbo Delphi
- niveau : développeur Delphi
- plan :
1 - Principe des Packages Delphi Les Packages Delphi ont la même fonction que les DLL: ils permettent de compiler des parties de codes qui peuvent être utilisées par plusieurs
applications. Mais lorsque nous utilisons des DLL Windows (Dynamic Link Library), il faut ajouter des instructions qui permettent d'exporter des informations Delphi, comme les CLASSes ou les tForm.
Lorsque nous utilisons des Packages c'est le compilateur Delphi qui se charge de mettre en forme ces exportation.
Les Packages sont utilisés dans deux cas - pour ajouter des composants à la Palette
- pour partitionner un .EXE en modules, afin de faciliter la mise à jour et le déploiement: il suffit de déployer la nouvelle version du Package, sans avoir besoin de recompiler le fichier .EXE. C'est en fait la technique
utilisée par Delphi pour ajouter un nouveau composant à la Palette: vous n'avez en effet pas besoin de recompiler l'IDE Delphi (dont vous n'avez pas le source de toutes les façons)
Dans cet article, nous allons présenter les techniques et outils pour créer des Packages Delphi.
2 - Exemple de Package Delphi 2.1 - Objectif
Nous allons montrer comment construire et utiliser un Package qui contient - une fonction de calcul
- une tForm Delphi.
2.2 - L'unité de Calcul
Voici une unité qui contient une fonction qui additionne deux nombres:
unit u_adder; interface
function f_one_plus_two(p_one, p_two: Integer): Integer; stdcall;
// -- Export the functions. exports
f_one_plus_two; implementation
function f_one_plus_two(p_one, p_two: Integer): Integer;
begin
Result:= p_one+ p_two;
end; // f_one_plus_two end | Notez que:
- nous avons forcé notre fonction à utiliser la convention de passage de paramètres Windows stdcall
- nous avons ajouté une clause d'exportation exports
2.3 - L'éditeur de Package Pour englober notre unité dans un Package, nous utilisons d'Editeur de Package: |
cliquez "File | New | Other " | | Delphi propose plusieurs éléments:
| | sélectionnez et cliquez "Package" (l'icône dans le cercle rouge) |
| Delphi ouvre l'Editeur de Package: | |
cliquez "Add" pour ajouter notre unité | | _Delphi présente un dialogue de recherche d'unité | | cliquez "Browse", localisez U_ADDER.PAS et cliquez "OK" |
| l'unité a été ajoutée au Package | | cliquez le bouton "compile"
| | Delphi compile le Package, et génère le code .DCU dans le répertoire du package: De plus, le répertoire par défaut C:\Program Files\Borland\Delphi6\ProjectsBpl contient un fichier .DCP et un fichier .BPL:
|
Si nous souhaitons charger un autre projet, Delphi nous demande si nous souhaitons sauvegarder le fichier PACKAGE1.DPK. Puisque nous sommes de toutes les façons obligés de sauvegarder le fichier
.DPK, autant le renommer tout de suite. Par conséquent: | sélectionnez "File | Save Project As", et tapez, par exemple PK_PACKAGE_FORM_FUNCTION.DPK
| | recompilez le package avec le nouveau nom | |
fermez par "File | Close All" |
2.4 - Inclure le Package dans un Projet Nous allons inclure le Package dynamiquement dans notre projet et appeler la fonction exportée par le Package.
Le chargement dynamique d'un Package et similaire à celui d'une .DLL: il faut appeler la fonction LoadPackage qui nous retourne la poignée du Package:
var my_package_handle: THandle;
my_package_handle:= LoadPackage('xxx.bpl'); |
A l'aide de la poignée, nous utilisons la fonction GetProcAddr pour récupérer l'adresse mémoire de n'importe quelle fonction exportée par le Package:
var my_address: dWord;
my_address:= GetProcAddress(my_package_handle, PChar('my_routine'));
| Lorsque la routine est une procédure sans paramètre, nous pouvons utiliser cette address pour appeler la procédure. Mais si la procédure a des paramètres,
ou s'il s'agit d'une fonction, il faut surtyper cette adresse pour que le compilateur comprenne qu'il faut pousser les paramètres sur la pile, appeler la routine, et éventuellement retourner des valeurs. Le plus simple est alors
- de définir le type procédural de la routine
- de déclarer une variable procédurale
- d'initialiser l'adresse de la variable procédurale par GetProcAddr
- d'appeler la routine.
Voici un exemple:
type t_f_my_function= function (p_param: Integer): integer; stdcall;
var my_f_function: t_f_my_function;
@my_f_function:= GetProcAddress(my_package_handle, PChar('my_f'));
my_result:= my_f_function(1234); |
Par conséquent, dans notre cas:
| 1- créez un nouveau projet, P_LOAD_PACKAGE | |
2- ajoutez 2 tEdit qui contiendront les valeurs à ajouter, un tButton pour appeler notre fonction et un tLabel pour afficher le résultat | |
3- tapez le code qui charge le Package, appelle la fonction et affiche le résultat:
const k_bpl_file_name= 'pk_add.bpl';
k_one_plus_two= 'f_one_plus_two';
type t_f_one_plus_two= function (p_one, p_two: Integer): integer; stdcall;
procedure TForm1.add_Click(Sender: TObject);
var l_package_handle: THandle;
l_f_one_plus_two: t_f_one_plus_two;
l_result: Integer;
l_one, l_two: integer; begin
l_package_handle:= LoadPackage(k_bpl_file_name);
@l_f_one_plus_two:= GetProcAddress(l_package_handle, PChar(k_one_plus_two));
l_one:= StrToInt(one_edit_.Text);
l_two:= StrToInt(two_edit_.Text);
l_result:= l_f_one_plus_two(l_one, l_two);
result_label_.Caption:= IntToStr(l_result);
UnloadPackage(l_package_handle); end; // add_Click |
| | 4- compilez et exécutez. Cliquez "Add" | |
voici le résultat: |
Notez que: - nous n'avons pas besoin d'inclure l'unité dans la clause USES
- le fichier Package.bpl doit être accessible par Delphi. Dans notre cas, comme le .BPL est dans le répertoire des Packages de Delphi, il suffit de fournir le nom (sans avoir à fournir de chemin)
3 - Anatomie d'un Package Delphi 3.1 - Les types de Packages Delphi peut utiliser plusieurs types de Packages:
- les Package d'exécution (runtime) qui sont incorporés, ou chargés, par les .EXE
- les Package de conception (design), destinés à être utilisés par l'Interface Delphi (IDE)
3.1.1 - Les Packages d'exécution Ces Packages sont destinés à être incorporés à des .EXE distribués chez les utilisateurs. Ils ne contiennent aucun éditeur de propriétés (l'utilisateur ne va pas recompiler le projet).
3.1.2 - Les Package de conception Ces Package accompagnent en général des composants, et contiennent - des éditeurs de propriétés
- des éditeurs de composants
- des Experts
Pour éviter tout piratage facile, Delphi ne fournit plus en source (depuis Delphi 6) les éditeurs de composants, et interdit par la license le déploiement de Packages contenant des éditeurs de propriétés chez des
utilisateurs qui n'ont pas acheté Delphi. Ceci limite donc les Packages de conception à l'ajout de fonctionalités à l'Interface de développement Delphi (l'IDE), ce qui, après tout, est bien leur vocation.
Néanmoins, si nous souhaitons fournir un composant qui contient du code qui sera utilisé aussi à l'exécution, nous sommes obligés de créer deux versions: une pour la conception, et une pour l'exécution.
3.2 - Choix du type de Package Comment choisir le type de Package: - s'il s'agit d'un composant qui sera incorporé à la Palette, nous recommandons de toujours créer deux Packages: un Package de conception et
un Package d'exécution
- pour les Packages destinés à débiter un .EXE gigantesque en modules sur disque (un .EXE principal et plusieurs Packages), nous utilisons des Packages d'exécution uniquement
Il est exact que si un composant n'a besoin d'aucun éditeur de propriété propre (les éditeurs standard d'Integer, de String etc lui suffisent), nous pourrions créer des Package mixtes (conception et exécution). Toutefois dès
que vous ajouterez à votre composant un élément "conception" (design) vous retomberez dans le cas où la séparation est nécessaire. La spécification du type de Package se fait par le dialogue des Options que nous examinerons ci-dessous ??
3.3 - Les fichiers d'un Package Lorsque nous utilisons un Package, Delphi va placer sur disque les fichiers suivants: - .DPK (Delphi PacKage) : le fichier qui contient le "texte"
du Package. Ce fichier ASCII spécifie les options de compilation, quelles UNITés sont inclues dans le Package et quels autres packages sont nécessaires pour pouvoir compiler ces UNITés
- .DCU (Delphi Compiled Units) : le résultat de la compilation d'une UNIT. C'est un fichier intermédiaire évitant de recompiler le .PAS si le texte du .PAS n'a pas été modifié depuis la dernière compilation
- .DCP (Delphi Compiled Package) : le fichier qui contient la partie compilée (fichier partiel, évitant les recompilations, l'équivalent pour le .DPK d'un .DCU)
- .BPL (Delphi Package Library) : la .DLL utilisable par les autres applications. C'est un "module windows", équivalent à une .DLL, mais avec les propriété Delphi permettant l'inclusion de CLASSes
3.4 - Structure d'un Package Nous pouvons voir le texte d'un fichier .DPK - soit en utilisant NotePad ("clic droit souris | ouvrir avec | NotePad")
- soit en utilisant l'Editeur de Package, en cliquant sur , Cet éditeur
correspond à un fichier sur disque (par défaut PACKAGE1.DPK), dont vous pouvez visualiser le source:
et voici le texte obtenu pour notre exemple:
package pk_add; {$R *.res}
{$ALIGN 8} {$ASSERTIONS ON} {$BOOLEVAL OFF}
{$DEBUGINFO ON} {$EXTENDEDSYNTAX ON} {$IMPORTEDDATA ON}
{$IOCHECKS ON} {$LOCALSYMBOLS ON} {$LONGSTRINGS ON}
{$OPENSTRINGS ON} {$OPTIMIZATION ON} {$OVERFLOWCHECKS OFF}
{$RANGECHECKS OFF} {$REFERENCEINFO ON} {$SAFEDIVIDE OFF}
{$STACKFRAMES OFF} {$TYPEDADDRESS OFF} {$VARSTRINGCHECKS ON}
{$WRITEABLECONST OFF} {$MINENUMSIZE 1} {$IMAGEBASE $400000}
{$IMPLICITBUILD OFF} requires rtl;
contains u_adder in 'u_adder.pas';
end. | Ce texte est composé de 3 parties essentielles - les options de compilation
- la partie CONTAINS
- la partie REQUIRES
Cette liste correspond à des options de compilation. La plupart sont des options que nous pouvons aussi utiliser avec des .DPR ou des UNITés Delphi,
comme $R+ pour vérifier les bornes des intervalles. Les options spécifiques aux Packages sont les suivantes: - les plus importantes:
- {$DESIGNONLY ON} et {$RUNONLY ON} : spécifie un Package de conception /
d'exécution
- {$LIBPREFIX 'my_string'}: ajoute automatiquement le préfixe au début du fichier généré (par exemple "IP" pour l'Institut Pascal)
- {$LIBSUFFIX 'my_string'}: ajoute automatiquement le suffixe au début du
fichier généré. En général nous plaçons là le numéro de la version Delphi: "60" pour Delphi 6
- moins fréquemment utilisées
- {$LIBVERSION 'my_string'}: version Linux
- {$DESCRIPTION 'my_string'}: une description, qui sera affichée dans l'IDE
- {$IMAGEBASE $xxxxxx}: l'adresse de chargement. Utilisé si nous souhaitons une adresse de chargement particulière. Les valeurs possibles
recommandées sont dans la plage $4000.0000..$7FFF.FFFF
- {$IMPLICITBUILD ON | OFF}: la valeur OFF évite la reconstruction du Package. A utiliser pour les Package qui évoluent peu
Les unités d'un Package peuvent aussi indiquer comment l'unité sera utilisée dans le Package: - {$IMPORTEDDATA ON | OFF}: la valeur OFF empêche l'accès aux variables des
autres Packages. Cette optimisation est rarement utilisée
- {$DENYPACAGEUNIT}: interdit à une unité d'être placée dans un Package
- {$WEAKPACKAGEUNIT}: utilisé essentiellement par les éditeurs de librairies
3.4.2 - La clause Contains Nous indiquons ici les unités que nous souhaitons inclure dans le Package 3.4.3 - La clause Requires Spécifie la liste des Packages qui sont nécessaires pour pouvoir compiler les
unités listées dans Contains. Si les unités citées dans Contains nécessitent d'autres Packages, ils doivent être ajoutés à la clause Requires. Si nous oublions de lister un Package, le compilateur présentera un
avertissement, et ajoutera les Packages nécessaires implicitement. Les importations Requires ne peuvent former un cycle. Le compilateur nous le signalera.
De plus un Package ne peut apparaître dans la clause Contains. Et une unité ne peut être citée que dans la clause Contains de l'un des Packages (mais
nous pouvons ajouter le premier Package à la clause Requires du second Package). Notez que cette règle interdit d'installer dans l'IDE un composant
dont le Package contient dans Contains une unité de la VCL (mais le Package du composant peut incorporer les Packages correspondants dans Requires) Notez que:
- lorsque nous importons les packages par la clause Requires, Delphi utilise le nom du .DCP (et non pas le nom du .DPK ou du .BPL)
- le nom du .DCP est SANS préfixe ("RTL.DCP" et non pas "RTL60.DCP")
- en revanche, le suffixe que nous avons indiqué sera ajouté au .BPL: avec le préfixe "IP_" et le suffixe "60", le fichier sur disque sera IP_pk_add60.bpl
L'intérêt est que lorsque nous changeons de version Delphi, il suffit de
recompiler les packages, sans avoir à modifier manuellement les clauses Requires
3.5 - L'emplacement des fichiers Par défaut, les fichiers .DCP et .BPL sont stockés dans le même répertoire que
le source du Package (le .DPK). Toutefois, il est important que les fichiers .BPL et .DCP soient dans des répertoires où soit l'IDE Delphi, soit les projets utilisateurs du Package pourront les trouver. Très souvent ces
fichiers sont placés dans C:\WINDOWS\WIN32. Mais nous pouvons aussi les placer dans des sous répertoires de Delphi ("C:\PROGRAM FILES\BORLAND\DELPHI6\") ou tout autre répertoire accessible par une entrée dans le PATH de Windows.
Pour indiquer au compilateur de Package où placer les différents fichiers, nous utilisons le dialogue des Options, qui sert aussi a choisir les options d'un Package
3.6 - Le Dialogue des Options
Pour indiquer quelles options nous souhaitons utiliser, nous pouvons utiliser le bouton "Options" de l'Editeur de Packages
3.6.1 - Spécification des chemins Pour spécifier dans quel dossier seront placés les fichiers du Package: |
cliquez sur le bouton "Options", puis sélectionnez l'onglet "Directories" | | Delphi ouvre un dialogue similaire à celui de "Project | Options"
| Et: - "Output Directory" indique où sera placé le .BPL (la .DLL finale)
- "Unit Output Directory" indique où seront placés les .DCU
- "Search Path" permet d'aller charger les unités depuis d'autres répertoires
- "DCP Output Directory" désigne le répertoire du .DCP (fichier intermédiaire)
3.6.2 - Type de Package
Le type conception / exécution est spécifié dans l'onglet "Description": | cliquez sur le bouton "Options", puis sélectionnez l'onglet "Description" |
| Delphi présente une page avec plusieurs options, dont le type de Package sous "Usage Options":
|
3.6.3 - Préfixes et Suffixes Les Préfixes et Suffixes
- Comme les Package doivent être uniques pour un projet donné, Delphi recommande d'utiliser pour chaque Package un préfixe qui est propre au développeur ou à la société qui propose le Package. Par exemple nous pouvons
préfixer nos Packages par IP_ (Institut Pascal)
- en ce qui concerne la version Delphi, Delphi propose d'ajouter le suffixe 60 pour Delphi 6, 70 pour Delphi 7 etc
Nous pouvons saisir ces préfixes et suffixes manuellement dans les options du source du Package. Mais nous pouvons aussi utiliser l'onglet "Description" du dialogue des Options de l'Editeur de Package:
4 - Utilisation d'un Package Delphi 4.1 - Composant dans un Package
Si notre Package contient un composant, l'inclusion est simple - nous plaçons le composant sur la Palette
- lorsque nous déposons le composant sur une tForm, Delphi ajoutera
automatiquement le nom des UNITés nécessaires à la clause USES
4.2 - Package d'Exécution Si notre Package correspond à un Package d'exécution, nous avons deux possibilités:
- inclure le code du Package dans le fichier .EXE
- charger dynamiquement le Package .BPL pendant l'exécution du projet
4.2.1 - Inclusion du .BPL dans l'.EXE
Dans le premier cas, il suffit de placer dans les clauses USES les unités qui nous intéressent. En fait, si c'est notre seule utilisation de ce Package, il n'aucune utilité:autant importer directement les unités dans les clauses
USES.
4.2.2 - Chargement Dynamique du .BPL Dans le second cas, nous souhaitons que le fichier .EXE ne contienne PAS le code binaire des UNITés du Package. Pour cela, il suffit de l'indiquer dans
le dialogue des options de d'Editeur de Packages:
Mentionnons que le dialogue "Project | Options | Packages" comporte aussi un
tEdit contenant un certain nombre de fichier. Ces noms correspondent à des fichiers .DCP disponibles pour votre projet. Et si une des UNITés de votre projet a besoin d'un élément de l'un de ces .DCP, le binaire correspondant ne
sera PAS incorporé au fichier .EXE, mais proviendra du chargement du fichier .BPL correspondant. Ce tEdit n'est pas là, comme nous le pensions, pour nous permettre d'ajouter
certains Packages à notre projet, mais pour indiquer, parmi tous les Packages dont notre projet a besoin, lesquels devront être chargés dynamiquement, et lesquels seront, par différence, inclus dans le fichier .EXE. Prenons un exemple:
- nous souhaitons utiliser des bases de données (VCLDB)
- si VclDb ne figure pas dans l'Edit, le binaire de DB.PAS etc seront dans le fichier .EXE
- si VclDb est dans l'Edit, le fichier .EXE ne contiendra rien concernant
DB.PAS, mais le .BPL qui contient ce code sera chargé dès que nous toucherons aux tDataSet etc
4.3 - Chargement statique et Dynamique Notez que en cochant "build with Runtime packages" ne préjuge pas du type de
chargement: statique ou dynamique. Pour provoquer un chargement statique, ajoutez l'unité à la clause USES. Pour provoquer un chargement dynamique, il faut appeler les fonction
LoadPackage / UnloadPackage en fournissant le (chemin et le) nom du fichier .BPL
5 - Télécharger le code source Delphi Vous pouvez télécharger: Ce .ZIP qui comprend: - le .DPR, la forme principale, les formes annexes eventuelles
- les fichiers de paramètres (le schéma et le batch de création)
- dans chaque .ZIP, toutes les librairies nécessaires à chaque projet (chaque .ZIP est autonaume)
Ces .ZIP, pour les projets en Delphi 6, contiennent des chemins RELATIFS. Par conséquent:
- créez un répertoire n'importe où sur votre machine
- placez le .ZIP dans ce répertoire
- dézippez et les sous-répertoires nécessaires seront créés
- compilez et exécutez
Ces .ZIP ne modifient pas votre PC (pas de changement de la Base de Registre, de DLL ou autre). Pour supprimer le projet, effacez le répertoire. La notation utilisée est la notation alsacienne qui consiste à préfixer les
identificateurs par la zone de compilation: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lasse. Elle est présentée plus en
détail dans l'article La Notation Alsacienne
Comme d'habitude:
- nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections
qui en résulteront pourront aider les prochains lecteurs
- tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même
les bienvenus à jcolibri@jcolibri.com.
- plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en
cliquant "envoyer" :
- et si vous avez apprécié cet article, faites connaître notre site,
ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.
6 - L'auteur John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le
conseil (composants, architecture, test) et la
formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et
UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client. |