[Gelöst] VB.NET Plugins: gleiches Plugin mehrfach laden

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 15 Antworten in diesem Thema. Der letzte Beitrag () ist von dietzi.

    [Gelöst] VB.NET Plugins: gleiches Plugin mehrfach laden

    Hallo Leute,

    ich arbeite aktuell an einer Software, welche Plugins läd. Nun möchte ich aber das gleiche Plugin mehrfach instanzieren. Jegliche Versuche sind bisher gescheitert.

    Mein PluginInterfaace sieht so aus:

    VB.NET-Quellcode

    1. Public Interface IalarmPlugins
    2. ReadOnly Property Name() As String
    3. ReadOnly Property GUID As String
    4. Property PluginUID As Integer
    5. Sub Action(ByVal args() as Object)
    6. Sub showForm()
    7. Sub saveSettings()
    8. Sub loadSettings()
    9. End Interface


    Die Plugins werden nach Bedarf einem TreeView hinzugefügt:

    VB.NET-Quellcode

    1. Dim p As IalarmPlugins = _Plugins(ListBox1.SelectedIndex)
    2. Dim t As TreeNode = TreeView1.SelectedNode.Nodes.Add(p.Name)
    3. t.Tag = p


    Das TreeView speichere ich so:

    VB.NET-Quellcode

    1. Public Sub saveEvents()
    2. pluginCounter = 0
    3. Dim xml1 As New Xml.XmlDocument()
    4. Dim s As String = "<Events>"
    5. For Each t As TreeNode In TreeView1.Nodes
    6. Dim p As IalarmPlugins = t.Tag
    7. p.PluginUID = pluginCounter
    8. s += "<action name=""" + p.GUID + """ value=""" + p.PluginUID.ToString + """>"
    9. pluginCounter += 1
    10. For Each t2 As TreeNode In t.Nodes
    11. saveAlarmEventsHelper(t2, s)
    12. Next
    13. s += "</action>"
    14. Next
    15. s += "</Events>"
    16. xml1.InnerXml = s
    17. xml1.Save(Application.StartupPath + "/Actions.dat")
    18. End Sub
    19. Private Sub saveAlarmEventsHelper(ByVal node As TreeNode, ByRef xml As String)
    20. Dim p As IalarmPlugins = node.Tag
    21. p.PluginUID = pluginCounter
    22. pluginCounter += 1
    23. xml += "<action name=""" + p.GUID + """ value=""" + p.PluginUID.ToString + """>"
    24. For Each t As TreeNode In node.Nodes
    25. saveAlarmEventsHelper(t, xml)
    26. Next
    27. xml += "</action>"
    28. End Sub


    //Der Quellcode ist auf das nötigste reduziert und die Variablen für diese Zwecke angepasst

    Das Plugin selbst speichert seine Einstellungen. Allerdings speichert das Plugin nur die Einstellungen welche zuletzt durchlaufen werden. Irgendwie muss ich bei jedem Hinzufügen im TreeView eine neue Instanz vom Plugin erstellen - aber wie?

    Activator.CreateInstance bringt keinen Unterschied.
    Eine Klasse mit implements IalarmPlugins und dem Konstruktor New bringt auch keine Änderung.

    Hat jemand eine Idee wie ich das ganze umsetzen kann OHNE das Plugin mehrfach in den Ordner zu legen?

    LG Dietzi

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

    dietzi schrieb:

    Der Quellcode ist auf das nötigste reduziert und die Variablen für diese Zwecke angepasst
    Wenn Dein Problem darin besteht, ein Plugin mehrfach zu instanziieren, warum postest Du dann Deinen nicht relevanten Treeview-Code, nicht aber den relevanten Instanziierungs-Code?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    VB.NET-Quellcode

    1. If (Directory.Exists(Application.StartupPath + "/plugins/")) Then
    2. Dim dllFileNames() As String
    3. dllFileNames = Directory.GetFiles(Application.StartupPath + "/plugins/", "*.dll")
    4. Dim assemblies As ICollection(Of Assembly) = New List(Of Assembly)(dllFileNames.Length)
    5. For Each plugin As String In dllFileNames
    6. Dim an As AssemblyName = AssemblyName.GetAssemblyName(plugin)
    7. Dim ass As Assembly = Assembly.Load(an)
    8. assemblies.Add(ass)
    9. Next
    10. Dim pluginType As Type = GetType(IalarmPlugins)
    11. Dim pluginTypes As ICollection(Of Type) = New List(Of Type)
    12. For Each assembly As Assembly In assemblies
    13. If assembly <> Nothing Then
    14. Dim types As Type() = assembly.GetTypes()
    15. For Each type As Type In types
    16. If type.IsInterface Or type.IsAbstract Then
    17. Continue For
    18. Else
    19. If type.GetInterface(pluginType.FullName) <> Nothing Then
    20. pluginTypes.Add(type)
    21. End If
    22. End If
    23. Next
    24. End If
    25. Next
    26. Dim plugins As ICollection(Of IalarmPlugins) = New List(Of IalarmPlugins)(pluginTypes.Count)
    27. For Each type As Type In pluginTypes
    28. Dim plugin As IalarmPlugins = Activator.CreateInstance(type)
    29. plugins.Add(plugin)
    30. _Plugins.Add(plugin.GUID, plugin)
    31. Next
    32. End If


    Das funktioniert ja soweit. Entscheidend ist aber das "Neuanlegen" des Plugins im TreeView. An genau dieser Stelle müsste ich eine neue Instanz des Plugins erstellen - aber wie?
    @dietzi Ruf doch den Code einfach mehrfach auf.
    Ich hab das mal bei mir ausprobiert, funktioniert sehr gut.
    Ich verwende die Plugin-Methode von hier: codeproject.com/Articles/24340…ribute-CompositionContain
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Zeile 28 in meinem letzten geposteten Code bringt aber leider nichts....

    Wenn ich beim anlegen des Plugin im TreeView diesen Code ausführe

    VB.NET-Quellcode

    1. Dim p As IalarmPlugins = Activator.CreateInstance(_Plugins(ListBox1.SelectedIndex).GetType)

    ändert das gar nichts. Der Verweis bleibt dennoch auf das erst geladene Plugin bestehen. Das Plugin wird somit nicht mehrfach geladen.
    @dietzi Probierma einfach die DLL ins selbe Verzeichnis zu kopieren (unter einem anderen Namen).
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    dietzi schrieb:

    dynamisch zu klonen
    Dann musst Du sie das Interface IClonable implementieren lassen.
    Welches Plugin-System verwendest Du?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @dietzi OK, da hast Du Dir aber viel Arbeit gemacht.
    Neben obigem Link findest Du hier ein älteres System: Wie erstelle ich ein PlugIn System unter VB.NET (Framework 2)
    Was passiert, wenn Du diesen Code mehrfach aufrufst?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    ICloneablewar der richtige Tip:

    VB.NET-Quellcode

    1. Public Function Clone() As Object Implements ICloneable.Clone
    2. Return Activator.CreateInstance(DirectCast(New Class1, IalarmPlugins).GetType)
    3. End Function


    Jetzt funktioniert es so wie es soll.

    Das PluginInterface sieht jetzt so aus:

    VB.NET-Quellcode

    1. Public Interface IalarmPlugins
    2. Inherits ICloneable
    3. ReadOnly Property Name() As String
    4. ReadOnly Property GUID As String
    5. Property PluginUID As Integer
    6. Sub Action(ByVal args() as Object)
    7. Sub showForm()
    8. Sub saveSettings()
    9. Sub loadSettings()
    10. End Interface
    So.... Das Problem ist doch nicht behoben. Egal wie ich die Instanzierung aufrufe: es wird immer wieder auf das "erst geladene Plugin" zurück gegriffen. Selbst wenn ich die DLL 2 mal lade (die komplette Sub doppelt aufrufe) macht das keinen Unterschied. Irgendwie wird immer wieder auf das erste Plugin referenziert. Gepseichert werden die Plugins inzwischen in einem Dictionary (Of String, Of Plugin)

    Jemand eine Lösung?
    @dietzi Kannst Du mal einen Projektrumpf posten, der die DLL instanziiert?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    VB.NET-Quellcode

    1. Public Class singleMonitorPluginSettings
    2. Public Property monitorID As Integer
    3. Public Property pluginID As String
    4. Public Sub New()
    5. End Sub
    6. Public Sub New(ByVal _monitorID As Integer, ByVal _pluginID As String)
    7. monitorID = _monitorID
    8. pluginID = _pluginID
    9. End Sub
    10. End Class
    11. Public _SingleMonitorPlugins As New Dictionary(Of singleMonitorPluginSettings, ISingleMonitorPlugin)
    12. Public Sub loadSingleMonitorPluginsByMonitorID(ByVal monitorID As Integer)
    13. If (Directory.Exists(IO.Path.GetFullPath("plugins\"))) Then
    14. Dim dllFileNames() As String
    15. dllFileNames = Directory.GetFiles(IO.Path.GetFullPath("plugins\"), "*.dll")
    16. Dim assemblies As ICollection(Of Assembly) = New List(Of Assembly)(dllFileNames.Length)
    17. For Each plugin As String In dllFileNames
    18. Dim ass As Assembly = Assembly.LoadFile(plugin)
    19. assemblies.Add(ass)
    20. Next
    21. Dim pluginType As Type = GetType(ISingleMonitorPlugin)
    22. Dim pluginTypes As ICollection(Of Type) = New List(Of Type)
    23. For Each assembly As Assembly In assemblies
    24. If assembly <> Nothing Then
    25. Dim types As Type() = assembly.GetTypes()
    26. For Each type As Type In types
    27. If type.IsInterface Or type.IsAbstract Then
    28. Continue For
    29. Else
    30. If type.GetInterface(pluginType.FullName) <> Nothing Then
    31. pluginTypes.Add(type)
    32. End If
    33. End If
    34. Next
    35. End If
    36. Next
    37. For Each type As Type In pluginTypes
    38. Dim plugin As ISingleMonitorPlugin = Activator.CreateInstance(type)
    39. Dim ps As New singleMonitorPluginSettings(monitorID, plugin.GUID)
    40. If (Not _SingleMonitorPlugins.ContainsKey(ps)) Then
    41. _SingleMonitorPlugins.Add(ps, plugin)
    42. End If
    43. Next
    44. End If
    45. End Sub

    @dietzi Wie mache ich aus Deinem Code den Projektrumpf, der Deinen Effekt reproduziert?
    Eine abgespeckte Projektmappe wäre hilfreich.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    So sieht das ganze extem abgespeckt aus:

    VB.NET-Quellcode

    1. Public Interface ImeinInterface
    2. Property meineID as Integer
    3. End Interface
    4. Public Module testingModule
    5. Public Class singleMonitorPluginSettings
    6. Public Property monitorID As Integer
    7. Public Property pluginID As String
    8. Public Sub New()
    9. End Sub
    10. Public Sub New(ByVal _monitorID As Integer, ByVal _pluginID As String)
    11. monitorID = _monitorID
    12. pluginID = _pluginID
    13. End Sub
    14. End Class
    15. Public _SingleMonitorPlugins As New Dictionary(Of singleMonitorPluginSettings, ImeinPlugin)
    16. Public Sub loadSingleMonitorPluginsByMonitorID(ByVal monitorID As Integer)
    17. If (Directory.Exists(IO.Path.GetFullPath("plugins\"))) Then
    18. Dim dllFileNames() As String
    19. dllFileNames = Directory.GetFiles(IO.Path.GetFullPath("plugins\"), "*.dll")
    20. Dim assemblies As ICollection(Of Assembly) = New List(Of Assembly)(dllFileNames.Length)
    21. For Each plugin As String In dllFileNames
    22. Dim ass As Assembly = Assembly.LoadFile(plugin)
    23. assemblies.Add(ass)
    24. Next
    25. Dim pluginType As Type = GetType(ImeinPlugin)
    26. Dim pluginTypes As ICollection(Of Type) = New List(Of Type)
    27. For Each assembly As Assembly In assemblies
    28. If assembly <> Nothing Then
    29. Dim types As Type() = assembly.GetTypes()
    30. For Each type As Type In types
    31. If type.IsInterface Or type.IsAbstract Then
    32. Continue For
    33. Else
    34. If type.GetInterface(pluginType.FullName) <> Nothing Then
    35. pluginTypes.Add(type)
    36. End If
    37. End If
    38. Next
    39. End If
    40. Next
    41. For Each type As Type In pluginTypes
    42. Dim plugin As ImeinPlugin = Activator.CreateInstance(type)
    43. Dim ps As New singleMonitorPluginSettings(monitorID, plugin.GUID)
    44. plugin.meineID = monitorID
    45. If (Not _SingleMonitorPlugins.ContainsKey(ps)) Then
    46. _SingleMonitorPlugins.Add(ps, plugin)
    47. End If
    48. Next
    49. End If
    50. End Sub
    51. Sub Main()
    52. For i = 1 To 5
    53. loadSingleMonitorPluginsByMonitorID(i)
    54. Next
    55. End Sub
    56. End Module


    Rufe ich dann irgendwo in meinem Programm

    VB.NET-Quellcode

    1. MsgBox(_SingleMonitorPlugins(New singleMonitorPluginSettings(1,GUID)).meineID)

    auf wird mir genau die gleiche GUID angezeigt wie wenn ich

    VB.NET-Quellcode

    1. MsgBox(_SingleMonitorPlugins(New singleMonitorPluginSettings(3,GUID)).meineID)

    aufrufe.


    EDIT:
    Problem gefunden.... Ich habe beim Zugriff auf das Dictionary im Key die monitorID nicht gesetzt und somit war diese immer bei 1. Manchmal sieht man den Wald vor lauter Bäumen nicht.....

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