À quoi ça sert?
Il est souvent nécessaire d'écrire des plugins pour les programmes. Mais en raison de l'incompatibilité binaire des classes, ces plugins devront être écrits dans le même langage que le programme principal. En C ++, il est habituel de placer la table des fonctions virtuelles en premier dans la classe. Si vous utilisez certaines règles (n'utilisez pas l'héritage multiple d'interfaces) et utilisez des classes abstraites, vous pouvez obtenir la possibilité d'exécuter des plugins compilés sous différents compilateurs C ++.
Dans cet article, je vais montrer comment utiliser un plugin écrit en utilisant le compilateur Free Pascal dans un programme C ++ (seulement une idée générale, pas un vrai plugin).
Qu'est-ce que VMT?
La table de méthode virtuelle (VMT) est une table de coordination ou vtable est un mécanisme utilisé dans les langages de programmation pour prendre en charge la correspondance dynamique (ou liaison tardive).
Les normes C ++ ne définissent pas clairement comment la coordination dynamique doit être implémentée, mais les compilateurs utilisent souvent certaines variantes du même modèle de base.
En règle générale, le compilateur crée une table virtuelle distincte pour chaque classe. Après avoir créé un objet, un pointeur vers cette table virtuelle, appelé pointeur de table virtuelle ou vpointer (parfois aussi appelé vptr ou vfptr), est ajouté en tant que membre masqué de l'objet (et souvent en tant que premier membre). Le compilateur génère également du code "caché" dans le constructeur de chaque classe pour initialiser vpointer'ov ses objets avec les adresses de la vtable correspondante.
(Paragraphes tirés de Wikipedia.)
Mise en œuvre.
Nous devons d'abord créer un wrapper autour du code en pascal.
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 ();
Où IPlugin est l'interface du plugin. Et thisPascal est un pointeur vers une version binaire de la classe d'implémentation d'interface en pascal.
Et le code wrapper lui-même:
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; }
Comme vous pouvez le voir, le code appelle des fonctions de la bibliothèque en pascal et leur transmet un pointeur vers l'implémentation du plugin en pascal qui a été précédemment enregistré lors de la création de la classe. getNewPlugin est appelé pour instancier la classe de plugin dans le programme principal.
Parlons maintenant de l'implémentation du plugin en Pascal.
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.
Ce fichier implémente presque la même interface en pascal et encapsule les fonctions du plug-in pour exporter des fonctions vers la bibliothèque. Notez que toutes les fonctions d'implémentation d'interface contiennent le pointeur vers la classe comme premier paramètre. Ce paramètre est transmis implicitement pour les méthodes de classe comme premier paramètre et est nécessaire pour accéder aux méthodes et aux champs de la classe. La fonction getNewPlugin est utilisée pour obtenir un pointeur dans une classe C ++. Le code Pascal est connecté en tant que bibliothèque.
PS: J'ai oublié de mentionner que le code en pascal devrait / devrait de préférence être enveloppé dans try / catch car les exceptions ne doivent pas être levées dans cette méthode de plugin. Le plugin doit gérer ses exceptions et renvoyer les résultats immédiatement ou en tant que fonction distincte sous forme de types simples.
PS2: Ajout d'un commentaire sur la fonction gratuite et modification de son code. Je n'ajouterai aucune modification ici pour maintenir la conformité avec les commentaires. Et il a ajouté un commentaire sur l'utilisation de la fonction getNewPlugin et la suppression de l'objet dans les applications tierces. Bien qu'une personne connaissant les interfaces, cela sera déjà clair.
→
Exemples de sources