Pluginschnittstelle

fadade

BIOS-Overclocker(in)
Hey,

wie der Titel schon sagt, wollte ich mal wissen, ob jemand schonmal eine Pluginschnittstelle programmiert hat.
Bestes Beispiel momentan: Minecraft mit Server Mods

Die bestehende Serversoftware bietet einen Ordner "Plugins" an, in dem man Plugins einfach reinkopieren muss und beim nächsten Start werden sie mitgeladen. Die bereigestellten Funktionen haben oft SEHR GROßEN EINFLUSS auf das Spiel, scheinen also sehr tief im eigentlichen Spiel "verankert" zu sein.
Jetzt hat sich mir die Frage gestellt, wie man sowas macht (ich verweise hier mal auf den Thread Programmierte Intelligenz, wo man das z.B. auch gut anwenden könnte ;) )

Grundlage:

- Ich habe eine selbst erstellte GUI mit ein paar Textfeldern, Menüs, Optionsdialogen etc.* und der Programmcode an sich ist sehr klein gehalten.
- und nun soll er eine Pluginschnittstelle bekommen. Diese soll nach und nach (Textverarbeitungs-)Funktionen bereitstellen.
- die zusätzlichen Funktionen werden z.B. bzw. am liebsten in eine DLL gepackt

Probleme:
- soweit ich weiß muss eine DLL im Hauptprogramm mit eingebunden sein, wobei im nachhinein eingefügte Plugins würden dann ja nicht erkannt :confused:
- wie bekommt man im Hauptprogramm den kompletten Funktionsumfang zum laufen? Weil wenn das Hauptprogramm z.B. nicht mehr geupdatet wird, aber immer wieder neue Pluginversionen "installiert" werden, sind die neuen Methoden und klassen des Plugins im Hauptprogramm ja unbekannt.

Ansatz am konkreten beispiel:
- ein Fenster mit 1 Textbox und drei buttons existieren. ein button dient zum beenden des programms. die anderen beiden sollen einmal den textbox-string umkehren (aus hallo wird ollah) und einmal die ToUpper() bzw. ToLower-Methoden aufrufen.
- die Funktionen sind ja schnell gemacht und in eine DLL gepackt.
- aber wie geht es jetzt weiter? :huh:


So, ich hoffe mal hier kann mir wer helfen, wenn ihr mich nicht verstanden habt, dann meckert bidde rum :D


* Meinetwegen sowas wie der Windowseigenen Editor)
 
Ja habe ich schon mal. ;)

Das grundsätzliche Vorgehen ist im Prinzip sehr einfach. Du definierst eine Schnittstelle, im Fall von C++ würde sich eine basisklasse mit Abstrakten Funktionen anbieten und eine globale Funktionen mit der du eine Instanz erzeugen kannst (es gibt auch bessere Mehtoden, aber wenn du damit rum spielst wirst du merken, dass ist die einfachste). Dein Plugin implementiert diese Funktionen dann. ;)

Als nächstes gehst du im Hauptprogramm hin und durchst einen bestimmten Ordner, wohl dein PlugIn Ordner, nach alle infrage kommenden Funktionen und versuchst diese in den Speicher zu laden und die Funktion aufzurufen. Wenn das bei einer Datei gelingt, dann hast du schon dein Plugin geladen und kannst es verwenden. ;)

Natürlich gibt es hier viele Fragen die du dir im Detail noch überlegen musst, aber das ist das Grundprinzip.

Edit:
wie bekommt man im Hauptprogramm den kompletten Funktionsumfang zum laufen? Weil wenn das Hauptprogramm z.B. nicht mehr geupdatet wird, aber immer wieder neue Pluginversionen "installiert" werden, sind die neuen Methoden und klassen des Plugins im Hauptprogramm ja unbekannt.
Nun, in diesen Fall sollte dein Plugin auch noch die GUI erweitern können. ;)
 
Ein wenig Win32-Beispielcode ;)
(Aus dem Kopf, also mit Vorsicht genießen :ugly:)

Code:
class IPlugin
{
public:
    virtual ~IPlugin()
    {
    }

    virtual void MachWasTolles(int param1) = 0;
};

// später im Programm...
#include <windows.h>

typedef IPlugin* (*PluginFactory)(void);

IPlugin* LoadPlugin()
{
    const char *szPluginPath = ...; // Mysteriöser Pfad zum Plugin
    IPlugin *plugin = NULL;

    HINSTANCE hPlugin = LoadLibrary(szPluginPath); // Plugin-DLL laden
    if (hPlugin != NULL)
    {
        PluginFactory pf = (PluginFactory) GetProcAddress(hPlugin, "createPluginInstance"); // Adresse der Factory-Funktion ermitteln
        if (pf != NULL)
        {
            plugin = pf(); // Funktion aufrufen und Plugin erzeugen
        }
        else
        {
            FreeLibrary(hPlugin); // Muss später auch für das Plugin aufgerufen werden, hab ich jetzt hier nicht gemacht.
        }
     }

     return plugin; // Instanz zurückgeben
}

// noch später...
...
IPlugin *plugin = LoadPlugin();
if (plugin != NULL)
    plugin->MachWasTolles(1337);

delete plugin; // wichtig!
...
Noch ein wenig Fehlerprüfungen rein und das sollte so funktionieren ;)

Plugin-DLL:
Code:
class MeinPlugin : public IPlugin
{
public:
    virtual ~MeinPlugin()
    {
    }

    virtual void MachWasTolles(int param1)
    {
        cout << param1 << endl; // sehr sinnvoll ^^
    }
};

IPlugin *createPluginInstance()
{
    return new MeinPlugin;
}
 
Zuletzt bearbeitet:
Sieht nicht schlecht aus, nur das hier gefällt mir gerade nicht.

Code:
HINSTANCE hPlugin = LoadLibrary(szPluginPath); // Plugin-DLL laden
if (hPlugin != NULL)
{
    ...
    FreeLibrary(hPlugin);
}
Laut Beschreibung, funktioniert die FreeLibrary wie folgt

This function decrements the reference count of the loaded DLL module.
When the reference count reaches zero, the module is unmapped from the address space of the calling process and the handle is no longer valid.

Würde doch eigentlich bedeuten, dass die DLL entladen wird und deine Funktionszeiger ungültig werden.
 
Ich brauch den FP ja nur 1x um ne Instanz des Plugins zu laden. In meinem Beispiel gehe ich halt davon aus, dass das Plugin nur 1x geladen bzw. bei jedem Neuladen neu aus der DLL geladen wird.

Edit: Hmm... hab grad nochmal in die Doku gesehen und könnte evtl. wirklich problematisch sein. Das ist jetzt so ein Punkt, wo ich es ausm Kopf nicht mehr kann und es einfach mal ausprobieren müsste ;)

Edit2: Ja, man darf FreeLibrary nicht da aufrufen. D. h. man müsste das Handle irgendwo speichern und beim Entladen des Plugins schließen. Hab das mal in meinem Code geändert.
 
Zuletzt bearbeitet:
Ok, aber was ist mit der vtab des IPlugin Objektes? Die Funktionsadresse müsste doch eigentlich auf einen Ort im Speicherbereich der Dll verweisen und dürfte so nachdem Entladen nicht mehr gültig sein. Oder stehe ich da gerade auf den Schlauch? :what:
 
Ok, aber was ist mit der vtab des IPlugin Objektes? Die Funktionsadresse müsste doch eigentlich auf einen Ort im Speicherbereich der Dll verweisen und dürfte so nachdem Entladen nicht mehr gültig sein. Oder stehe ich da gerade auf den Schlauch? :what:
hab meinen Post schon vorher editiert... meine letzte Pluginschnittstelle hab ich in C# geschrieben, da musste ich mich mit so low-level Zeugs net quälen :ugly:
 
hi,
danke schonmal ihr beiden für das ausführliche Feedback. Werde mir die Posts nach der Fahrstunde mal durchlesen und dann etwaige Fragen konkretisieren ;)

bd

PS: Hatte jetzt auch vor das mit C# zu machen ..... EInwände? :D
 
Nee, natürlich nicht. Vom Prinzip geht es da genauso, nur musst du dich nicht mit LoadLibrary & co. rumschlagen. Ich hatte da mal nen Tutorial zu erstellt, keine Ahnung, ob ich das noch finde...
 
so, also nochmal danke für den Code, sieht (für mich!) jetzt zwar komplizierter aus, aber isses anscheinend nicht :D
verstanden hab ich ihn zwar nicht so ganz, aber das scheint mir auch nicht unbedingt C# zu sein oder? :P

Wäre an deinem Tutorial also auch interessiert ;)

und @Fragile heart: Fragen zum Prinzip bekomm ich jetz nicht mehr hin ... erstmal Bububettchen^^ und dann überleg ich weiter, aber thx schonmal für deine kurzbeschreibung.
Hier mal, was mir spontan eingefallen ist:


Das grundsätzliche Vorgehen ist im Prinzip sehr einfach. Du definierst eine Schnittstelle, im Fall von C++ würde sich eine basisklasse mit Abstrakten Funktionen anbieten und eine globale Funktionen mit der du eine Instanz erzeugen kannst (es gibt auch bessere Mehtoden, aber wenn du damit rum spielst wirst du merken, dass ist die einfachste). Dein Plugin implementiert diese Funktionen dann. ;)

- Instanz erzeugen hätte ich mir irgendwie auch denken können
- was genau sind abstrakte Funktionen? (der Code von bingo88 sieht z.B. ziemlich abstrakt aus :D ) -> wiki ist wie immer nicht so ... einfach zu verstehen^^
- "Dein Plugin implementiert diese Funktionen dann" .... bezieht sich auf die Instanz, die dann diese "abstrakten Funktionen" enthält, die ich aus meiner DLL lade?

Als nächstes gehst du im Hauptprogramm hin und durchst einen bestimmten Ordner, wohl dein PlugIn Ordner, nach alle infrage kommenden Funktionen und versuchst diese in den Speicher zu laden
hmm... also z.B. ein array mit den "paths" der plugins/DLLs bekomme ich hin. aber das mit dem "in den speicher laden" ... wie macht man das (in C#)? :ugly: :what:

und die Funktion aufzurufen. Wenn das bei einer Datei gelingt, dann hast du schon dein Plugin geladen und kannst es verwenden. ;)
Heißt ich müsste die "speicherorte" (?) der gefundenen Funktionen wieder durchlaufen und testen (geht ja jetzt in meinem beispiel noch einfach)


Nun, in diesen Fall sollte dein Plugin auch noch die GUI erweitern können. ;)

Nu ma aber langsam erstmal :D
Da muss ich mich dann ja in der klasse irgendwie auf WinFOrm1 oder so beziehen und dann da ein label erstellen lassen o.ä.

Also wenn ihr jetzt nicht so die Lust habt euch mit so einem Anfängergeschwafel von mir abzugeben, dann is das auch okay :ugly:
Ich schmeiß morgen auch mal google an und poste hier auch gefundene Ergebnisse

Pre-PS: Ihr habt echt Ahnung :hail:
 
Aber das scheint mir auch nicht unbedingt C# zu sein oder? :P
Nein das ist C++, wie das in C# geht kann ich dir im Moment auch nicht sagen, hab ich mich noch nie mit beschäftigt. Aber ich denke Bingo kann uns da aufklären.

- was genau sind abstrakte Funktionen? (der Code von bingo88 sieht z.B. ziemlich abstrakt aus :D ) -> wiki ist wie immer nicht so ... einfach zu verstehen^^
Also eine Abstrakte Funktion ist eine Funktion in einer Basis Klasse die nicht implementiert ist. Damit kannst du beschreiben wie eine Funktion aussehen muss und der Compiler stellt dann für dich sicher, dass du sie in den abgeleiteten Klassen auch implementiert hast, da er sonst kein Objekt erzeugung zu lässt.

Ist in Bingos Programmteil enthalten im IPlugin bei der Funktion MachWasTolles() ;)

"Dein Plugin implementiert diese Funktionen dann" .... bezieht sich auf die Instanz, die dann diese "abstrakten Funktionen" enthält, die ich aus meiner DLL lade?
Ja, genau.

Heißt ich müsste die "speicherorte" (?) der gefundenen Funktionen wieder durchlaufen und testen (geht ja jetzt in meinem beispiel noch einfach)
Ja, auch richtig verstanden. Allerdings suchst du nach Dateien und schaust dann ob die Funktion dadrin exportiert wurde.

Nu ma aber langsam erstmal :D
Da muss ich mich dann ja in der klasse irgendwie auf WinFOrm1 oder so beziehen und dann da ein label erstellen lassen o.ä.
Naja, das ist recht einfach wenn du das Prinzip verstanden hast.
 
Ich hab es doch tatsächlich noch auf meinem Server gefunden :)

Das habe ich 2010 erstellt, ist allerdings auf Englisch (da meine Website auf Englisch ist). Ich hab nochmal reingeschaut, ob es auch funktioniert und mir sind keine groben Schnitzer aufgefallen. Das Design ist vermutlich nicht das beste, sollte aber ausreichend sein, um das Vorgehen zu demonstrieren. Bei Fragen einfach melden.
 

Anhänge

  • PluginTutorial.zip
    33 KB · Aufrufe: 27
Hi,

jop, sowas habe ich gesuch :daumen:
Auch wenn es, wie du schreibst, nicht unbedingt tauglich ist für ernste Projekte, aber es ist schonmal ein Anfang. BIG THX dafür^^

Hab auch mal ein bisschen rumgesucht, aber wie man sowas selber macht hab ich nicht gefunden, nur wie man bestehende Schnittstellen und Frameworks nutzt :daumen2:
 
Du musst im Prinzip "nur" die Interfaces IPlugin und IHost selbst designen und evtl. nen bisschen Verwaltungslogik hinzupacken. Mehr ist das eigentlich nicht mehr.
 
Wenn du das wirklich ernst nutzen willst, denk bitte daran, dass du so eventuell gefährlichen Code ausführst, denke also immer darüber nach wie du die Userdaten schützen kannst. ;)
 
Zurück