vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#
Mails senden, abrufen und decodieren - ganz easy ;-)  
 vb@rchiv Quick-Search: Suche startenErweiterte Suche starten   Impressum  | Datenschutz  | vb@rchiv CD Vol.6  | Shop Copyright ©2000-2024
 
zurück
Rubrik: .NET   |   VB-Versionen: VB.NET15.09.06
Erstellen von Plugins zum Erweitern eigener Programme

Mit diesem Workshop möchten wir Ihnen zeigen, wie man seine eigenenen Programme um sogenannte Plugins in Form von DLL-Dateien erweitern kann.

Autor:  Fabian SternBewertung:     [ Jetzt bewerten ]Views:  32.538 

Mit diesem Workshop möchten wir Ihnen zeigen, wie man seine eigenenen Programme um sogenannte Plugins in Form von DLL-Dateien erweitern kann.

Ein Tutorial von Fabian Stern (  http://www.smart-coding.com )

Die drei Projekte zu den hier angesprochenen Codes sind im Download enthalten.

Vorwort
Viele Programmierer möchten nachträglich die Möglichkeit haben, bereits ausgelieferte Programme zu erweitern oder grundlegende Funktionen zu ändern, um einen besseren Funktionsablauf des eigentlichen Programms zu gewährleisten. Genau zu diesem Zweck haben sich Plugins in Form von DLL-Dateien durchgesetzt, die wir in der .NET-Welt Assembly-Klassen nennen sollten. Dieses Tutorial soll aufzeigen, wie einfach sich das für eigene Programme verwirklichen lässt und dabei weniger Theorie als Praxis verwenden.

Und darum geht es auch gleich los mit…

1. Funktionsweise von Plugins in .NET durch ein Interface
Plugins sind nichts anderes als Codefragmente, die zur Laufzeit in das Programm implementiert werden und somit die Funktionalität erweitern. Dabei werden alle Funktionen, die durch das Interface gepushed (geschoben) werden, zunächst genauestens definiert und festgelegt. Es entsteht somit ein Schattenabbild von Funktionen, Properties oder Subs, die in einem Interface Platz finden, aber keinerlei Code enthalten.

Das Interface ist folgendermaßen aufgebaut:

(clsPluginLoader)

Public Interface IPlugin
  Function GetName() As String
  Sub Load()
  Sub Unload()
  Sub Run()
  Sub SetHost(ByVal mHost As IHost)
  End Interface
 
  Public Interface IHost
  Function myLabel() As System.Windows.Forms.Label
End Interface

Das Plugin besitzt also beispielsweise die Funktion GetName, die eine eindeutige Kennung des Plugins zurückliefern sollte, damit man es später identifizieren und zuordnen kann. In dem Assembly clsPluginSample ist der eindeutige Name einfach "MyPlugin by Fabian Stern".

Unsere Anwendung hingegen soll lediglich den Zugriff auf ein Label ermöglichen, in das wir vom Plugin aus hineinschreiben möchten. Wie wir sehen, ist also ein Zwei-Wege-Zugriff durchaus erwünscht und möglich. Nun gilt ein Spezialfall: Die Interfaces müssen von Plugin und Anwendung identisch sein, daher erstellen wir die Interfaces in einem extra Assembly (clsPluginLoader.dll) und fügen sowohl beim Plugin als auch bei der Anwendung einen Verweis auf diese Datei hinzu.

Damit haben wir Schritt 1 von 3 getan und können gleich weitergehen zur...

2. Erstellung des Plugins mit vorgegebenen Funktionen vom Interface
(clsPluginSample)

Ist das Interface erstellt und der Verweis auf den Loader vollzogen, müssen grundlegende Dinge beachtet werden:

  • A. Ein bekannter Namespace und ein bekannter Klassenname, der bei jedem Plugin identisch sein sollte.

                   Hier clsPluginSample.clsPlugin

  • B. Die Implementierung des Interfaces durch das Implements-Schlüsselwort
    Implements clsPluginLoader.IPlugin
  • C. Das Festlegen der Gültigkeit (meist ist diese unendlich)
    Public Overrides Function InitializeLifeTimeService() As Object
      Return Nothing 	' für unendliche Gültigkeit
    End Function

Zunächst jedoch müssen wir der CLR (Common Language Runtime) erlauben, das Plugin zur Laufzeit zu erstellen. Dies geschieht durch einfache Serialisierung der Klasse:

<Serializable()> Public Class clsPlugin  ' Make it creatable on Runtime

Hiermit erlauben wir das Kapseln der Funktionen mit unserem Programm:

Inherits MarshalByRefObject  ' AppDomain Compatibility

Es folgt ein Beispiel einer gekapselten Funktion:

Public Function GetName() As String Implements clsPluginLoader.IPlugin.GetName
  ' Get the plugin name
  Return "MyPlugin by Fabian Stern"
End Function

Zunächst haben wir also eine ganz normale Funktion, die durch Implements an das Interface und die Funktion GetName im Interface gekoppelt ist. Funktionsname und Interface-Funktionsname können sich durchaus unterscheiden. Hier wird auch der Code festgelegt, der beim Aufruf ausgeführt werden soll – die Rückgabe des eindeutigen Plugin-Namen.

Um innerhalb des Plugins einen Zugriff auf die Hauptanwendung zu erhalten, muss die Hauptanwendung selbst die Objekte zum Plugin leiten, dann kann das Plugin darauf zugreifen:

Dim myHost As clsPluginLoader.IHost

und

Public Sub SetHost(ByVal mHost As clsPluginLoader.IHost) _
  Implements clsPluginLoader.IPlugin.SetHost
 
  myHost = mHost 	' Set a link to objects of the main-application
End Sub

Hierbei ist myHost unsere Zugriffsvariable auf die von der Hauptanwendung freigegebenen Objekte und Funktionen. Die Hauptanwendung ruft SetHost im Plugin auf und übergibt uns "sich selbst", damit wir darauf zugreifen können. Fortan haben wir myHost.myLabel als zugreifbares Objekt (also das lblAccess in wappPluginSample) von unserem Plugin aus.

Da nun alles Wichtige zur Laufzeit, Serialisierung und Zugreifbarkeit gesagt wurde, geht es weiter zu

3. Erstellen einer AppDomain und Laden der Plugins in der Hauptanwendung
(wappPluginSample)

Durch die Funktion InitPlugins werden alle Plugins eingeladen, die sich im Anwendungsverzeichnis-Ordner "Plugins" befinden (also momentan nur plugins\clsPluginSample.dll). Unsere Klasse erbt die Funktionen des Formulars, da es sich hier um eine Windows-Forms-Anwendung handelt. Wir müssen auch noch unser IHost-Interface an die Form-Klasse Form1 koppeln:

Implements clsPluginLoader.IHost

Hier folgen nun unsere wichtigsten Elemente zur Plugin-Funktionalität:

Dim mySetup As New AppDomainSetup
Dim myAppDomain As AppDomain
Dim myEvidence As System.Security.Policy.Evidence = AppDomain.CurrentDomain.Evidence
Dim myPlugin() As clsPluginLoader.IPlugin

AppDomainSetup: Wir wollen hier lediglich die ApplicationBase festlegen, die den Hauptpfad unserer Anwendung und Plugins beinhaltet.

AppDomain: Die wichtigste Variable, da wir in diese Domain (etwas, das unsere Plugins verwaltet) unsere Plugins packen, und damit ermöglichen, alle Plugins zu einer späteren Zeit wieder zu entladen. Die Entlademöglichkeit ist sehr wichtig, wenn man beispielsweise sein Programm nicht beenden möchte, um neue Plugins hinzuzufügen. Trotzdem ist das manchmal erforderlich, wenn man das Interface ändert oder weitere Objekte hinzugefügt hat, die nun allen Plugins zur Verfügung stehen sollen. Hierbei gelten folgende Regeln:

  1. Wenn ich das IHost Interface verändere, brauche ich ältere Plugins nicht zu verändern, es sei denn, ich ändere eine Funktion, auf die die Plugins zugreifen.
  2. Wenn ich das IPlugin Interface verändere, muss ich alle Plugins noch einmal neu compilieren, da die Hostanwendung nun kein identisches Abbild der Pluginfunktionen generieren kann.

Evidence: Der Evidence (Nachweis) ist im Grunde zur Sicherheit geschaffen worden. Damit verhindert man, dass ungewollter Code in das Hauptprogramm eingeschleust werden kann. Da es hier unzählige Informationen wie zum Beispiel Sponsoren oder Gültigkeitsbereiche gibt, verweise ich auf die MSDN. ( http://msdn.microsoft.com )

Evidence und Setup werden nun benötigt, um die AppDomain zu erstellen:

mySetup.ApplicationBase = System.Environment.CurrentDirectory
myAppDomain = AppDomain.CreateDomain("AppBase", myEvidence, mySetup)

Nun geht alles vergleichsweise einfach:

myPlugin(myPI) = myAppDomain.CreateInstanceFromAndUnwrap(myFile, "clsPluginSample.clsPlugin") 
' Add Plugin to myPlugin variable
myPlugin(myPI).SetHost(Me)

Die AppDomain soll das Plugin mit dem eindeutigen Namespace und der eindeutigen Klasse aus dem Plugins-Ordner einladen und zur Ausführung bereitstellen (CreateInstanceFromAndUnwrap).

Als nächstes wird im Plugin die Funktion SetHost aufgerufen, bei der wir alle von der Hauptdomain freigegebenen Objekte, Funktionen, etc. durch Me übergeben. Das Plugin kennt an dieser Stelle durch das Interface schon alle benötigten Informationen.

Da hier die Möglichkeit gegeben ist, mehrere Plugins einzuladen, haben wir ein Array myPlugin(), welches uns alle Plugins zur Verfügung stellt.

An dieser Stelle möchte ich noch einmal darauf hinweisen, dass alle Plugins dieselben Objekte, Funktion, etc. haben müssen, die im Interface definiert sind. Das wird jetzt auch klar, denn (es folgt ein nicht machbares Beispiel):

PluginA hat die Funktionen GetSong und GetName.
PluginB hat die Funktionen GetLyrics und GetName.

Im Interface steht jedoch nur GetSong, nicht aber GetLyrics.

Wenn wir nun myPlugin(myPI). (der Punkt dahinter ist wichtig) im Code-Editor eingeben, sähen wir in der Quickhilfe lediglich die Funktion GetSong. Somit ist PluginB ungültig und kann nicht verwendet werden.

Man kann dieses Problem unter einer Ausnahme umgehen:

Die Funktion muss Identische Parameter sowie Rückgabetypen haben, dann kann man im Code des Plugin- und Hauptassemblies Implements clsPluginSample.GetSong bei der Funktion GetLyrics angeben, da - wie vorhin schon erwähnt - Funktionsname vom Interfacefunktionsnamen unterschiedlich sein kann.

4. "Last but not least"

Sind nun Plugin, Loader und Hauptanwendung auf dieselben Funktionen abgestimmt, steht der Benutzung des Plugins nichts mehr im Wege. Einmal eingeladene Plugins können mit dem hier beschriebenen Weg nicht einzeln wieder verworfen werden. Dazu müssen alle Plugins mit dem Verwerfen der AppDomain ent-laden, und danach alle wieder ge-laden werden:

For I As Int32 = 0 To myPlugin.GetUpperBound(0)
  If Not myPlugin(I) Is Nothing Then
    myPlugin(I).Unload()
    myPlugin(I) = Nothing
  End If
Next
If bLoaded Then AppDomain.Unload(myAppDomain) : bLoaded = False

Beachtet werden muss auch, dass eine AppDomain nur einmal entladen werden kann. Andernfalls folgt ein Ausnahmefehler, da die AppDomain ja nicht mehr existiert.

5. Letzte Verdeutlichung zur Funktionsweise von Plugins
Letztendlich besitzen wir drei wichtige Assemblies, die miteinander verbunden werden.

  1. clsPluginLoader (ist verbunden mit) -> clsPlugin, wappPluginSample
  2. wappPluginSample -> clsPluginLoader (zwei-wege) <- clsPlugin, … (und weiteren Plugins)
  3. clsPlugin, … (und weitere Plugins) -> clsPluginLoader (zwei-wege) <- wappPluginSample

Bei der Anfertigung sollten also folgende Dinge zuerst erledigt werden:

  1. Überlegt, welche Funktionen, Objekte, etc. ihr beim Plugin, und welche bei eurer Hauptanwendung freigeben möchtet (denn diese sind fest und können nur schwerlich verändert werden).
  2. Beginnt mit dem Loader (Also IHost und IPlugin)
  3. Fügt den Verweis zur Loader-Assembly eurer Hauptanwendung hinzu, dann dem Plugin
  4. Erstellt das erste Plugin und prüft, ob eure Funktionen… ausreichen
  5. Zuletzt sollte die Hauptanwendung editiert, und Funktionen zum Laden/Entladen der Plugins angefertigt werden.
  6. Erstellt eine Batch-Datei, die aus allen bin-Ordnern die .DLL bei Loader und Plugins in den bin\Plugins-Ordner der Hauptanwendung kopieren. Wenn ihr nun ein Plugin geändert habt, braucht ihr nur doppelklicken und habt eure Hauptanwendung aktuell

6. Schlusswort
Zunächst klingt alles ein wenig kompliziert und schwer. Aber auch ohne an die hundert Seiten über Plugins zu lesen, sollten mit dem Tutorial gute Plugins erstellt werden können. Seht euch vielleicht noch einmal die Code-Beispiele in der MSDN zur AppDomain an. Übrigens: Für Plugins, die ihr zur Laufzeit nicht entladen wollt, gibt es einen einfacheren Weg ohne AppDomains. Dazu schaut einmal bei Reflection nach. Hier gibt es den Activator mit der Funktion CerateInstanceFromAndUnwrap. Man braucht dann keine Interfaces, sondern kann auf Public Funktionen der Assemblies zugreifen. Für „richtige“ Plugins empfehle ich allerdings den Weg über die AppDomain.

Falls während des Durcharbeitens Fehler oder Probleme entstanden sind, könnt ihr eine Mail an webmaster@smart-coding.com mit Betreff: Plugin-Tutorial Frage senden. Ich antworte so schnell wie möglich. Bei der Gelegenheit möchte ich auch auf meine Homepage www.smart-coding.com verweisen, bei der ihr eure Sources hochladen, in Foren und an Votes teilnehmen und eure eigene mySQL Datenbank mit externem Zugriff haben könnt. Ich freue mich über eure Anmeldung und ein paar neue Dateien/Projekte oder Links.

Weiterhin habt ihr die Möglichkeit, mir "Live" im IRC-Chat von irc.cumZ.de (Raum #cumZ) Fragen zu stellen.

Dieser Workshop wurde bereits 32.538 mal aufgerufen.

Über diesen Workshop im Forum diskutieren
Haben Sie Fragen oder Anregungen zu diesem Workshop, können Sie gerne mit anderen darüber in unserem Forum diskutieren.

Aktuelle Diskussion anzeigen (7 Beiträge)

nach obenzurück


Anzeige

Kauftipp Unser Dauerbrenner!Diesen und auch alle anderen Workshops finden Sie auch auf unserer aktuellen vb@rchiv  Vol.6
(einschl. Beispielprojekt!)

Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv!
- nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten
- Symbol-Galerie mit mehr als 3.200 Icons im modernen Look
Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m.
 
   

Druckansicht Druckansicht Copyright ©2000-2024 vb@rchiv Dieter Otter
Alle Rechte vorbehalten.
Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein.

Diese Seiten wurden optimiert für eine Bildschirmauflösung von mind. 1280x1024 Pixel