Wofür ist es?
Es ist oft notwendig, Plugins für Programme zu schreiben. Aufgrund der binären Inkompatibilität der Klassen müssen diese Plugins jedoch in derselben Sprache wie das Hauptprogramm geschrieben werden. In C ++ ist es üblich, die Tabelle der virtuellen Funktionen zuerst in der Klasse zu platzieren. Wenn Sie bestimmte Regeln verwenden (keine Mehrfachvererbung von Schnittstellen verwenden) und abstrakte Klassen verwenden, können Sie Plugins ausführen, die unter verschiedenen C ++ - Compilern kompiliert wurden.
In diesem Artikel werde ich zeigen, wie ein Plugin verwendet wird, das mit dem Free Pascal Compiler in einem C ++ - Programm geschrieben wurde (nur eine allgemeine Idee, kein echtes Plugin).
Was ist VMT?
Die virtuelle Methodentabelle (VMT) ist eine koordinierende Tabelle, oder vtable ist ein Mechanismus, der in Programmiersprachen zur Unterstützung des dynamischen Abgleichs (oder der späten Bindung) verwendet wird.
C ++ - Standards definieren nicht klar, wie dynamische Koordination implementiert werden soll, aber Compiler verwenden häufig einige Variationen desselben Basismodells.
In der Regel erstellt der Compiler für jede Klasse eine separate vtable. Nach dem Erstellen eines Objekts wird ein Zeiger auf diese vtable, der als virtueller Tabellenzeiger oder vpointer (manchmal auch als vptr oder vfptr bezeichnet) bezeichnet wird, als verstecktes Element des Objekts (und häufig als erstes Element) hinzugefügt. Der Compiler generiert auch "versteckten" Code im Konstruktor jeder Klasse, um vpointer'ov seiner Objekte mit den Adressen der entsprechenden vtable zu initialisieren.
(Absätze aus Wikipedia.)
Implementierung.
Zuerst müssen wir einen Wrapper um den Code in Pascal erstellen.
plugin.hpp#pragma once #include "ApiEntry.hpp" class IPlugin { public: virtual void APIENTRY free () = 0; virtual void APIENTRY print () = 0; }; class Plugin : public IPlugin { public: virtual void APIENTRY free (); virtual void APIENTRY print (); Plugin (); virtual ~Plugin (); private: void* thisPascal; }; extern "C" IPlugin* APIENTRY getNewPlugin ();
Wobei IPlugin die Plugin-Schnittstelle ist. Und thisPascal ist ein Zeiger auf eine Binärversion der Schnittstellenimplementierungsklasse in Pascal.
Und der Wrapper-Code selbst:
plugin.cpp #include "plugin.hpp" #include "pascalunit.hpp" #include <iostream> void APIENTRY Plugin::free () { IPlugin_release (thisPascal); delete this; } void APIENTRY Plugin::print () { IPlugin_print (thisPascal); } Plugin::Plugin () { std::cout << "Plugin::Plugin" << std::endl; thisPascal = IPlugin_getNewPlugin (); } Plugin::~Plugin () { std::cout << "Plugin::~Plugin" << std::endl; } extern "C" IPlugin* APIENTRY getNewPlugin () { Plugin* plugin = new Plugin (); return plugin; }
Wie Sie sehen können, ruft der Code Funktionen aus der Bibliothek in Pascal auf und übergibt ihnen einen Zeiger auf die Implementierung des Plugins in Pascal, das zuvor beim Erstellen der Klasse gespeichert wurde. getNewPlugin wird aufgerufen, um die Plugin-Klasse im Hauptprogramm zu instanziieren.
Lassen Sie uns nun über die Implementierung des Plugins in Pascal sprechen.
library pascalunit; {$MODE OBJFPC} uses ctypes; type IPlugin = interface procedure _release (); cdecl; procedure print (); cdecl; end; TPlugin = class (TInterfacedObject, IPlugin) public procedure _release (); cdecl; procedure print (); cdecl; constructor Create (); destructor Free (); end; PPlugin = ^TPlugin; procedure TPlugin._release (); cdecl; begin Free; end; procedure TPlugin.print (); cdecl; begin writeln ('Hello World'); end; procedure _release (this: PPlugin); cdecl; begin this^._release (); end; procedure print (this: PPlugin); cdecl; begin this^.print (); end; constructor TPlugin.Create (); begin inherited; writeln ('TPlugin.Create'); end; destructor TPlugin.Free (); begin writeln ('TPlugin.Free'); end; function getNewPlugin (): PPlugin; cdecl; var plugin: PPlugin; begin New (plugin); plugin^ := TPlugin.Create (); result := plugin; end; exports getNewPlugin name 'IPlugin_getNewPlugin', print name 'IPlugin_print', _release name 'IPlugin_release'; begin end.
Diese Datei implementiert fast dieselbe Schnittstelle in Pascal und umschließt die Plug-In-Funktionen, um Funktionen in die Bibliothek zu exportieren. Beachten Sie, dass alle Schnittstellenimplementierungsfunktionen den Zeiger auf die Klasse als ersten Parameter enthalten. Dieser Parameter wird implizit für Klassenmethoden als erster Parameter übergeben und wird für den Zugriff auf die Methoden und Felder der Klasse benötigt. Die Funktion getNewPlugin wird verwendet, um einen Zeiger in einer C ++ - Klasse abzurufen. Pascal-Code ist als Bibliothek verbunden.
PS: Ich habe vergessen zu erwähnen, dass der Code in Pascal vorzugsweise in try / catch eingeschlossen werden sollte / sollte, da bei dieser Plugin-Methode keine Ausnahmen ausgelöst werden sollten. Das Plugin sollte seine Ausnahmen behandeln und die Ergebnisse entweder sofort oder als separate Funktion in Form einfacher Typen zurückgeben.
PS2: Es wurde ein Kommentar zur freien Funktion hinzugefügt und der Code geändert. Ich werde hier keine Änderungen hinzufügen, um die Einhaltung der Kommentare zu gewährleisten. Außerdem fügte er einen Kommentar zur Verwendung der Funktion getNewPlugin und zum Löschen des Objekts in Anwendungen von Drittanbietern hinzu. Obwohl eine Person, die über die Schnittstellen Bescheid weiß, wird dies bereits klar sein.
→
Beispielquellen