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

Soyez le premier à donner votre avis sur cette source.

Vue 7 836 fois - Téléchargée 417 fois


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

Ajouter un commentaire Commentaires
Messages postés
64
Date d'inscription
mercredi 24 juillet 2002
Statut
Membre
Dernière intervention
26 novembre 2009

Bonjour,
Pour ceux qui suivent la source, une nouvelle version plus puissante, plus clair, et plus commentée a été publie il y a quelques jours. Suite à une étrangeté, la mise à jour est marquée deux fois, mais c'est rien, c'est là même. Tout le texte de la source a été mis à jour aussi, il faut donc relire.

MyGoddess
Messages postés
180
Date d'inscription
jeudi 21 août 2003
Statut
Membre
Dernière intervention
26 novembre 2007
2
Félicitations, très bonne source.

Dommage que ce n'est pas commenté. Autrement, je pense que le plus important est dit dans la description :d.

Bonne Programmation.
Messages postés
64
Date d'inscription
mercredi 24 juillet 2002
Statut
Membre
Dernière intervention
26 novembre 2009

Tu n'a pas compris. Essaie avec ta version de charger une autre version d'une assembly, normalement, ca plantera. Car il verras que tu as déjà chargé l'assembly ou parce qu'il te diras qu'il peut pas la charger. Avec la solution que je propose, tu peux avoir de chargé en même temps, plusieurs versions d'un même assembly. De plus ce n'est pas juste les objets ou les informations de type que je décharge, c'est toutes les informations de type. Si tu garde le pointeur vers un plugin, puis que tu décharge le plugin et donc toute l'assembly et le domaine d'application qui va avec, et que après tu essaie d'appeler un méthode sur l'objet, il te dirat tout simplement : "Impossible d'effectuer l'appel, le composant distant n'est plus disponible." Enfin un truc du style.
Après je part d'un interface et pas d'une classe d'une part parce que c'est moins contraignant à écrire et le concepteur gère le fait de te demander d'implémenter directement au niveau de l'éditeur, pas besoin de taper override, et d'autre part pour permettre d'hériter d'une classe dérivant de MarshalByRefObject et pas obligatoirement de MarshalByRefObject. Puisque ma source peu être utilisée tel qu'elle pour définir n'importe quel type de plugin personnalisé, puisque ca ne contient que les informations nécessaire à l'hébergeur, c'est à la personne qui va l'utiliser d'implémenter son interface de plugin en implémentant celui de l'hébergeur.
Il est vrai que je n'ai pas expliquer le principe sur lequel ca se basait et sur quel niveau exactement se fesait le déchargement, qui est je le reprécise au niveau de code et des meta donnees en eux-mêmes et pas seulement des données.
J'espère avoir réussi a te faire comprendre la légère différence de fonctionnalité.
Messages postés
473
Date d'inscription
mercredi 7 août 2002
Statut
Membre
Dernière intervention
10 juin 2015

Ma source permet de précharger dans l'application cliente des DLL et les classes qu'elle contiennent en fonction d'une interface ou d'une classe de base puis d'instancier les objets à la demande. Mais je peux aussi enlever des classes de l'instancieur sans problème (c'est une collection). Si j'ai bien compris ce que fait ta source, c'est qu'elle précharge les classes pour pouvoir les utiliser dans un programme externe, non ? Sinon, c'est mal expliqué.
Mon principe surtout d'utiliser une classe paramétrable qui permet d'utiliser n'importe quelle classe ou interface de référence. Le principe peut certainement être réappliqué à ton cas.
Messages postés
64
Date d'inscription
mercredi 24 juillet 2002
Statut
Membre
Dernière intervention
26 novembre 2009

Sympa de faire la pub pour ta source Warny...
Enfin, là on a deux besoins différents (je fesais du même style que ce que tu as fait avant, mais avec des interfaces), ta source serait plutôt utile dans le cadre d'une application client qui peux être fermée/relancée si on veux virer des plugins. La mienne serait plutôt pour une application serveur ou une application client qui est assez lourde à charger. Ce qui signifie que l'on peux mettre un peu plus de contraintes à nos objets.
Afficher les 6 commentaires

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.