Vos applications doivent tout le temps évoluer, nécessitent de nouvelles fonctionnalités :
la solution est de développer de façon modulaire afin que vos fonctionnalités se rajoutent
automatiquement sans avoir à recompiler votre application à chaque fois.
Cet article vous permettra de voir comment développer des applications de
façon modulaire. Vous verrez comment faire pour intégrer une nouvelle fonctionnalité
sans tout recompiler mais juste en mettant la DLL dans le répertoire de votre
application.
Pour cela nous utiliserons une interface pour donner un modèle à notre plugin.
Ensuite nous développerons un plugin qui s'intègrera automatiquement dans l'application.
Dans un premier temps nous verrons la notion d'interface et quelle est
son utilité.
Ensuite nous verrons comment réaliser un plugin à partir d'une interface.
Pour terminer, nous verrons comment les intégrer dans votre application.
2. Développer une application avec plugin
2.1. L'interface
Afin de réaliser vos plugins vous devez IMPERATIVEMENT définir un modèle de
plugin. Sur le plan de la programmation, un modèle peut être défini soit
par une classe abstraite, soit plus généralement par une interface.
En .NET une classe ne peut hériter que d'une SEULE autre classe mais de
plusieurs interfaces. Il est donc fortement conseillé d'utiliser des
interfaces. De cette manière, vos plugins pourront répondre à plusieurs modèles.
Voici un exemple d'interface :
Imports System
Imports System.Windows.Forms
Namespace PluginApp
public interface IPlugin
Function Traitement() AsStringFunction VisualComponent() As System.Windows.Forms.UserControl
La fonction Traitement de vos plugins devra répondre au modèle et donc
retourner une chaîne de caractères.
La fonction VisualComponent permettra de récupérer le composant visuel de
vos plugins afin de l'intégrer dans votre application.
L'interface est un projet à part entière : vous devez simplement développer
une DLL qui contient uniquement votre interface.
2.2. Le plugin
La réalisation d'un plugin demande quelques règles. En effet pour
charger un plugin vous devez connaître le nom de l'espace de nom et
de la classe de votre plugin. Nous allons donc utiliser la règle
suivante pour l'exemple. Le plugin aura comme espace de nom
PluginApp et le nom de la classe sera PluginClass.
Imports System
Imports System.ComponentModel
Imports System.Drawing
Imports System.Data
Imports System.Windows.Forms
Namespace PluginApp
' Classe du plugin respectant le modèle : hérite de IPlugin PublicClass PluginClass
Inherits System.Windows.Forms.UserControl
Implements IPlugin 'ToDo: Add Implements Clauses for implementation methods of these interface(s) Private components As System.ComponentModel.Container = NothingPrivate lbl As System.Windows.Forms.Label
' Constructeur : Initilialise la partie visuelle du composant PublicSubNew()
InitializeComponent()
EndSub 'New ' DestructeurProtectedOverridesSub Dispose(ByVal disposing AsBoolean)
If disposing ThenIfNot (components IsNothing) Then
components.Dispose()
EndIfEndIfMyBase.Dispose(disposing)
EndSub 'Dispose ' Positionnement des composants visuels sur le contrôle représentant le plugin PrivateSub InitializeComponent()
Me.lbl = New System.Windows.Forms.Label()
Me.SuspendLayout()
Me.lbl.Location = New System.Drawing.Point(16, 8)
Me.lbl.Name = "lbl"Me.lbl.Size = New System.Drawing.Size(64, 24)
Me.lbl.TabIndex = 0
Me.lbl.Text = "Plugin 1"Me.Controls.Add(lbl)
Me.Name = "UserControlPlugin1"Me.Size = New System.Drawing.Size(184, 96)
Me.ResumeLayout(False)
EndSub 'InitializeComponent ' Fonction de traitement ' Dans cet exemple elle retourne juste un descriptif PublicFunction Traitement() AsStringReturn"Le traitement du plugin 1"EndFunction 'Traitement ' Permet de récupérer le contrôle utilisateur qui est la classe d'où ' le returnthis. PublicFunction VisualComponent() As System.Windows.Forms.UserControl
ReturnMeEndFunction 'VisualComponentEndClass 'PluginClassEnd Namespace 'PluginApp
Comme vous pouvez le constater, la classe hérite de System.Windows.Forms.UserControl
ce qui signifie que le plugin est un composant visuel. Elle hérite
aussi de l'interface IPlugin que nous avons vu au paragraphe
précédent.
Le plugin est extrêmement simple : il retourne "Le traitement du plugin 1"
lors de l'appel de la fonction Traitement, et le composant visuel
lors de l'appel de la fonction VisualComponent. Ce composant contient
un label avec comme texte "Plugin 1".
Si vous souhaitez développer d'autres plugins vous devez OBLIGATOIREMENT
faire hériter votre composant de l'interface IPlugin. De plus votre
composant doit implémenter les fonctions Traitement et VisualComponent.
Rien ne vous empêche de développer d'autres fonctions privées, liées
au fonctionnement de votre composant.
C'est ainsi que vous pourrez développer des fonctionnalités
supplémentaires à votre application. Votre plugin est entièrement
autonome hormis le fait qu'il doit intégrer une application.
Pour cela votre plugin doit être développé sous forme de DLL. Pour
la compilation vous devez rajouter la DLL de l'interface en tant
que référence.
2.3. Développement de l'application principale
La dernière phase est celle du développement de l'application
principale. Pour notre exemple nous allons intégrer le plugin de façon
très simple. Un onglet qui contiendra votre plugin visuel,
sera créé dynamiquement dans le TabControl du formulaire. De plus,
la fonction Traitement sera appelée au chargement du formulaire.
Imports System
Imports System.Drawing
Imports System.Collections
Imports System.ComponentModel
Imports System.Windows.Forms
Imports System.Data
Namespace PluginApp
' Fenêtre principale PublicClass frmMain
Inherits System.Windows.Forms.Form
Private components As System.ComponentModel.Container = NothingPrivate tabControl As System.Windows.Forms.TabControl
PublicSubNew()
InitializeComponent()
' Création d'une instance du plugin dans le domaine principal ' La fonction CreateInstanceFromAndUnWrap retourne un objet de type object Dim obj AsObject
obj = AppDomain.CurrentDomain.CreateInstanceFromAndUnwrap("Plugin1.dll", "PluginApp.PluginClass")
' Execution de la fonction de traitement
MessageBox.Show(("Chargement de " + CType(obj, IPlugin).Traitement()))
' Rajout d'un tabPage avec le composant visuel du plugin ' Rajout d'un onglet ayant comme titre "Ma page" Dim tp AsNew TabPage("Ma page")
' Intégration du contrôle utilisateur dans le tabPage ' obj étant de type object, le cast est nécessaire pour pouvoir ' appeler la fonction VisualComponent de l'objet
tp.Controls.Add(CType(obj, IPlugin).VisualComponent())
' Ajout du tabPage au tabControl général
tabControl.TabPages.Add(tp)
EndSub 'New ProtectedOverridesSub Dispose(disposing AsBoolean)
If disposing ThenIfNot (components IsNothing) Then
components.Dispose()
EndIfEndIfMyBase.Dispose(disposing)
EndSub 'Dispose ' Positionnement des composants visuels sur le formulairen PrivateSub InitializeComponent()
Me.tabControl = New System.Windows.Forms.TabControl()
Me.SuspendLayout()
Me.tabControl.Location = New System.Drawing.Point(16, 16)
Me.tabControl.Name = "tabControl"Me.tabControl.SelectedIndex = 0
Me.tabControl.Size = New System.Drawing.Size(488, 232)
Me.tabControl.TabIndex = 0
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(520, 269)
Me.Controls.Add(tabControl)
Me.Name = "frmMain"Me.Text = "Application Principale"Me.ResumeLayout(False)
EndSub 'InitializeComponent ' Point d'entrée du programme Shared <STAThread()>
Sub Main()
Application.Run(New frmMain())
EndSub 'MainEndClass 'frmMain End Namespace 'PluginApp
Dans le constructeur une phase est importante : celle de la création
d'une instance du plugin.
Dim obj AsObject
obj = AppDomain.CurrentDomain.CreateInstanceFromAndUnwrap("Plugin1.dll", "PluginApp.PluginClass")
Voici pourquoi dans les chapitres au dessus, je disais que vous deviez
connaître le namespace et le nom de la classe de votre plugin. La fonction
CreateInstanceFromAndUnWrap permet de créer une instance de la classe
donnée en second paramètre de la DLL spécifiée en premier paramètre.
Dans le cas d'une application de production, le plus simple est de créer
un répertoire "plugins" et d'y copier tous vos plugins. Ainsi il vous reste
à développer une fonction qui liste les dll du répertoire et qui les charge.
Si vous regardez dans la documentation MSDN vous constaterez que cette fonction
retourne un objet de type object. Il faut donc le "caster" pour pouvoir
utiliser les fonctions du plugin.
2.4. Appel de méthodes statiques dans les plugins
Ce qui est présenté au dessus permet de charger un assembly et de créer des
instances dans le domaine courant. Le problème est pour appeler les méthodes
statiques de votre classe. En effet il est impossible d'accéder au membres statiques,
même publics, à partir d'une instance ; vous faites toujours MaClasse.MaMethode(); :
pas de new.
La technique pour accéder à vos membres statiques et d'utiliser la réflexion.
Je ne ferai pas de discours sur ce sujet qui est très vaste mais vous permet
d'accéder de "naviguer" dans vos assemblies, classes à partir de code. Dans notre
cas nous allons utiliser une très fine partie de la réflexion.
Pour utiliser une méthode statique il faut passer par 3 étapes :
Charger l'assembly
Récupérer la classe
Appeler la méthode
Pour montrer l'ensemble des cas possibles, nous allons étudier 3 cas :
Un simple appel
Un appel avec argument
Un appel avec un retour
2.4.1. Appel de méthodes statiques sans paramètre
Le code suivante constitue un exemple d'appel dynamique à une méthode
statique. La "clé" se trouve dans la méthode InvokeMember.
Le premier paramètre est une chaîne de caractères donnant le non de
la méthode appelée.
Le deuxième paramètre est une succession de flag indiquant les filtres
de recherche du membre donné en premier paramètre. Dans le cas suivant, nous
recherchons une méthode publique et statique.
Le troisième paramètre, de type Bonder, permet de donner votre propre sélectionneur de membres.
Cet objet indiquera à la méthode InvokeMember comment récupérer le bon membre.
Par défaut vous pouvez laisser null. Il est renseigné dans les cas comme les surcharges.
Le quatrième paramètre est l'instance sur laquelle vous voulez effectuer l'appel. Dans notre cas,
comme nous souhaitons faire appel à une méthode statique, nous n'aurons pas d'instance donc null.
En effet, vous pouvez aussi utiliser cette technique pour faire appel dynamiquement à des instances
de vos plugins.
Le dernier paramètre qui est un tableau, permet de passer la liste des paramètres à la méthode appelée.
Imports System
Imports System.Reflexion;
Namespace PluginApp
' Classe appelantePublicClass CTest
' Point d'entrée du programme Shared <STAThread()>
Sub Main()
' Chargement de l'assemblyDim a AsAssembly
a = Assembly.LoadFrom("ClassLibrary1.dll");
' Récupération de l'ensemble des classes contenu dans l'assemblyDim types As Type()
types = a.GetTypes()
' Appel de la méthode Write de la première classe de l'assembly
types[0].InvokeMember("Write",BindingFlags.InvokeMethod |
BindingFlags.Public |
BindingFlags.Static,null,null,null);
EndSubEndClassEnd Namespace
Le code suivant est compilée à part pour obtenir une librarie : vbc /t:library /out:ClassLibrary1.dll Class1.cs
Imports System
Namespace ClassLibrary1
' Classe appelantePublicClass Class1
' Méthode qui écrit kikoo dans la console StaticSub Write()
Console.Out.WriteLine("kikoo")
EndSubEndClassEnd Namespace
Si vous exécutez le code, vous verrez kikoo apparaître dans la console.
2.4.2. Appel de méthodes statiques avec paramètre
Assembly de départ :
Imports System
Imports System.Reflexion
Namespace PluginApp
' Classe appelantePublicClass CTest
' Point d'entrée du programme Shared <STAThread()>
Sub Main()
' Chargement de l'assemblyDim a AsAssembly
a = Assembly.LoadFrom("ClassLibrary1.dll")
' Récupération de l'ensemble des classes contenu dans l'assemblyDim types As Type()
types = a.GetTypes()
' Préparation des arguments à passer à la méthodeDim arg AsNewString(1)
arg(0) = "kikoo2";
' Appel de la méthode Write de la première classe de l'assembly
types[0].InvokeMember("Write",BindingFlags.InvokeMethod |
BindingFlags.Public |
BindingFlags.Static,null,null,arg)
EndSubEndClassEnd Namespace
Le code suivant est compilée à part pour obtenir une librarie : csc /t:library /out:ClassLibrary1.dll Class1.cs
Imports System
Namespace ClassLibrary1
' Classe appelantePublicClass Class1
' Méthode qui écrit kikoo dans la console PublicSharedSub Write(message AsString)
Console.Out.WriteLine(message)
EndSubEndClassEnd Namespace
Si vous exécutez le code, vous verrez kikoo2 apparaître dans la console.
2.4.3. Appel de méthodes statiques avec paramètre et valeur de retour
Dans le code suivant j'ai fait au plus simple car je connais le type de retour.
Dans le cas où vous ne le connaissait pas, utilisez la classe MethodInfo pour obtenir
toutes les informations sur votre méthodes. Après nous rentrons dans le vif
du sujet sur la réflexion, ce qui n'est pas l'objet de cet article.
Imports System
Imports System.Reflexion
Namespace PluginApp
' Classe appelantePublicClass CTest
' Point d'entrée du programme Shared <STAThread()>
Sub Main()
' Chargement de l'assemblyDim a AsAssembly
a = Assembly.LoadFrom("ClassLibrary1.dll")
' Récupération de l'ensemble des classes contenu dans l'assemblyDim types As Type()
types = a.GetTypes()
Dim arg AsNew object(2);
arg(0) = 2;
arg(1) = 5;
Dim i AsInteger
i = CInt(types[0].InvokeMember("Calc",BindingFlags.InvokeMethod |
BindingFlags.Public |
BindingFlags.Static,null,null,arg))
Console.WriteLine("2 + 5 = " + i)
EndSubEndClassEnd Namespace
Le code suivant est compilée à part pour obtenir une librarie : csc /t:library /out:ClassLibrary1.dll Class1.cs
Imports System
Namespace ClassLibrary1
' Classe appelantePublicClass Class1
' Méthode qui additionne deux entiers PublicShared function Calc(int i, int j) AsInteger
Calc = i + j
End function
EndClassEnd Namespace
Si vous exécutez le code, vous verrez 2 + 5 = 7 apparaître dans la console.
3. Conclusion
Grâce à un développement de ce type vous pouvez rajouter des
fonctionnalités facilement à votre application. Le tout est de le
prévoir au début du développement. Après vous pouvez le gérer de la façon
qui vous plait le plus : par des onglets comme dans l'exemple,
par des menus ou autres moyens.
Ce document peut être diffusé, amélioré, reformaté à souhait mais DOIT
IMPERATIVEMENT contenir la source du document (http://www.developpez.com).