La gestion des packages et la compatibilité des composants avec Delphi 6

Olivier Dahan

Nombreux sont les fils de discussion sur les forums à parler de problèmes de compatibilité des composants avec Delphi 6. On cherche partout sur les disques les DsgnIntf.dcu ou .pas et on ne les trouve pas ! Horreur, erreur de packaging, on crie, on vocifère !

… Alors qu'il s'agit d'une méconnaissance de la stratégie de développement des composants conseillée depuis Delphi 5 (donc plus de deux ans – voir référence en fin d'article) – et aujourd'hui, après cette longue phase de transition, imposée dans Delphi 6.

Tout cela repose sur la séparation propre et nette des éditeurs de propriété et autres éditeurs de composants d'avec le code runtime utile des composants.
La mise en œuvre passe par l'utilisation des packages qui sont les grands incompris de Delphi alors que, introduits dès Delphi 3, ils ont apporté une souplesse dans la gestion des palettes de composants qui faisait cruellement défaut à Delphi 1 et son alter ego 32 bits, Delphi 2.

Nous présenterons ici les packages et la stratégie de développement convenable pour ne pas avoir de problème de compatibilité avec Delphi 6.

Cet article s'inspire de plusieurs publications en langue anglaise sur le net, notamment un excellent article de Xavier Pacheco datant de 1999 (pour preuve que le sujet est loin d'être récent !).

- Titre : La gestion des packages et la compatibilité des composants avec Delphi 6

- Auteur : Olivier Dahan

- Parution : 5 septembre 2001

- Mise à jour : 29 décembre 2019

- Licence : Copyright ® 2004 Olivier Dahan. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Vous avez dit package ?

- II. Pourquoi utiliser des packages ?

- II-A. Réduire la taille du code

- II-B. Englober un ensemble de composants

- III. Les différents types de packages

- IV. Les fichiers des packages

- V. Installation des packages dans l'IDE

- V-A. Les packages « Projet » et les packages « par défaut »

- VI. Créer vos propres packages

- VI-A. L'éditeur de package

- VI-A-1. La section Contains (contient)

- VI-A-2. La section Requires (nécessite)

- VI-A-3. Un peu de stratégie

- VII. La stratégie de conception des packages

- VII-A. Les packages de composants Design time et Runtime

- VII-B. L'enregistrement des composants

- VIII. Les directives de compilation spécifiques aux packages

- IX. Convention de nommage

- X. …et encore ?

I - Vous avez dit package ?

Sous Delphi 1 et 2, la bibliothèque de composants était une DLL qui contenait la totalité des composants installés dans l'IDE. L'idée était intéressante puisqu'elle permettait, grande nouveauté, de créer des composants avec le même outil de développement et de les intégrer dynamiquement à son espace de travail en une manipulation simple. Il faut rappeler que de nombreux outils de développement n'autorisaient pas, et n'autorisent toujours pas une telle souplesse et qu'il est fréquent qu'il faille utiliser un autre langage pour étendre leur IDE (lorsque cela est possible).

Toutefois, cette stratégie qui fut valable à la sortie de Delphi où les composants n'étaient pas trop nombreux et où les machines sous Windows 16 bits ne pouvaient pas offrir beaucoup de place mémoire ne pouvait plus être maintenue longtemps : la « Lib » devenait énorme, il fallait la recompiler à chaque ajout ou suppression de composants, et surtout tous les composants étaient présents en même temps, ce qui n'incitait pas en créer de trop.

Avec Delphi 3 Borland a introduit la notion de Package pour offrir cette souplesse indispensable à l'explosion des composants.

Les packages ne sont qu'une suite logique de la « Lib » de D1 et D2 puisque cela revient à avoir plusieurs « Lib » en même temps qu'on peut charger ou décharger à volonté.

Ainsi, les Packages sont des DLL répondant à un modèle particulier autorisant l'IDE de Delphi à reconnaître les composants ou experts qu'elle contient.
Cette caractéristique leur permet d'être exploités de façon particulière : au lieu de laisser Delphi associer (via son linker) le code des composants dans votre EXE, vous pouvez choisir d'utiliser les packages au runtime en accompagnement de vos EXE qui deviennent alors bien plus petits.

Les packages différents des DLL standard en ce sens qu'ils répondent à une norme spécifique à Delphi. À la différence des DLL que vous pouvez créer par ailleurs avec Delphi, les Packages compilés ne sont exploitables que par des applications Delphi.

Il existe deux utilisations des packages : l'une en mode Runtime (à l'exécution) l'autre en mode Designtime (à la conception sous Delphi), plus un troisième mode mixant les deux premiers. Nous étudierons plus loin les principales différences où se cache d'ailleurs la solution du problème de compatibilité avec Delphi 6.

II - Pourquoi utiliser des packages ?

II-A - Réduire la taille du code

Lorsqu'on utilise des packages « runtime », Delphi n'associe plus le code des composants à celui de votre EXE. Ce dernier devient donc beaucoup plus petit. Par contre vous devez fournir les packages compilés en accompagnement de votre logiciel. Comme le Linker de Delphi est intelligent (c'est un smart linker) il supprime le code non appelé, de fait, un EXE avec les packages de runtime semblera plus petit, mais l'ensemble des fichiers à fournir sera bien plus gros que l'EXE original sans package runtime.
Où se trouve la réduction de code alors ? … Elle est toute relative et ne s'apprécie que si vous devez fournir de nombreux EXE sur un même site client. À partir d'un moment, plein de petits EXE partageant les mêmes packages finissent par être bien moins gros que plusieurs EXE intégrant à chaque fois tout le code nécessaire.
À titre personnel l'auteur n'a jamais vu de situations réelles où cela soit un vrai avantage (les problèmes de stockage sur disque dur n'existent plus et la fourniture et le maintien d'un ensemble cohérent de packages possèdent un coût important à ne pas négliger).
On peut toutefois voir un avantage dans la séparation du code en unités indépendantes (les packages) notamment pour la distribution d'applications via Internet. Dans ce cadre vous pouvez fournir une fois pour toutes les packages de votre application et permettre une mise à jour plus rapide de votre EXE qui sera de taille plus réduite. La mise à jour d'un package, le cas échéant, sera elle aussi moins consommatrice de bande passante.

II-B - Englober un ensemble de composants

Si vous désirez distribuer un ensemble de composants sans fournir le code source, la création d'un package est une excellente solution. D'ailleurs même si vous souhaitez distribuer le code source, la création de composants passe aujourd'hui par celle de packages ! Les composants autonomes « à la » Delphi 1 et 2 n'existent plus et leur installation dans Delphi 3, 4, 5 et 6 passe par leur intégration dans un package « utilisateur », ce que propose l'expert dédié à cet effet dans l'IDE de Delphi.
Si vous créez des composants, alors vous devez tout savoir des packages !

III - Les différents types de packages

Il existe quatre types de packages :

1. Les packages Runtime. Ces packages contiennent du code (fonctions, etc.) et des composants. Ils sont nécessaires à l'exécution des applications qui s'en servent ;

2. Les packages de Design. Ces packages contiennent des composants, des éditeurs de propriétés, des éditeurs de composants, des experts, etc. Ils sont nécessaires à la conception des applications sous l'IDE de Delphi. Ils ne sont pas exploités à l'exécution de vos applications et n'ont pas à être distribués avec ces dernières ;

3. Les packages Design et Runtime. Ce type de package est utilisé lorsqu'il contient du code et des composants qui ne font pas appel à des éléments de conception (éditeurs de propriété, etc.). Si le package contient des éléments de conception, il posera de gros problèmes de compatibilité avec Delphi 6 ;

4. Les packages non Design et non Runtime. Voilà des bêtes bien curieuses ! C'est une espèce excessivement rare de packages. Leur unique vocation est d'être exploité par des références directes depuis une application ou depuis l'environnement de conception. De tels packages peuvent être intégrés dans d'autres packages (bibliothèque de fonctions par exemple).

IV - Les fichiers des packages

La table ci-dessous liste l'ensemble des fichiers liés directement à la notion de package :

.dpk

Source
(Delphi PacKage)

Ce fichier est créé par Delphi lorsque vous appelez l'Éditeur de package. Le « dpk » est l'équivalent du « dpr » pour un package. Il contient une description des unités plutôt que le code utile lui-même.

.dcp

code compilé
(Delphi Compiled Package)

Ce fichier contient la version compilée d'un package. Il stocke les entêtes et les symboles permettant le bon fonctionnement du package. Il contient aussi tous les « dcu » du package. Ce fichier est nécessaire pour construire une application qui fait usage du package en question.

.dcu

unités compilées
(Delphi Compiled Unit)

Comme pour un projet classique. Les « dcu » contiennent le code compilé de chaque unité du projet.

.bpl

bibliothèque compilée
(Borland Package Library ? doute..)

C'est le fichier runtime ou design time du package équivalent à une DLL Windows. Si le package est de type runtime, c'est ce fichier que vous distribuerez avec votre application. Si le package est de type design time, il faudra fournir ce fichier pour permettre l'utilisation de son code dans l'IDE.
Pour un package design time, si vous ne distribuez pas le code source, vous devrez aussi distribuer le fichier « dcp » avec votre « bpl ».

V - Installation des packages dans l'IDE

L'installation des packages dans l'IDE de Delphi est une chose simple et nécessaire si vous devez intégrer des composants tiers dans votre environnement.
La première chose à faire consiste à placer les différents fichiers au bon endroit. La table suivante liste les emplacements typiques des fichiers principaux d'un package.

*.Bpl

Les fichiers runtime des packages doivent être placés dans \Windows\System sous Windows 9x et sous \WinNT\System32 sous Windows NT.

*.Bpl

Les fichiers de packages Design doivent être placés dans un répertoire commun d'où ils peuvent être facilement gérés. Le mieux reste de créer un sous-répertoire de Delphi lui-même et d'y placer tous les « bpl » de ce type.

*.Dcp

Les fichiers de Symboles sont placés avec les « bpl » correspondants.

*.Dcu

Les unités de code compilées doivent être distribuées si vous distribuez un package de Design. Le mieux est de créer un sous-répertoire comme pour les Bpl de Design et de regrouper tous les « dcu » de packages Design à cet endroit.

Pour l'installation proprement dite des packages compilés, Delphi rend les choses très simples puisqu'il suffit d'ouvrir le dialogue ad hoc depuis le menu principal « Composant | Installer des paquets »

En cliquant sur le bouton « Ajouter », vous pouvez choisir un fichier « bpl ». Si le package contient des composants, ils seront affichés dans la palette.

V-A - Les packages « Projet » et les packages « par défaut »

Voici une « ruse » très peu connue et donc peu utilisée qui, pourtant, est très pratique : la configuration variable des packages.

Il n'y a pas de menu ni d'option spéciale pour çà, tout dépend si un projet est chargé ou non dans l'IDE.

De là provient certainement la méconnaissance de cette facette de l'installation des packages.

À quoi cela peut-il servir ?

C'est fort simple : si vous avez beaucoup de packages installés, cela prend de la place en mémoire, dans la palette de l'IDE, etc. Pire, les projets créés référencent par défaut ces packages et si vous portez les sources ailleurs, les développeurs auront des erreurs indiquant que le package « truc » est manquant alors qu'il ne sert à rien dans votre projet.

L'idée consiste donc à n'activer par défaut dans l'IDE que les packages utiles à tous les projets. Ensuite, dans chaque projet, vous ajoutez (ou enlevez) les packages nécessaires (ou superflus).

Comment procéder ?

Pour créer une sélection « par défaut », il suffit de fermer tous les fichiers ouverts dans l'IDE (menu « Fichier | Tout Fermer »), ensuite vous appelez la fenêtre d'installation des packages (figure précédente) et vous désélectionnez (la case à cocher) les packages inutiles. Par exemple les serveurs OLE ou le Decision Cube ne sont pas indispensables à tous les projets en général. Vous refermez la fenêtre d'installation des packages et vous quittez Delphi (il semble que cela fonctionne sans refermer Delphi, mais tout dépend de la version, alors pour plus de sûreté).

Ensuite, lorsque vous créez un nouveau projet, il suffit d'appeler les Options du projet et d'aller faire votre choix dans l'onglet « Paquets ». Delphi stocke les packages exclus dans le fichier "<monprojet>.DOF » sous la section "[Excluded Packages]".

VI - Créer vos propres packages

Avant de créer vos propres packages, vous devez prendre certaines décisions. La première consiste à décider du type du package : runtime, design, les deux, aucun. Le choix se fondera sur certains éléments que nous détaillerons plus bas. En second lieu vous devrez décider comment appeler votre package et où le placer. Enfin, vous devrez décider des unités qui seront intégrées au package ainsi que de l'inclusion d'éventuels autres packages.

VI-A - L'éditeur de package

La majorité du temps, vous créerez un nouveau package en invoquant le menu « Fichier | Nouveau » puis en sélectionnant « nouveau paquet ». L'éditeur de paquets, ci-dessus, sera affiché, vide bien entendu (à l'exception du paquet VCL50 présent dans la rubrique Requires).

On remarque que l'éditeur contient deux nœuds principaux : Contains et Requires.

VI-A-1 - La section Contains (contient)

Vous spécifiez ici l'ensemble des unités de code qui doivent être intégrées dans le package.
Un certain nombre de règles doivent être respectées :

1. Le package ne doit pas être référencé dans la section « Contains » d'un autre package ou dans la clause « Uses » d'une unité de code se trouvant dans un autre package ;

2. Les unités listées dans la section « Contains » d'un package, directement ou indirectement (donc dans la clause « uses » d'une unité), ne peuvent pas être listées dans la section « Requires » du package. Simplement, car elles sont déjà liées au package en apparaissant dans la section « Contains » ;

3. Vous ne pouvez pas ajouter une unité de code dans la section « Contains » d'un package si elle est déjà intégrée dans la section « Contains » d'un autre package utilisé par la même application.

VI-A-2 - La section Requires (nécessite)

Vous spécifiez ici tous les packages nécessaires au fonctionnement de votre package et qui n'apparaissent pas dans la clause « Contains ». On peut voir cette section comme l'équivalent de la clause « Uses » dans une unité de code.

En général et au minimum, les packages que vous créerez intégreront bibliothèque VCL50 (ou 60 pour Delphi 6), car c'est ce package qui contient la base de la VCL. D'ailleurs, comme nous le disions plus haut, Delphi ajoute automatiquement cette référence à la création d'un nouveau package. Vous pouvez bien entendu supprimer cette référence si votre package ne fait pas référence à la VCL, mais c'est une situation rarissime.

VI-A-3 - Un peu de stratégie

Le fameux monstre du loch Ness pointe le bout de son museau par ici !
En effet, le problème de DsgnIntf manquant sous Delphi 6 commence dès la conception d'un package de composants. Notamment pour éviter les problèmes de mélange entre code runtime et code de design, il était fortement conseillé de séparer les deux catégories en créant deux packages différents. Sous Delphi 6 c'est une obligation.

L'arrangement le plus typique est de placer le code de vos composants dans un package runtime et de créer ensuite un package de design qui intègre le premier dans sa section « Requires ».
Un tel placement de package en « Requires » doit répondre à quelques règles :

1. Pas de référence circulaire : le package 1 ne peut pas avoir le Package 2 en « Requires » si le package 2 possède le package 1 dans sa section « Requires » ;

2. Dans la chaîne des références, un package ne peut pas référencer un autre package déjà référencé dans la chaîne.

VII - La stratégie de conception des packages

Nous touchons ici à la cause, et à la solution, du problème de « DsgnIntf » manquant sous Delphi 5 ou Delphi 6. Mais ce qui pouvait se régler sous Delphi 5 par une copie du source de cette unité n'est plus possible sous Delphi 6, car l'unité n'est plus fournie du tout… D'où l'intérêt d'ouvrir grand vos yeux pour comprendre ce qui va suivre !

Comme indiqué plus haut dans cet article, la première chose à savoir quand on crée un package est le type de celui-ci. Nous allons ici voir comment choisir entre les types runtime, design time, etc.

VII-A - Les packages de composants Design time et Runtime

C'est le scénario classique pour celui qui conçoit des composants, car :

- vous souhaitez que les programmeurs qui utiliseront vos composants puissent compiler et lier votre code dans leurs propres applications ;

- vous souhaitez qu'ils puissent aussi référencer vos composants sans les lier en fournissant à leurs utilisateurs le package runtime ;

- vous ne souhaitez pas que les utilisateurs de votre package soient obligés de traîner le code de design (éditeurs, experts, etc.) dans leurs applications finales ;

- vous voulez que votre code soit compatible avec la philosophie proposée avec Delphi 5 et imposée avec Delphi 6 qui réclame que le code de Design soit séparé du code de Runtime.

Ici vous n'avez guère le choix de la structure à choisir. Il vous faudra créer deux packages distincts, l'un pour le Design et l'autre pour le Runtime.

Le dessin suivant schématise le jeu d'inclusion entre les deux packages :

Ainsi que vous le voyez, le package de Design (« MonPackLib50.Dpk ») intègre à la fois les fonctions de Design proprement dites (éditeurs divers) et l'enregistrement des composants (appel(s) à RegisterComponent dans l'unité « MonCompReg.Pas ») ainsi qu'une référence au package Runtime (« MonPackStd50.Dpk »). Ce dernier contenant le code du (des) composant(s) eux-mêmes (« Compomain.pas », « Compo2main.Pas », « MesFnctions.Pas »).

Le package de Runtime est donc intégré dans le package de Design par une référence dans la section « Requires » de ce dernier.

Pour que ce montage fonctionne, vous devez modifier correctement les options de chacun des deux packages.

Pour ce faire, vous devez appeler le dialogue Options (accessible par le bouton « Options » en haut à droite de la fenêtre de l'éditeur de package).
Dans cette fenêtre il faut sélectionner le bon type de package en cliquant sur l'un des boutons radio du groupe « Options d'utilisation », comme le montre l'illustration suivante :

Comme vous le voyez ci-dessus, il s'agit du package de Design des composants Indy. Le titre du package est parlant puisqu'on lit « …Property and Component Editors» et que le type (options d'utilisation) est bien à « Seulement en conception » (donc Design time).

Pour le package des composants eux-mêmes, l'option « Seulement en exécution» doit être cochée.

VII-B - L'enregistrement des composants

Lorsque vous créez un nouveau composant (et si vous passez par »nouveau | composant ») Delphi ajoute automatiquement une fonction « Register()» qui fait un appel à « RegisterComponents() ».
C'est cette procédure qui permet à l'IDE de repérer un code à installer.

Dans le montage qui vient d'être présenté, cette stratégie ne s'applique plus. Il convient de déplacer le code de Register() dans une unité séparée qui recensera l'ensemble des composants s'il y en a plusieurs ainsi que l'enregistrement des éditeurs et experts éventuels.

Cette unité, comme nous l'avons vu plus haut, appairait dans la section « Contains» du package de Design.
En faisant de la sorte, le code d'enregistrement se trouve bien là où il doit être : dans le package de Design, il ne sera pas intégré dans les applications qui utilisent vos composants, et mieux encore, cela interdira d'installer vos composants dans Delphi à partir du « bpl» si celui-ci est distribué avec les applications (au lieu d'être lié à l'EXE), ce qui constitue une protection supplémentaire pour gérer vos licences.

(Certains malins pourraient penser qu'il suffit de récréer une unité d'enregistrement puis un package de Design intégrant votre « bpl» pour contourner la chose… Certes… En effet, avec une telle astuce il sera possible de monter les composants dans la palette de Delphi et même de jouer un peu avec, mais il sera impossible de compiler une application, car il manquera les « dcu » …)

VIII - Les directives de compilation spécifiques aux packages

Certaines directives sont spécifiques aux packages, elles sont incluses soit dans le code même du package, soit dans les unités qui peuvent être utilisées par un package.

{$G} ou {$IMPORTEDDATA OFF}

Cette directive se place dans l'entête d'une unité pour l'empêcher d'être intégrée dans un package. Elle est en quelque sorte le contraire de {$WEAKPACKAGEUNIT} qui autorise l'ajout de l'unité dans un package, mais dont le code doit être lié statiquement à celui de l'application.

{$DENYPACKAGEUNIT}

Idem que {$G}

{$WEAKPACKAGEUNIT}

Force le code compilé de l'unité à être intégré dans l'EXE de façon statique plutôt que de rester dans le package (il est placé dans le « 'dcp » plutôt que dans le « bpl »). Cette directive a été créée pour régler des problèmes d'accès à des DLL éventuellement non existantes sur la machine au sein du noyau de la VCL. On ne s'en sert donc que de façon rarissime.

{$DESIGNONLY ON}

Directive de Package. Compile en mode Design seulement.

{$RUNONLY ON}

Directive de Package. Compile en mode Runtime seulement.

{$IMPLICITBUILD OFF}

Empêche le package d'être reconstruit implicitement par Delphi lors d'une reconstruction. À utiliser avec précaution, l'intérêt n'étant pas flagrant.

IX - Convention de nommage

Les packages vont souvent par paire, ne serait-ce que pour les composants, comme nous l'avons vu plus haut.

De plus, les packages sont dépendants de la version de Delphi, c'est-à-dire qu'un package compilé avec Delphi 5 ne fonctionnera qu'avec des applications compilées sous Delphi 5.
Il est dès lors essentiel de pouvoir faire la différence entre les packages compilés pour des versions différentes de Delphi.

Il n'y aucune règle fixe, mais il existe quelques conventions relativement bien suivies.

La première consiste à placer la version de Delphi sur deux chiffres dans le nom des packages. Par exemple le noyau de la VCL est contenu dans VCL50.bpl pour Delphi 5 et VCL60 pour Delphi 6.

La seconde convention consiste à bien identifier les packages Design time et Runtime.

L'une des normes suivies est celle qui consiste à ajouter, avant le numéro de version de Delphi, un sigle de trois lettres : Lib pour les packages de Design et Std pour les packages Runtime.

On trouve aussi une autre norme qui consiste à utiliser dcl pour les packages de Design et soit rien (Indy par exemple) soit les lettres Ctl (comme dans la RxLib).

Enfin, il est habituel de placer un préfixe de trois lettres indiquant le nom du créateur.

Reste à ajouter au milieu de tout çà le nom du package lui-même.

De fait, les deux packages d'un produit fictif « TOTO » créé par le célèbre John Smith pour Delphi 5 s'appelleraient :

- JSTotoLib50.dpk pour le package de Design, et,

- JSTotoStd50.dpk pour le package Runtime.

Dans Indy, les packages sont nommés :

- Indy50.dpk pour le Runtime Delphi 5, et,

- DclIndy50.dpk pour le package de Design.

Vous pouvez adopter d'autres conventions, tant qu'elles sont claires et permettent de bien gérer la version de Delphi et la finalité (Design ou Runtime).

X - …et encore ?

Il reste beaucoup encore à dire sur les packages ! C'est un sujet bien plus riche qu'on peut le penser de prime abord…

Par exemple, il est possible d'utiliser les packages pour créer un système de Plugin pour une application. On peut aussi tout simplement se servir des packages pour séparer le code d'une grosse application et ainsi rendre les mises à jour moins grosses (pour un envoi sur le Net par exemple).

Il resterait aussi à parler du système de distribution et d'installation automatique des packages contenu dans Delphi via le Package Collection Editor qui permet de créer des fichiers compressés contenant tout le nécessaire pour l'installation d'un package sur Delphi.

Mais nous en sommes déjà arrivés à la 3e page et, pour un petit topo sur les packages, vous avouerez que cela est bien suffisant ! :-)

Référence officielle sur le problème de DsgnIntf, extrait du fichier Readme.txt dans la racine de Delphi 5

DSGNINTF et le déploiement
-----------------------------------------------

DSGNINTF.DCU n'est plus fourni avec Delphi. C'est pourquoi les développeurs
de composants doivent isoler le code de conception en unités séparées du code d'exécution. Le code d'exécution ne doit pas faire référence à ces unités.

Si cette approche n'est pas possible, vous avez l'option de compiler DSGNINTF.PAS
(situé dans votre répertoire \source\toolsapi\).
Ceci doit cependant être considéré comme un pis-aller, et ne sera pas disponible comme option dans les versions futures.

Olivier Dahan

Ingénieur Certifié Borland Delphi

Pour écrire à l'auteur : cliquez ici !

TeamB-FR

Septembre 2001

Visitez le site http://www.e-naxos.com pour y découvrir MK Query Builder ou simplement pour accéder à la partie gratuite du site, le célèbre Delphi Stargate !