IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Développer une application Windows en .NET de façon modulaire

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. ♪

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

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.

II. Développer une application avec plugin

II-A. L'interface

Afin de réaliser vos plugins vous devez IMPÉRATIVEMENT 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 :

 
Sélectionnez
Imports System
Imports System.Windows.Forms

Namespace PluginApp

  public interface IPlugin
  
    Function Traitement() As String
    Function 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.

II-B. 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.

 
Sélectionnez
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 
   Public Class 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 = Nothing
      Private lbl As System.Windows.Forms.Label

        
      ' Constructeur : Initilialise la partie visuelle du composant 
      Public Sub New()
       
         InitializeComponent() 
      
      End Sub 'New 


      ' Destructeur
      Protected Overrides Sub Dispose(ByVal disposing As Boolean) 
      
         If disposing Then
              
            If Not (components Is Nothing) Then 
                
               components.Dispose() 
               
            End If 
            
         End If 
         MyBase.Dispose(disposing) 
      
      End Sub 'Dispose 


      ' Positionnement des composants visuels sur le contrôle représentant le plugin 
      Private Sub 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)
      
      End Sub 'InitializeComponent 


      ' Fonction de traitement 
      ' Dans cet exemple elle retourne juste un descriptif 
      Public Function Traitement() As String 
      
         Return "Le traitement du plugin 1" 
      
      End Function 'Traitement 


      ' Permet de récupérer le contrôle utilisateur qui est la classe d'où 
      ' le returnthis. 
      Public Function VisualComponent() As System.Windows.Forms.UserControl
      
         Return Me 
      
      End Function 'VisualComponent
 
   End Class   'PluginClass

End 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 vue 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.

II-C. 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.

 
Sélectionnez
Imports System 
Imports System.Drawing 
Imports System.Collections 
Imports System.ComponentModel 
Imports System.Windows.Forms 
Imports System.Data 


Namespace PluginApp 

   ' Fenêtre principale 
   Public Class frmMain
   
      Inherits System.Windows.Forms.Form
      Private components As System.ComponentModel.Container = Nothing 
      Private tabControl As System.Windows.Forms.TabControl 
      
      
      Public Sub New() 
      
         InitializeComponent() 
          
         ' Création d'une instance du plugin dans le domaine principal 
         ' La fonction CreateInstanceFromAndUnWrap retourne un objet de type object 
         Dim obj As Object 
         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 As New 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)
       
      End Sub 'New      

      Protected Overrides Sub Dispose(disposing As Boolean)
       
         If disposing Then
          
            If Not (components Is Nothing) Then 
            
               components.Dispose() 
            
            End If
          
         End If 
         MyBase.Dispose(disposing) 
      
      End Sub 'Dispose 
        
      
      ' Positionnement des composants visuels sur le formulaire 
      Private Sub 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) 
      
      End Sub 'InitializeComponent 
      
      
      ' Point d'entrée du programme 
      Shared <STAThread()>
      Sub Main()
       
         Application.Run(New frmMain()) 
      
      End Sub 'Main
    
   End Class 'frmMain 

End Namespace 'PluginApp

Dans le constructeur une phase est importante : celle de la création d'une instance du plugin.

 
Sélectionnez
            Dim obj As Object 
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.

II-D. 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 trois étapes :

  • charger l'assembly ;
  • récupérer la classe ;
  • appeler la méthode.


Pour montrer l'ensemble des cas possibles, nous allons étudier trois cas :

  • un simple appel ;
  • un appel avec argument ;
  • un appel avec un retour.

II-D-1. Appel de méthodes statiques sans paramètre

Le code suivant 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.

 
Sélectionnez
Imports System
Imports System.Reflexion;

Namespace PluginApp
    ' Classe appelante
    Public Class CTest
        ' Point d'entrée du programme 
        Shared <STAThread()>
        Sub Main() 
            ' Chargement de l'assembly
            Dim a As Assembly
            a = Assembly.LoadFrom("ClassLibrary1.dll");

            ' Récupération de l'ensemble des classes contenu dans l'assembly
            Dim 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);
        End Sub
    End Class
End Namespace

Le code suivant est compilé à part pour obtenir une librairie : vbc /t:library /out:ClassLibrary1.dll Class1.cs

 
Sélectionnez
Imports System

Namespace ClassLibrary1
    ' Classe appelante
    Public Class Class1
        ' Méthode qui écrit kikoo dans la console 
        Static Sub Write() 
            Console.Out.WriteLine("kikoo")
        End Sub
    End Class
End Namespace

Si vous exécutez le code, vous verrez kikoo apparaître dans la console.

II-D-2. Appel de méthodes statiques avec paramètre

Assembly de départ :

 
Sélectionnez
Imports System
Imports System.Reflexion

Namespace PluginApp
    ' Classe appelante
    Public Class CTest
        ' Point d'entrée du programme 
        Shared <STAThread()>
        Sub Main() 
            ' Chargement de l'assembly
            Dim a As Assembly
            a = Assembly.LoadFrom("ClassLibrary1.dll")

            ' Récupération de l'ensemble des classes contenu dans l'assembly
            Dim types As Type()
            types = a.GetTypes()

            ' Préparation des arguments à passer à la méthode
            Dim arg As New String(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)
        End Sub
    End Class
End Namespace

Le code suivant est compilé à part pour obtenir une librairie : csc /t:library /out:ClassLibrary1.dll Class1.cs

 
Sélectionnez
Imports System

Namespace ClassLibrary1
    ' Classe appelante
    Public Class Class1
        ' Méthode qui écrit kikoo dans la console 
        Public Shared Sub Write(message As String) 
            Console.Out.WriteLine(message)
        End Sub
    End Class
End Namespace

Si vous exécutez le code, vous verrez kikoo2 apparaître dans la console.

II-D-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 connaissez pas, utilisez la classe MethodInfo pour obtenir toutes les informations sur votre méthode. Après nous rentrons dans le vif du sujet sur la réflexion, ce qui n'est pas l'objet de cet article.

 
Sélectionnez
Imports System
Imports System.Reflexion

Namespace PluginApp
    ' Classe appelante
    Public Class CTest
        ' Point d'entrée du programme 
        Shared <STAThread()>
        Sub Main() 
            ' Chargement de l'assembly
            Dim a As Assembly
            a = Assembly.LoadFrom("ClassLibrary1.dll")

            ' Récupération de l'ensemble des classes contenu dans l'assembly
            Dim types As Type()
            types = a.GetTypes()

            Dim arg As New object(2);
            arg(0) = 2;
            arg(1) = 5;
            Dim i As Integer
            i = CInt(types[0].InvokeMember("Calc",BindingFlags.InvokeMethod |
                                                  BindingFlags.Public | 
                                                  BindingFlags.Static,null,null,arg))
            Console.WriteLine("2 + 5 = " + i)
        End Sub
    End Class
End Namespace

Le code suivant est compilé à part pour obtenir une librairie : csc /t:library /out:ClassLibrary1.dll Class1.cs

 
Sélectionnez
Imports System

Namespace ClassLibrary1
    ' Classe appelante
    Public Class Class1
        ' Méthode qui additionne deux entiers 
        Public Shared function Calc(int i, int j) As Integer
            Calc = i + j
        End function
    End Class
End Namespace

Si vous exécutez le code, vous verrez 2 + 5 = 7 apparaître dans la console.

III. 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.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Ce document peut être diffusé, amélioré, reformaté à souhait mais DOIT IMPERATIVEMENT contenir la source du document (http://www.developpez.com).