[.net2] hébergeur de plugins avec chargement et déchargement de plugins


Description

Cette source est composée de deux projets. Ils sont PluginGuest et PluginHost.

PluginGuest est le projet contenant la définition de la classe abstraite de plugin (PluginGuest.GenericPlugin) et de la fabrique (PluginGuest.PluginFactory). Cette fabrique est nécessaire pour récupérer les types des plugins déclarés dans n'importe quelle assembly. On peut alors les instancier avec la méthode CreateInstance(). La classe abstraite GenericPlugin défini 5 membres. Il y a trois propriétés. La première déjà implémentée, permet de récupérer le nom de l'assembly (GenericPlugin.Assembly) dans laquelle est déclaré le plugin. La seconde permet de récupérer le nom du plugin (GenericPlugin.Name) tel qu'il se le défini. La dernière permet de récupérer la catégorie de plugin (GenericPlugin.Category) dans laquelle se range le plugin. Les deux autres membres sont les fonctions appelées après le chargement (GenericPlugin.Load) du plugin et avant le déchargement (GenericPlugin.Unload) du plugin. La surcharge de la méthode ToString est faite pour afficher le nom de plugin.

PluginGuest contient aussi la définition des catégories de plugin (PluginGuest.PluginCategory) et des classes associées permettant d'avoir un dépôt de catégorie unique dans le domaine d'application principal. Une catégorie est ajoutée à l'aide de la méthode PluginCategory.AddCategory(). L'instance d'une catégorie est récupérée à l'aide de PluginCategorie.get_Category().

PluginHost est le projet contenant l'hébergeur de plugin en lui-même, l'interface des chargeurs de plugin, et deux chargeurs prédéfinis. L'interface des chargeurs de plugin (PluginHost.IPluginLoader) permet d'effectuer le chargement et le déchargement des assemblies des plugins. L'hébergeur de plugin (PluginHost.PluginManager<PluginType>) permet de gérer la synchronisation d'accès aux plugins et du chargement/déchargement de ceux-ci. La classe est statique uniquement. La généricité permet d'avoir de gérer plusieurs types de plugins clairement séparés, en plus de la séparation des catégories. Il y 4 méthodes pour les deux aspects d'utilisation : LockPlugins()/UnLockPlugins(), LoadPluginAssembly()/UnloadPlugins(). Le premier couple permet l'accès de façon sécurisée aux instances des plugins. Le second couple permet de charger et décharger les plugins. Le chargement et le déchargement sont mutuellement exclusifs. Ils ne peuvent non plus être exécuté lorsqu'il y a des plugins verrouillés. Le verrouillage de plugin ne peut plus être effecter dès qu'une demande de chargement ou déchargement est en attente de traitement.

L'utilisation des plugins se fait de la façon suivante : On démarre la boucle d'appels aux plugins avec la méthode PluginManager<PluginType>.LockPlugins() qui nous renvoie un tableau des instances de plugin (PluginType[]). LockPlugins() prend en paramètre la liste des catégories de plugins à verrouiller. PluginCategory.Categories peut être utilisé pour tout verrouiller. Il ne faut pas modifier le retour de LockPlugins. Une fois nos appels effectués, on utilise la méthode PluginManager<PluginType>.UnLockPlugins() en lui passant le tableau des plugins récupéré précédemment pour déverrouiller. Il ne faut plus utiliser le tableau de plugin. Il est impératif de suivre ce schéma afin de garantir le bon fonctionnement. L'oublie d'un déverrouillage bloque tout chargement et déchargement.

La méthode PluginManager<PluginType>.LoadPluginAssembly() prend le chemin absolu ou relatif au dossier de travail ou juste le nom du fichier s'il est dans les chemins de recherche et charge les plugins qui se trouvent dans l'assembly. Un booléen indiquant la réussite est renvoyé. Dans le cas d'un échec, l'exception peut être récupérée grâce à la propriété PluginManager<PluginType>.Errors (réinitialisable avec PluginManager<PluginType>.ResetErrors()). Le méthode PluginManager<PluginType>.UnloadPlugins() prend en paramètre le nom d'un des plugins de l'assembly à décharger et lance le déchargement de tous les plugins de l'assembly (Attention !). La valeur de retour possède les mêmes propriétés que la méthode explique précédemment.

Trois chargeurs de plugins sont disponibles : PluginHost.LocalLoader (par défaut), PluginHost.IPCLoader et PluginHost.TCPLoader. Ces trois chargeurs utilisent des domaines d'application séparés du domaine principal. Cette séparation permet le déchargement d'une assembly, sa mise à jour et son rechargement dans la nouvelle version. Si vous voulez écrire votre propre chargeur, consultez le code de ceux existants. Les deux derniers chargeurs utilisent des processus séparés pour communiquer. Le code des processus séparés sont dans les projets supplémentaires IPCPluginRunner et TCPPluginRunner. Les exécutables générés doivent obligatoirement se trouver dans le dossier de travail de l'application lorsque leur chargeur correspondant est utilisé.

Il y a quelques détails de fonctionnement à prendre en compte si vous utilisés mono. Il n'est pas possible sous mono de charger une nouvelle version d'une assembly même si elle était dans un domaine séparé du même processus. Le chargeur IPC ne fonctionne pas sous mono. Seul le chargeur TCP fonctionne avec mono. La détection étant celle du système d'exploitation, il ne faut pas utiliser mono sous windows pour utiliser cette bibliothèque.

Conclusion :


Attention ! Les deux projets dépendent maintenant de log4net disponible sur le site http://logging.apache.org/. Il est important de rajouter les références nécessaires.

Il est possible d'imaginer des plugins qui seront eux-mêmes extensibles. C'est possible, l'hébergeur n'étant pas partagé avec les chargeurs par défaut. Et même si ce n'était pas le cas, il suffirait que ce ne soit pas le même type de plugin spécifié lors de l'utilisation de l'hébergeur. Il faudra donc faire attention dans ce cas aux déchargements en cascade.

Comme indiqué dans l'autre partie, les catégories de plugins sont gérées. L'ensemble des catégories disponible est enregistré dans le domaine d'application principal.

Au niveau des références de projet à définir, c?est assez simple. L?application principale doit référencer les deux projets. Le projet PluginHost référence PluginGuest, ce qui est logique puisqu?il va utiliser ses types. Les différents plugins vont référencer uniquement le projet PluginGuest. Il est conseillé de faire un autre projet qui va définir les classes abstraites des plugins du projet, permettant une meilleure aisance dans la demande des plugins et leur utilisation. Ce projet sera également référencé par les différents plugins et l'application principale. Je conseil d'utiliser au maximum des objets dérivant de MarshalByRefObject pour les objets communiqués entre les plugins et l'application principale. Les collections ne répondant pas à ce critère, il vous faudra soit écrire un objet d?encapsulation pour la liste implémentant tout l?interface, soit écrire un objet qui va contenir une ou plusieurs données avec les accesseurs et méthodes appropriées. Dans les deux cas, il faut l'utilisation de MarshalByRefObject. Rien ne vous empêche d?utiliser d?autres méthodes de communication entre l'application principale et les plugins, autre que l?appel de méthode, si l?envie vous prend.

Dans les déclarations de vos objets qui circulent entre l'application principale et les plugins, il faut veiller à ce que les interfaces que connais l'application principale soient les premières dans la liste des interfaces de l?objet, dans le cas contraire, il y a un risque qu?un type spécifique à votre plugin soit chargé dans le domaine de l'application principale et que la possibilité de déchargement soit bloquée pour ce plugin.

Ajout : Me disant que beaucoup aimeraient pouvoir utiliser les plugins en interface graphique et se demandent si c'est possible, je répondrais tout simplement que oui. Le classe de base de tous les contrôles Windows Forms, System.Windows.Forms.Control, étends directement MarshalByRefObject. Il est donc possible de faire une classe de plugin qui hérite directement d'un contrôle Windows Form et d'implémenter au minimum mon interface en première interface, voir une extension de celle-ci. Il sera plus prudent de fournir une propriété 'Control' dans l'interface qui sera implémentée par le plugin qui se renvoie lui-même avec le bon type Windows Forms. Il faudra veiller bien sur à implémenter la méthode Unload de manière à ce qu'elle déconnecte le composant de son parent, et si le composant peut être dupliqué, que tous les dupplicatas soient déconnectés aussi de leurs parents. Sinon c'est « vive les exceptions à la pelle » lors du prochain événement qui les atteindra ^^.

Je n?ai pas fournis d?exemple pour le moment, ca viendra peut-être. Je me suis inspiré d?un article sur CodeProject pour écrire cette source. Le lien est dans les fichiers sources qui ont récupérés des bouts de code de cet article.

Et je crois que c?est tout.

Au niveau de ce que j?ai à faire, voici la liste :
- Encapsuler le tableau des plugins par un objet qui va le placer en lecture seule. (collection en lecture seule)
- Commenter encore plus le code ^^
- Autre suggestions ?

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.