Pourquoi utiliser des packages ?
La réponse est simple : grâce à la puissance du package. Les packages au moment de la conception simplifient la publication et l'installation de composants personnalisés ; les packages au moment de l'exécution injectent une nouvelle puissance dans la programmation traditionnelle. Une fois que vous avez compilé du code réutilisable dans une bibliothèque d'exécution, vous pouvez le partager sur plusieurs applications. Toutes les applications peuvent accéder aux composants standard via des packages, et Delphi lui-même le fait. Étant donné que l'application n'a pas besoin de copier une bibliothèque de composants distincte dans le fichier exécutable, cela économise considérablement les ressources système et l'espace disque. De plus, les packages réduisent le temps consacré à la compilation car il vous suffit de compiler le code spécifique à l'application.
Si les packages peuvent être utilisés de manière dynamique, nous pouvons alors obtenir plus d'avantages. Les packages offrent une nouvelle approche modulaire du développement d'applications. Parfois, vous souhaiterez peut-être rendre certains modules facultatifs de l'application, comme un système de comptabilité fourni avec un module RH facultatif. Dans certains cas, il vous suffit d'installer l'application de base, tandis que dans d'autres cas, vous devrez peut-être installer des modules RH supplémentaires. Cette approche modulaire peut être facilement mise en œuvre grâce à la technologie des packages. Dans le passé, cela ne pouvait être réalisé qu'en chargeant dynamiquement une DLL, mais en utilisant la technologie de packaging de Delphi, vous pouvez « regrouper » chaque type de module de l'application dans des bundles. En particulier, les objets de classe créés à partir de packages appartiennent à l'application et peuvent donc interagir avec les objets de l'application.
Packages et applications d'exécution
De nombreux développeurs considèrent uniquement les packages Delphi comme un endroit où placer des composants, alors qu'en fait les packages peuvent (et doivent) être utilisés dans la conception d'applications modulaires.
Pour montrer comment utiliser des packages pour modulariser votre application, créons un exemple :
1. Créez un nouveau programme Delphi avec deux formulaires : Form1 et Form2 ;
2. Supprimez Form2 de la liste de formulaires créée automatiquement (PRoject | Options | Forms) ;
3. Placez un bouton sur Form1 et entrez le code suivant dans le gestionnaire d'événements OnClick du bouton :
avec TForm2.Create(application) faire
commencer
AfficherModal ;
Gratuit;
Fin;
4. N'oubliez pas d'ajouter Unit2 à la clause use de Unit1 ;
5. Enregistrez et exécutez le projet.
Nous avons créé une application simple qui affiche un formulaire avec un bouton qui, lorsque vous cliquez dessus, crée et affiche un autre formulaire.
Mais que devons-nous faire si nous voulons inclure Form2 dans l'exemple ci-dessus dans un module réutilisable et le faire fonctionner normalement ?
La réponse est : Bao !
La création d'un package pour Form2 nécessite le travail suivant :
1. Ouvrez le gestionnaire de projet (Affichage | Gestionnaire de projet) ;
2. Cliquez avec le bouton droit sur le groupe de projets et sélectionnez « Ajouter un nouveau projet » ;
3. Sélectionnez « Package » dans la liste de projets « Nouveau » ;
4. Vous devriez maintenant pouvoir voir l'éditeur de package ;
5. Sélectionnez l'élément « Contient » et cliquez sur le bouton « Ajouter » ;
6. Cliquez ensuite sur le bouton « Parcourir... » et sélectionnez « Unit2.pas » ;
7. Le package devrait maintenant contenir l'unité "Unit2.pas" ;
8. Enfin, enregistrez et compilez le package.
Nous avons maintenant terminé le package. Il devrait y avoir un fichier nommé « package1.bpl » dans votre répertoire Project/BPL. (BPL est l'abréviation de Borland Package Library et DCP est l'abréviation de Delphi CompiledPackage.)
Ce package est complet. Nous devons maintenant activer le commutateur d'options du package
et recompilez l'application d'origine.
1. Double-cliquez sur « Project1.exe » dans le gestionnaire de projet pour sélectionner le projet ;
2. Faites un clic droit et sélectionnez "Options..." (vous pouvez également sélectionner Projet | Options... dans le menu) ;
3. Sélectionnez la page d'option « Packages » ;
4. Cochez la case « Construire avec des packages d'exécution » ;
5. Editez la zone d'édition "Runtime packages" : "Vcl50;Package1" et cliquez sur le bouton "OK" ;
6. Remarque : Ne supprimez pas Unit2 de l'application ;
7. Enregistrez et exécutez l'application.
L'application fonctionnera comme avant, mais la différence est visible dans la taille du fichier.
Project1.exe ne fait désormais que 14 Ko, contre 293 Ko auparavant. Si vous utilisez le navigateur de ressources pour afficher le contenu des fichiers EXE et BPL, vous verrez que le DFM et le code Form2 sont désormais enregistrés dans le package.
Delphi effectue la liaison statique des packages lors de la compilation. (C'est pourquoi vous ne pouvez pas supprimer Unit2 du projet EXE.)
Pensez à ce que vous pouvez en tirer : vous pouvez créer un module d'accès aux données dans le package, et lorsque vous modifiez les règles d'accès aux données (comme le passage des connexions BDE aux connexions ADO), modifier légèrement et republier le package. Vous pouvez également créer un formulaire dans un package affichant le message « Cette option n'est pas disponible dans la version actuelle », puis créer un formulaire entièrement fonctionnel dans un autre package portant le même nom. Nous disposons désormais du produit en versions "Pro" et "Enterprise" sans aucun effort.
Chargement et déchargement dynamiques des colis
Dans la plupart des cas, une DLL ou une BPL liée statiquement suffira. Mais que se passe-t-il si nous ne voulons pas publier BPL ? "La bibliothèque de liens dynamiques Package1.bpl est introuvable dans le répertoire spécifié" est le seul message que nous pouvons recevoir avant la fin de l'application. Ou, dans une application modulaire, pouvons-nous utiliser n’importe quel nombre de plugins ?
Nous devons nous connecter dynamiquement au BPL au moment de l'exécution.
Pour les DLL, il existe une méthode simple, qui consiste à utiliser la fonction LoadLibrary :
fonction LoadLibrary(lpLibFileName : Pchar) : HMODULE;stdcall;
Après avoir chargé la DLL, nous pouvons utiliser la fonction GetProcAddress pour appeler les fonctions et méthodes exportées de la DLL :
fonction GetProcAddress (hModule : HMODULE ; lpProcName : LPCSTR) : FARPROC ;
Enfin, nous utilisons FreeLibrary pour désinstaller la DLL :
fonction FreeLibrary(hLibModule: HMODULE): BOOL;stdcall;
Dans l'exemple suivant, nous chargeons dynamiquement la bibliothèque HtmlHelp de Microsoft :
function TForm1.ApplicationEvents1Help(Command: Word; Data: Integer; var CallHelp: Boolean):Boolean;
taper
TFNHtmlHelpA = fonction (hwndCaller : HWND ; pszFile : PansiChar ; uCommand : UINT ; dwData : Dword) : HWND ;
var
Module d'aide : module H ;
Aide HTML : TFNHtmlHelpA ;
commencer
Résultat := Faux ;
HelpModule := LoadLibrary('HHCTRL.OCX');
si HelpModule <> 0 alors
commencer
@HtmlHelp := GetProcAddress(HelpModule, 'HtmlHelpA');
si @HtmlHelp <> nul alors
Résultat := HtmlHelp(Application.Handle,Pchar(Application.HelpFile), Command,Data) <> 0;
Bibliothèque gratuite (Module d'aide);
fin;
CallHelp := False ;
fin;
Chargement dynamique de BPL
Nous pouvons utiliser la même méthode simple pour traiter le BPL, ou devrais-je dire fondamentalement la même manière simple.
Nous pouvons charger dynamiquement des packages à l'aide de la fonction LoadPackage :
fonction LoadPackage (const Nom : chaîne) : HMODULE ;
Utilisez ensuite la fonction GetClass pour créer un objet de type TPersistentClass :
function GetClass(const AclassName: string):TPersistentClass;
Une fois que tout est fait, utilisez UnLoadPackage(Module:HModule);
Apportons quelques petites modifications au code d'origine :
1. Sélectionnez « Project1.exe » dans le gestionnaire de projet ;
2. Faites un clic droit dessus et sélectionnez « Options » ;
3. Sélectionnez la page d'option « Packages » ;
4. Supprimez « Package1 » de la zone d'édition « Packages d'exécution » et cliquez sur le bouton OK ;
5. Dans la barre d'outils Delphi, cliquez sur le bouton « Supprimer le fichier du projet » ;
6. Sélectionnez « Unit2 | Form2 » et cliquez sur OK ;
7. Maintenant, dans le code source de "Unit1.pas", supprimez Unit2 de la clause use ;
8. Entrez le code temporel OnClick de Button1 ;
9. Ajoutez deux variables de type HModule et TPersistentClass :
var
Module de package : HModule ;
Classe A : TPersistentClass ;
10. Utilisez la fonction LoadPackage pour charger le package Pacakge1 :
PackageModule := LoadPackage('Package1.bpl');
11. Vérifiez si PackageModule est 0 ;
12. Utilisez la fonction GetClass pour créer un type persistant :
AClass := GetClass('TForm2');
13. Si ce type persistant n'est pas nul, on peut revenir au précédent
Créez et utilisez des objets de ce type de la même manière :
avec TComponentClass(AClass).Create(Application) comme le fait TcustomForm
commencer
AfficherModal ;
Gratuit;
fin;
14. Enfin, utilisez le processus UnloadPackage pour désinstaller le package :
DéchargerPackage(PackageModule);
15. Enregistrez le projet.
Voici la liste complète des gestionnaires d'événements OnClick :
procédure TForm1.Button1Click(Expéditeur : Tobject);
var
Module de package : HModule ;
Classe A : TPersistentClass ;
commencer
PackageModule := LoadPackage('Package1.bpl');
si PackageModule <> 0 alors
commencer
AClass := GetClass('TForm2');
si AClass <> nul alors
avec TComponentClass(AClass).Create(Application) comme le fait TcustomForm
commencer
AfficherModal ;
Gratuit;
fin;
DéchargerPackage(PackageModule);
fin;
fin;
Malheureusement, ce n'est pas tout.
Le problème est que la fonction GetClass ne peut rechercher que les types enregistrés. Les classes de formulaire et les classes de composants généralement référencées dans un formulaire sont automatiquement enregistrées lorsque le formulaire est chargé. Mais dans notre cas, le formulaire ne peut pas être chargé plus tôt. Alors, où enregistrons-nous le type ? La réponse est dans le sac. Chaque unité du colis est initialisée lorsque le colis est chargé et nettoyée lorsque le colis est déchargé.
Revenons maintenant à notre exemple :
1. Double-cliquez sur « Package1.bpl » dans le gestionnaire de projet ;
2. Cliquez sur le signe + à côté de « Unit2 » dans la section « Contient » ;
3. Double-cliquez sur « Unit2.pas » pour activer l'éditeur de code source de l'unité ;
4. Ajoutez la section d'initialisation à la fin du fichier ;
5. Utilisez la procédure RegisterClass pour enregistrer le type de formulaire :
RegisterClass(TForm2);
6. Ajoutez une section de finalisation ;
7. Utilisez la procédure UnRegisterClass pour désenregistrer le type de formulaire :
UnRegisterClass(TForm2);
8. Enfin, enregistrez et compilez le package.
Nous pouvons maintenant exécuter "Project1" en toute sécurité et cela fonctionnera comme avant, mais vous pouvez désormais charger les packages comme vous le souhaitez.
fin
N'oubliez pas que, que vous utilisiez des packages de manière statique ou dynamique, activez Options du projet | Packages | Construire avec des packages d'exécution.
Avant de désinstaller un package, n'oubliez pas de détruire tous les objets de classe du package et de désenregistrer toutes les classes enregistrées. Le processus suivant peut vous aider :
procédure DoUnloadPackage(Module : HModule);
var
je : entier ;
M : TMemoryBasicInformation ;
commencer
pour i := Application.ComponentCount - 1 jusqu'à 0 faire
commencer
VirtualQuery(GetClass(Application.Components[i].ClassName), M, Sizeof(M));
si (Module = 0) ou (HMODULE(M.AllocationBase) = Module) alors
Application.Components[i].Gratuit ;
fin;
DésinscrireModuleClasses(Module);
DéchargerPackage(Module);
fin;
Avant de charger le package, l'application doit connaître les noms de toutes les classes enregistrées. Une façon d'améliorer cette situation consiste à créer un mécanisme d'enregistrement qui indique à l'application les noms de toutes les classes enregistrées par le package.
Exemple
Packages multiples : les packages ne prennent pas en charge les références circulaires. Autrement dit, une unité ne peut pas faire référence à une unité qui fait déjà référence à cette unité (hehe). Cela rend difficile la définition de certaines valeurs du formulaire appelant par la méthode appelée.
La solution à ce problème consiste à créer des packages supplémentaires référencés à la fois par l'objet appelant et par les objets du package. Imaginez comment nous faisons d'Application le propriétaire de tous les formulaires ? La variable Application est créée dans Forms.pas et incluse dans le package VCL50.bpl. Vous avez peut-être remarqué que votre application doit non seulement compiler VCL50.pas, mais nécessite également VCL50 dans votre package.
Dans notre troisième exemple, nous concevons une application pour afficher les informations clients et, à la demande, les commandes clients (de manière dynamique).
Alors, par où pouvons-nous commencer ? comme toutes les applications de base de données
La procédure est la même, il faut se connecter. Nous créons un module de données principal contenant une connexion TDataBase. Ensuite nous encapsulons ce module de données dans un package (cst_main).
Maintenant, dans l'application, nous créons un formulaire client et référençons DataModuleMain (nous lions statiquement VCL50 et cst_main).
Ensuite, nous créons un nouveau package (cst_ordr) qui contient le formulaire de commande client et nécessite cst_main. Nous pouvons maintenant charger dynamiquement cst_ordr dans l'application. Étant donné que le module de données principal existe déjà avant le chargement du package dynamique, cst_ordr peut utiliser directement l'instance du module de données principal de l'application.
L'image ci-dessus est un schéma fonctionnel de cette application :
Packages remplaçables : un autre cas d’utilisation des packages consiste à créer des packages remplaçables. L'implémentation de cette fonctionnalité ne nécessite pas les capacités de chargement dynamique du package. Supposons que nous souhaitions publier une version d'essai du programme à durée limitée, comment y parvenir ?
Tout d'abord, nous créons un formulaire "Splash", généralement une image avec le mot "Trial" dessus, et l'affichons au démarrage de l'application. Ensuite, nous créons un formulaire « À propos » qui fournit des informations sur l'application. Enfin, nous créons une fonction qui teste si le logiciel est obsolète. Nous encapsulons ces deux formulaires et cette fonction dans un package et le publions avec la version d'essai du logiciel.
Pour la version payante, nous créons également un formulaire "Splash" et un formulaire "A propos" - avec les mêmes noms de classes que les deux formulaires précédents - et une fonction de test (qui ne fait rien) et les ajoutons encapsulés dans un package avec le même nom.
Quoi quoi ? Est-ce utile, demandez-vous ? Eh bien, nous pouvons publier une version d’essai du logiciel au public. Si un client achète l'application, nous devons uniquement lui envoyer le package sans essai. Cela simplifie grandement le processus de publication du logiciel, car une seule installation et une seule mise à niveau du package d'enregistrement sont nécessaires.
Le package ouvre une autre porte à la conception modulaire pour les communautés de développement Delphi et C++ Builder. Avec les packages, vous n'avez plus besoin de transmettre des handles de fenêtre, plus de fonctions de rappel, plus d'autres technologies DLL. Cela raccourcit également le cycle de développement de la programmation modulaire. Tout ce que nous avons à faire est de laisser les packages de Delphi travailler pour nous.