Wie erstelle ich ein PlugIn System unter VB.NET (Framework 2)

    • VB.NET

    Es gibt 47 Antworten in diesem Thema. Der letzte Beitrag () ist von Linkai.

      Wie erstelle ich ein PlugIn System unter VB.NET (Framework 2)

      hallo,

      die Frage lautet, wie erstelle ich ein PlugIn System unter VB.NET.
      Ich will dies hier anhand einer kleinen Anwendung zeigen.
      Verwenden werde ich das Framework 2 - im Framework 3.5 gibt es dafür einen eigenen Namespace !
      Wobei der hier gezeigte weg auch im Framework 3 und 3.5 angewendet werden kann.

      Zuerst erstellen wir uns eine WindowsForms Anwendung.
      Dieser wird unser Host, welche die PlugIns lädt und steuert.

      Das Problem bei einem PlugIn System (oder die Idee welche dahintersteckt) ist doch folgendes.
      Wie kann ich eine Anwendung schreiben, die Problemlos erweiterbar ist, ohne jedesmal die exe verändern zu müssen.
      Z.B. wenn viele Entwickler Teile zum ganzen beitragen wollen, aber nicht jeder den Quellcode der eigentlichen exe haben kann.

      Dafür benötigen wir eine Schnittstelle, die die Kommunikation zwischen dem Host (exe) und den Clients (PlugIn dll's) regelt.

      Für unser Beispiel hab ich mir gedacht, Rechnen wir einfach mal einbischen.
      OK - Dann wollen wir beginnen.
      Die Form benötigt folgende Steuerelemente

      2 x ListBox (ListBox1 und ListBox2)
      3 x Label (Label1, Label2,Label3)
      1 x Button

      Ich denke soweit ist alles klar, wie man die Steuerelemente platziert sollte ja kein Problem darstellen.

      Nun zur Schnittstelle.

      Fügen wir nun zu unserem Host Projekt ein weiteres eigenständiges Projekt hinzu.
      Dies geht im Datei-Menü unter Hinzufügen (Add) eines neuen Projektes.
      Nennen Sie das neue Projekt z.B. Interface
      Dafür müssen wir unser Host Projekt mindestens schon einmal gespeichert haben. ;)

      OK - da die Schnittstelle eine eigene DLL werden soll erstellen wir nun eine Klassenbibliothek.
      Nennen Sie diese z.B. MyInterface.vb
      In dieser Klassenbibliothek führen wir alle öffentlichen Funktionen, Subs oder Properties auf.
      Hier der Code für unsere Beispiel Schnittstelle.

      VB.NET-Quellcode

      1. 'hier müssen alle öffentlichen members aufgeführt werden, welche die schnittstelle transportieren soll.
      2. Public Interface myInterface
      3. 'z.b. funktion
      4. Function Addieren(ByVal zahl1 As Integer, ByVal zahl2 As Integer) As Integer
      5. 'z.b. property
      6. ReadOnly Property Datum() As Date
      7. End Interface

      soweit so gut.
      Um nun diese Schnittstelle sichtbar zu machen müssen wir diese einmal kompilieren.

      Nach dem kompilieren fügen wir unserer Host-Anwendung einen Verweis zum Interface hinzu.
      Dies geht am Einfachsten im Menü Projekt -> Verweis hinzufügen
      Dort suchen Sie die Interface Dll - diese befindet sich im Interface Projekt Ordner unter Release

      Jetzt zum Client.
      Der Client ist ebenfalls eine Dll.
      Also - wieder dem Projekt ein neues Projekt hinzufügen - und wieder eine Klassenbibliothek.
      Der Client muss natürlich auch etwas vom Interface wissen.
      Setzen Sie auch hier einen Verweis auf die Interface dll.

      Nun zum Code des Client (PlugIn)
      Normalerweise sollte, sobald sie

      VB.NET-Quellcode

      1. Implements [Interface].myInterface

      eingegeben haben, die IDE alle öffentlichen Member - aus dem Interface - selbstständig eintragen.
      geschieht dies nicht, macht das auch nichts. Dann machen wir es eben von Hand.

      VB.NET-Quellcode

      1. 'eine einfache plugin dll
      2. Public Class myClient
      3. Implements [Interface].myInterface
      4. Public Function Addieren(ByVal zahl1 As Integer, ByVal zahl2 As Integer) As Integer Implements [Interface].myInterface.Addieren
      5. Return zahl1 + zahl2
      6. End Function
      7. Public ReadOnly Property Datum() As Date Implements [Interface].myInterface.Datum
      8. Get
      9. Return Now
      10. End Get
      11. End Property
      12. End Class


      Was jetzt noch fehlt ist der Code unserer Host Anwendung.
      Diese besteht aus der Form und einer Klasse zum laden der Dll's
      Hier erstmal der Code für die Form

      VB.NET-Quellcode

      1. Imports System.IO
      2. Public Class Form1
      3. Private myplugin As [Interface].myInterface
      4. Private WithEvents t As New Timer
      5. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
      6. For i As Integer = 1 To 100
      7. Me.ListBox1.Items.Add(i.ToString)
      8. Me.ListBox2.Items.Add(i.ToString)
      9. Next
      10. 'alle plugins laden - in unserem fall ja nur eines
      11. For Each plugin As String In Directory.GetFiles(My.Application.Info.DirectoryPath, "*.dll")
      12. myplugin = PlugInConnector.LoadPlugIn(plugin)
      13. 'lass uns hier mal die schleife beenden.
      14. 'da sich unsere client dll im selben ordner wie die interface dll befindet, kann es dazu kommen, dass das interface
      15. 'zuletzt untersucht wird. dies würde dann unser gültiges plugin überschreiben
      16. If myplugin IsNot Nothing Then Exit For
      17. Next
      18. If myplugin Is Nothing Then
      19. MessageBox.Show("Kein gültiges PlugIn gefunden !")
      20. Me.Close()
      21. End If
      22. 'noch ein paar texte erstellen
      23. Me.Label1.Text = ""
      24. Me.Label2.Text = "+"
      25. Me.Label3.Text = "= ?"
      26. Me.Button1.Text = "Addieren"
      27. 'jeweils das erste item jeder listbox aktiviren
      28. Me.ListBox1.SelectedIndex = 0
      29. Me.ListBox2.SelectedIndex = 0
      30. 'jetzt noch den timer starten
      31. t.Interval = 1000
      32. t.Start()
      33. End Sub
      34. Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
      35. Me.Label3.Text = " = " & myplugin.Addieren(CInt(Me.ListBox1.SelectedItem), CInt(Me.ListBox2.SelectedItem))
      36. End Sub
      37. Private Sub t_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles t.Tick
      38. Me.Label1.Text = myplugin.Datum.ToString
      39. End Sub
      40. End Class


      So nun zum interessanten Teil der Host-Anwendung.
      Fügen sie zu Ihrem Projekt eine neue Klasse hinzu und nennen Sie diese

      PlugInConnector.vb

      Zu dieser Klasse gehört folgender Code.

      VB.NET-Quellcode

      1. Public Class PlugInConnector
      2. Public Shared Function LoadPlugIn(ByVal strFile As String) As [Interface].myInterface
      3. Dim vPlugIn As [Interface].myInterface
      4. Dim a As System.Reflection.Assembly = System.Reflection.Assembly.LoadFile(strFile)
      5. Dim types() As Type = a.GetTypes
      6. For Each pType As Type In types
      7. 'hier wird versucht die dll zu laden.
      8. 'dies funktioniert nur, wenn die dll auch das selbe interface implementiert
      9. 'wie der host vorgiebt
      10. Try
      11. vPlugIn = CType(a.CreateInstance(pType.FullName), [Interface].myInterface)
      12. 'dll ist ein gültiges plugin
      13. Return vPlugIn
      14. Catch ex As Exception
      15. End Try
      16. Next
      17. 'keine gültige dll gefunden
      18. Return Nothing
      19. End Function
      20. End Class

      Ich hoffe, dass ich diesen ausreichend Kommentiert habe, sodass keine Fragen dazu auftreten sollten.

      Hinweis1:
      Sie sollten sich einen Ordner anlegen, indem sie alle PlugIn Dll's sammeln.
      In unserem Beispiel ist das der Anwendungsordner.
      Bei dieser Art von Anwendung, kann man den Debugger nur sinnvoll nutzen, wenn man nach jeder Codeänderung im
      Client (PlugIn) diesen neu kompilieren lässt.
      Standardmäßig wird die erzeugte Dll dann im eigenen Projekt im Debug oder Release Ordner abgelegt.
      Dies bedeutet weiterhin, dass wir dann jedesmal die kompilierte Dll in unseren PlugIn Ordner kopieren müssen.

      Um dies zu vermeiden tragen wir am besten unseren PlugIn Ordner als Ausgabeort in den Projekteigenschaften ein.

      Hinweis2:
      Ich würde gerne ein paar Screenshots hinzufügen, jedoch kann ich im moment nicht auf meine Homepage zugreifen,
      das das Hotel hier diese Art der Verbindung nicht erlaubt.
      Deshalb bitte geduld bis nächste Woche, da bin ich dann wieder daheim.

      Nun viel Spass beim PlugIn entwickeln

      Gruss

      mikeb69
      Hey, gutes Tutorial :]

      Ich habe alles gemacht wie Beschrieben (bis zu folgendem Punkt:

      eingegeben haben, die IDE alle öffentlichen Member - aus dem Interface - selbstständig eintragen.
      geschieht dies nicht, macht das auch nichts. Dann machen wir es eben von Hand.

      VB.NET-Quellcode

      1. 'eine einfache plugin dll
      2. Public Class myClient
      3. Implements [Interface].myInterface
      4. Public Function Addieren(ByVal zahl1 As Integer, ByVal zahl2 As Integer) As Integer Implements [Interface].myInterface.Addieren
      5. Return zahl1 + zahl2
      6. End Function
      7. Public ReadOnly Property Datum() As Date Implements [Interface].myInterface.Datum
      8. Get
      9. Return Now
      10. End Get
      11. End Property
      12. End Class

      )

      Aber bei "[Interface].myInterface" bekomme ich immer den Fehler:

      "Fehler 1 Der Typ "Interface.MyInterface" ist nicht definiert. C:\Users\☺☺☺\Documents\Visual Studio 2008\Projects\Plugin System\MyClient\MyClient.vb 3 16 MyClient"

      Kann mir jemand helfen ? :]
      Halllo,
      Gutes Tutorial erstmal, Aber wie musss ich es machen, wenn ich nicht weiß,
      Wieviele Plugins es Gibt.
      Das ist ja auch eigentlich der sinn von Plugins:
      Der Anwender Kann selbständig das Programm erweitern.

      MfG

      edit für mich gehts so:

      eine Text eigenschaft in die Schnittstelle
      beim laden neues menuitem erstellen mit dem selben text

      bei click auf das entsprechende Menuitem überpüfen ob der text gleich ist
      (Alle plugins weden in einer liste zusammengeffast.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Atlas“ ()

      er hat doch die Funktion LoadPlugin ... und ich geh mit der einfach immer ein Verzeichnis durch und lasse mir alle dlls laden:

      VB.NET-Quellcode

      1. Dim PlugIns As New List(Of MeinInterface)
      2. For Each plug As String In IO.Diretory.GetFiles(Application.StartupPath & "\plugins", "*.dll")
      3. Dim PlugIn As MeinInterface = LoadPlugin(plug)
      4. If Not(PlugIn Is Nothing) Then PlugIns.Add(PlugIn)
      5. Next

      Das ist zwar nur so ausm Kopf geschrieben, sollte aber hinhauen. Was du danach machst mit den Plugs ist dir überlassen.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Marcus Gräfe“ ()

      Kann ich dem Host auch ein ganzes Form übergeben? Wenn ja, wie muss ich das in der Client.dll machen, muss ich da jedes Control per Code erstellen, oder kann ich das mit dem "Designer" machen?

      Edit: Ich hab es gelöst. Ich habe einfach ein Windowsform der Klassenbibliothek hinzugefügt. Dann im Interface eine Sub deklariert. Und im Client, habe ich in dem Sub folgendes geschrieben.

      VB.NET-Quellcode

      1. Dim test As New die_form
      2. test.Show()


      Wenn man im Host, dann den Sub aufruft, wird die Form geöffnet.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Chris213“ ()

      Schneider96 schrieb:

      Und wie bekomme ich jetzt ein 2tes Plugin eingebunden ohne die exe zu verändern?

      In dem du mit der oben genannten Funktion deine Plugin-Dll lädst und dieses PlugIn verwendest.
      Mal angenommen ich mache jetzt ein 2tes Plugin....

      VB.NET-Quellcode

      1. 'eine einfache plugin dll
      2. Public Class myClient
      3. Implements [Interface].myInterface
      4. Public Function Subtrahieren(ByVal zahl1 As Integer, ByVal zahl2 As Integer) As Integer Implements [Interface].myInterface.Subtrahieren
      5. Return zahl1 - zahl2
      6. End Function
      7. End Class


      Das muss ich doch im Interface auch erst eingeben. Und zuvor muss ich doch das ganze in der exe auch coden damit dies funktion machbar ist oder nicht?
      Du kannst mit den oben gennanten Funktionen deine DLLs laden, wenn sie eine Klasse, das mit dem Interface aufbaut, hat. Also machst du für dein PlugInSystem eine Routine in der Exe welche einen Ordner nach PlugIns durchsucht (System.IO) und dann machst du einfach in einem neuen Projekt ein paar PlugIns als DLL und dann kannst du sie in den PlugIn-Ordner der Exe packen und sie werden geladen.
      Mann kann das auch über Delegates machen, aber das würde dem Event-System entsprechen
      Es ist für mich immer noch nicht schlüssig.

      Sinn und Zweck eine Plugins ist es doch einem fertigen Programm weiter Plugins zur Verfügung zu stellen?
      D.h Plugin entwerfen und in den PuginOrdner schmeißen. Aber hier muss ich ja dazu immer noch die Interface bearbeiten um die öffentlichen Member zu erweitern. Ist es nur so möglich?
      Warum musst du das Interface bearbeiten ?
      Bei einer gutdurchdachten Struktur muss der PlugIn-Bereich nicht überarbeitet werden.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Mangafreak1995“ ()

      Eine Lösung, eine gewisse Kompatibelität zu erzielen ist, einfach mehrere kleine Interfaces zur Verfügung zu stellen.
      Sollten dann Updates kommen, fügt man einfach ein neues, kleines Interface hinzu. Somit werden die älteren Plugins nicht inkompatibel, da es kein großes Interface mehr gibt, das die Identität ändern kann.
      Von meinem iPhone gesendet

      Käfersuche.....

      Hier wurde zwar schon seit einigen Tagen nichts mehr geschrieben, vieleicht kann ich aber trotzdem noch eine antwort abfangen ;)

      hab probiert das ganze pluginssystem etwas für meine zwecke abzuändern, jedoch, wie der thread ja erwarten lässt, ohne erfolg.

      hier mal mein ansatz:

      HostProgramm:
      Spoiler anzeigen

      VB.NET-Quellcode

      1. Public Class ZentralForm
      2. Private myModule As [BasisModule].myInterface
      3. Private modules As List(Of [BasisModule].myInterface)
      4. Private Function LoadModule(ByVal strFile As String) As [BasisModule].myInterface
      5. Dim newModule As [BasisModule].myInterface
      6. Dim a As System.Reflection.Assembly = System.Reflection.Assembly.LoadFile(strFile)
      7. Dim types() As Type = a.GetTypes
      8. For Each pType As Type In types
      9. Try
      10. 'Fehler
      11. newModule = CType(a.CreateInstance(pType.FullName), [BasisModule].myInterface)
      12. Return newModule
      13. Catch ex As Exception
      14. End Try
      15. Next pType
      16. Return Nothing
      17. End Function
      18. Private Sub SearchModules()
      19. modules = Nothing
      20. For Each modu As String In System.IO.Directory.GetFiles(My.Application.Info.DirectoryPath, "*.dll")
      21. myModule = LoadModule(modu)
      22. If myModule IsNot Nothing Then
      23. modules.Add(myModule)
      24. End If
      25. Next modu
      26. End Sub
      27. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
      28. SearchModules()
      29. End Sub
      30. End Class



      Interface .dll
      Spoiler anzeigen

      VB.NET-Quellcode

      1. Public Interface myInterface
      2. Function Work(ByVal FunctionName As String, ByVal Input As Object) As Object
      3. End Interface


      Spoiler anzeigen

      VB.NET-Quellcode

      1. Public Class Class1
      2. Implements [BasisModule].myInterface
      3. Function Work(ByVal FunctionName As String, ByVal Input As Object) As Object Implements [BasisModule].myInterface.Work
      4. Return "RückgabeObjekt"
      5. End Function
      6. End Class



      Der Fehler wird im Hostprogramm bei folgender Zeile angezeigt: newModule = CType(a.CreateInstance(pType.FullName), [BasisModule].myInterface)
      Bei jedem Schleifendurchlauf bekomme ich bei der zeile verschiedene Meldungen:

      -> object kann nicht umgewandelt werden
      -> konstruktor nicht gefunden
      -> instanz kann nicht erstellt werden


      ich möchte im hostprogramm alle .dlls innerhalb eines ordners verfügbar machen. eine Funktion (Work) soll implementiert werden und somit als schnittstelle dienen.

      Hostprogramm, sowie die .dll haben einen verweis auf das interface.


      Hat hier vieleicht jemand mit etwas mehr durchblick die güte mir zu helfen? ;)