I. Définition du problème▲
Je souhaite donner la possibilité à mon programme de charger une DLL avec sa classe de traitement à la demande.
Le but étant que mon programme ne soit pas tributaire de cette classe et qu'il ne soit pas alourdi par du code supplémentaire.
Mais pour clarifier tout ça, prenons un exemple.
Imaginons que je travaille sur un programme avec un module d'intégration de données tarifaires de fournisseurs divers dans ma base de données articles.
Chaque fournisseur mettant à disposition un fichier texte avec ses propres particularités.
Pour traiter ces données dans mon programme je vais construire un modèle de traitement, idéalement une classe pour réaliser l'intégration.
Mais voilà les fichiers à traiter sont très différents, des prétraitements sont à réaliser dessus, pour les remettre à ma norme d'intégration.
Dans un cadre classique, j'écrirais une classe de base pour modéliser les traitements à réaliser sur ces fichiers, puis au fil des différentes intégrations je créerai une autre classe héritée de la première pour tenir compte des particularités du fichier à intégrer.
Au bout du compte mon traitement s'appuiera sur une classe de prétraitement de fichier qui sera dérivée autant de fois que je dispose de type de fichiers fournisseurs à traiter.
Tout cela est satisfaisant dans un contexte simple, mais qu'arrivera-t-il si mon programme et son module d'intégration sont destinés à être utilisés pour plusieurs clients avec chacun leurs fournisseurs spécifiques ?
Mon code va enfler et se retrouver avec du code inutile d'un client à l'autre.
D'où l'idée : si je pouvais externaliser mon traitement dans une classe qui serait chargée dynamiquement, mon programme ne contiendrait que l'essentiel, libre à moi de distribuer mes DLL de prétraitements suivants mes clients.
Il me suffira ensuite de fournir une nouvelle DLL de traitement pour un nouveau fournisseur sans toucher à mon programme principal.
Facile ?
Pas tout à fait, le fait de définir l'utilisation de ma classe de traitement dans mon programme m'oblige à en définir le contenu et donc à lier mon programme à une DLL.
Sauf que je veux que mon programme puisse fonctionner sans DLL préalablement chargée, et surtout je veux continuer à travailler en C++ avec une classe.
Je précise ce dernier point, car rien de plus facile que de mettre un traitement dans une DLL et de le rendre accessible par une fonction « extern » en C dont on établira le lien dynamique avec l'api32 GetProcAddress.
I-A. La classe de traitement▲
Je vais commencer par définir ma classe de base de traitement sous forme d'une classe interface, en fait une classe abstraite qui ne pourra donc avoir d'instances directes.
Rappel : une classe est abstraite dès qu'elle possède une fonction virtuelle pure ou qu'elle hérite d'une fonction virtuelle pure sans la redéfinir.
Le fait que ma classe soit abstraite résout mon problème d'édition des liens.
Voyons maintenant comment procéder.
II. Génération du projet de tests▲
Pour les besoins de l'exemple je vais créer un projet MFC boîte de dialogue nommé ImportTarif.
Vous pouvez faire de même avec Visual Studio 2005 en suivant les étapes ci-dessous :
Cliquez ensuite sur le bouton « Terminer », notre projet de tests est généré.
III. Définition de la classe abstraite de traitement▲
Passons maintenant à la définition de notre classe abstraite.
Utilisez l'assistant pour créer une nouvelle classe :
Choisissez le modèle classe MFC, et cliquez sur le bouton ajoutez.
Choisissez le modèle classe MFC, et cliquez sur le bouton ajoutez.
J'ai nommé ma classe de base CPlugImportTarif elle hérite de la classe de base MFC Cobject.
Dans le cas présent l'assistant me génère plus d'éléments que j'en ai besoin, seul le .h m'intéresse, on supprimera du projet le fichier source généré.
Le .h généré donne ceci :
Je vais modifier la classe pour qu'elle soit exportable dans une DLL d'extensions et surtout définir mon interface de traitement.
Ma classe est très simple volontairement pour illustrer la méthode.
À ce stade la compilation et l'édition des liens ne causent pas de problème.
Je peux aussi déclarer sans problème un pointeur sur ma classe abstraite dans classe boite de dialogue sans plus de problème.
Passons maintenant à la réalisation de la DLL d'extensions.
IV. Création de la DLL d'extensions▲
À partir de l'explorateur de solution au sommet du projet, faites un clic droit : ajouter un nouveau projet :
Je crée mon premier plugin pour le fournisseur imaginaire Dupont :
Dans les paramètres du projet, je prends bien garde de sélectionner le modèle DLL d'extensions comme ci-dessous :
Je vais maintenant définir ma classe de traitement qui hérite bien sûr de ma classe abstraite :
V. Définition de la classe plug-in▲
Ma classe de traitement se nomme CPlugImpDupont, elle hérite bien de ma classe base abstraite CPlugImportTarif définie dans mon projet principal.
Maintenant que mes classes sont définies, la question que vous devez certainement vous poser est : comment faire pour créer une instance de cette classe et surtout l'affecter au pointeur sur ma classe abstraite dans mon projet de tests ?
En fait c'est la que réside toute l'astuce du procédé, je vais utiliser la capacité des MFC à créer une instance à partir d'une signature de classe.
Cette propriété est assumée par la présence des macros DECLARE_SERIAL et IMPLEMENT_SERIAL dans ma classe.
Enfin je vais rajouter dans le source principal de ma DLL d'extension la fonction PlugInit définie ci-dessous :
C'est une simple fonction C ,qui me renverra la signature de ma classe de traitement.
Ceci fait, voyons maintenant comment utiliser notre plugin.
VI. Gestion du Plug-in▲
Pour cela je vais écrire une petite classe template de gestion de plugin, la classe CPlug qui prend comme paramètre la classe de base abstraite de votre plugin.
VI-A. Fonctionnement de la classe CPlug▲
La DLL de plug-in est lue avec l'api32 LoadLibrary.
Pour retourner une instance de notre classe plug-in on associe un pointeur de fonction à notre fonction d'initialisation PlugInit qui sera présente dans chaque plugin.
On appelle PlugInit pour récupérer la signature de la classe à instancier.
Enfin on crée l'instance grâce à la méthode CreateObject() de la classe CRuntimeClass.
Il ne reste plus qu'à utiliser notre objet.
VII. Utilisation du Plug-in▲
Dans ma classe CDialog de traitement, j'ai rajouté deux fonctions :
- Une pour permettre à l'utilisateur de sélectionner la DLL de plug-in.
- Et une autre pour tester la classe plug-in
L'utilisation est très simple, après sélection de la DLL par l'utilisateur il ne me reste plus qu'à lire le plug-in, récupérer une instance de ma classe et d'appeler les différentes fonctions.
VIII. Conclusion▲
La mise en place et l'utilisation de plug-in de classe en utilisant les DLL d'extensions sont relativement faciles à mettre en œuvre, il ne vous reste plus qu'à trouver votre terrain d'application.
Le programme de tests avec deux plug-ins : ImportTarif