| 
   |  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 DelphiLes 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
  Dans cet article, nous allons présenter les techniques et outils pour créer des Packages Delphi.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)
  
 
 
 2 - Exemple de Package Delphi2.1 - ObjectifNous allons montrer comment construire et utiliser un Package qui contient une fonction de calcul
une tForm Delphi.
 
 
 2.2 - L'unité de CalculVoici 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 PackagePour 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 ProjetNous 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  
Voici un exemple: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.
     | 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 Delphi3.1 - Les types de PackagesDelphi 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écutionCes 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 conceptionCes Package accompagnent en général des composants, et contiennent 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.des éditeurs de propriétés
des éditeurs de composants
des Experts
  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 PackageComment choisir le type de Package: 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.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
  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 PackageLorsque 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 PackageNous pouvons voir le texte d'un fichier .DPK et voici le texte obtenu pour notre exemple: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:   
     | 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}
 
    requiresrtl;
 
 
  containsu_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 unités d'un Package peuvent aussi indiquer comment l'unité sera utilisée dans le Package: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
  {$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 ContainsNous indiquons ici les unités que nous souhaitons inclure dans le Package
 3.4.3 - La clause RequiresSpé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:  
L'intérêt est que lorsque nous changeons de version Delphi, il suffit de
recompiler les packages, sans avoir à modifier manuellement les clauses Requireslorsque 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
  
 
 3.5 - L'emplacement des fichiersPar 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 OptionsPour indiquer quelles options nous souhaitons utiliser, nous pouvons utiliser le bouton "Options" de l'Editeur de Packages    
 
 3.6.1 - Spécification des cheminsPour spécifier dans quel dossier seront placés les fichiers du Package: Et: |   | cliquez sur le bouton "Options", puis sélectionnez l'onglet "Directories"   |   |   | Delphi ouvre un dialogue similaire à celui de "Project | Options" 
 
 |   "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 PackageLe 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 SuffixesLes Préfixes et Suffixes
Nous pouvons saisir ces préfixes et suffixes manuellement dans les options du source du Package.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
  Mais nous pouvons aussi utiliser l'onglet "Description" du dialogue des Options de l'Editeur de Package:
     
 
 
 4 - Utilisation d'un Package Delphi4.1 - Composant dans un PackageSi 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écutionSi 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'.EXEDans 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 .BPLDans 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 DynamiqueNotez 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 DelphiVous pouvez télécharger: Ce .ZIP qui comprend:   Ces .ZIP, pour les projets en Delphi 6, contiennent des chemins RELATIFS. Par conséquent: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 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.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
  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'auteurJohn 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.
 |