Ca faisait longtemps que je n'avais pas écris d'article technique sur le .Net. Je manque un peu d'idées car tous les sujets ont déjà été traités. Néanmoins, à la demande de certains de mes internautes sur le forum français de Veler Software, je publie cet article qui va vous expliquer le fonctionnement d'une application modulable en .Net.
Définition
Il me serait inutile de définir ce qu'est le .Net Framework, j'imagine que toutes les personnes s'intéressant à cet article savent ce que c'est. Alors plutôt, qu'est-ce qu'une application modulable? On peut traduire cette phrase par "un logiciel utilisant des plugins". Un "plugin" est une bibliothèque (un fichier .dll) pouvant être aussi bien ajoutée que supprimée d'un logiciel, sans mettre en cause son dysfonctionnement (il est courant qu'un logiciel crash si on supprime une dll au hasard). Ce plugin a pour but principal d'ajouter de nouvelles fonctionnalités à ce fameux logiciel. Une application modulable est donc un logiciel auquel on peut rajouter des fonctionnalités en ajoutant une simple dll, sans avoir pour autant besoin de signifier au logiciel que cette dll existe. En effet, le logiciel doit être capable de trouver cette dll, de la configurer (si nécessaire) et d'en exploiter toute ses capacités automatiquement, sans intervention humaine (j'entends par là, réinstallation du logiciel, modification de l'exécutable pour accepter la dll...)
Exemple d'applications modulable
Nombreux sont les logiciels pouvant recevoir des "modules", "plugins" :
- SoftwareZator (héhéhé)
- Firefox (et ses modules complémentaires)
- Google Chrome
- Internet Explorer
- Les logiciels Adobe (je parle de Photoshop et Dreamweaver surtout)
- Office
- Visual Studio
- Windows Live Messenger (et le fameux Messenger Plus)
- Et j'en passe des dizaines très connus
Architecture
Évidement, pour chacun des logiciels que j'ai cité ci-dessus, l'architecture du système de modulation est différent, par exemple, les plugins pour Visual Studio nécessites une installation de celui-ci "par" Visual Studio, là ou SoftwareZator n'en a pas besoin.
Mais au final, le principe de base de cette architecture est le même :
Explications : on a une bibliothèque qui est aussi bien connu par notre logiciel que par nos plugin. Par conséquent, les plugins sont capables de communiquer avec cette bibliothèque, notre logiciel aussi. En revanche, les plugins sont incapables de communiquer directement avec notre logiciel, ils doivent le faire par l'intermédiaire de cette fameuse bibliothèque.
Pourquoi? Et bien imaginez que notre logiciel est le "Père", la bibliothèque est la "Gouvernante" et les plugins sont les "Enfants". Le père donne des ordres aux enfants par l'intermédiaire de la gouvernante, et la gouvernante rapporte le résultat des enfants au père. C'est une manière un peu "vieille monarchie" de présenter la chose, mais je trouve que ça illustre bien.
Mise en situation et exemple
On a :
- Une dll "PluginEngine" qui jouera le rôle de notre bibliothèque intermédiaire
- Une Class "MyPluginType" déclarée dans cette bibliothèque, représentant la déclaration d'un plugin pour notre logiciel.
- Notre logiciel "UsingAssemblyDynamically" ayant dans ses références "PluginEngine"
- Une dll "MyFirstPlugin", ayant dans ses références "PluginEngine" et une Class "Plug1" héritée de "MyPluginType", représentant un plugin.
Voici quelques informations sur notre dll "MyFirstPlugin" :
On constate que notre dll possède en référence "PluginEngine" et possède un type "Plug1" hérité de "MyPluginType". Par conséquent, Plug1 sera considéré comme étant un plugin par notre logiciel. Vous pourrez retrouver les sources de cette dll à la fin de cet article.
Maintenant intéressons-nous au chargement et à l'initialisation du plugin : la première étape consiste à charger dynamiquement (durant l'exécution) notre dll en mémoire, pour cela on va utiliser la méthode "LoadFile" du type "Assembly" :
System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFile("MyFirstPlugin.dll");
Une fois la dll chargée, il faut lister le contenu de celle-ci, déterminer si le type analysé est bien un plugin pour notre logiciel, et créer une instance de celui-ci :
// Pour chaque type de l'assembly
foreach (Type type in assembly.GetTypes())
{
// Si le type est hérité de MyPluginType et a l'attribut public, alors
if (type.IsSubclassOf(typeof(PluginEngine.MyPluginType)) && type.IsPublic)
{
// On créer une nouvelle instance de ce type
PluginEngine.MyPluginType plugin = (PluginEngine.MyPluginType)assembly.CreateInstance(type.FullName);
}
}
On dit que l'instance est créée dynamiquement car elle n'est pas prévue proprement par le compilateur. Le code ci-dessus permettant de créer l'instance revient à faire la chose suivante :
PluginEngine.MyPluginType plugin = new MyFirstPlugin.Plug1()
Mais étant donné que cette dll est inconnue de notre projet au moment de la compilation, on ne peut pas écrire la ligne ci-dessus car il ne trouvera pas le type Plug1 (ni la dll qui va avec).
Conclusion
Pour créer une application modulable il faut :
- Charger les dll dynamiquement.
- Lister les types de ces dll et chercher les informations qui nous intéresses dedans.
- Créer une instance des types trouvés.
La méthode proposée ci-dessus n'est pas forcément la meilleure. On peut également utiliser le type "AppDomain" du .Net Framework si on voulait que notre application soit capable de "décharger" un plugin durant l'exécution par exemple, ou si l'on veut charger nos dll dans un registre différent de notre logiciel dans la mémoire RAM.
Voici les codes sources du projet en C# (pour les non adeptes, je ne pense pas que vous aurez de grandes difficultés à les traduire en VB.Net) : Télécharger les sources du projet