What to do if the compiler does not support VMT zero offset interfaces.

What is it for



It is often necessary to write plugins for programs. But because of the binary incompatibility of classes, these plugins will have to be written in the same language as the main program. In C ++, it is customary to place the virtual function table first in the class. If you use certain rules (do not use multiple inheritance of interfaces) and use abstract classes, you can achieve the possibility of running plugins compiled under different C ++ compilers.



In this article, I will show how to use a plugin written using the Free Pascal Compiler compiler in a program with C ++ (only a general idea, not a real plugin).



What is VMT



Virtual method table (English virtual method table, VMT) - coordinating table or vtable - a mechanism used in programming languages ​​to support dynamic matching (or late binding method).



C ++ standards do not clearly define how dynamic coordination should be implemented, but compilers often use some variations of the same basic model.



Usually the compiler creates a separate vtable for each class. After creating an object, a pointer to this vtable, called a virtual table pointer or vpointer (also sometimes called vptr or vfptr), is added as a hidden member of this object (and often as the first member). The compiler also generates "hidden" code in the constructor of each class to initialize its objects' vpointer'ov addresses of the corresponding vtable.

(Paragraphs are taken from Wikipedia.)



Implementation.



First we need to create a wrapper around the code on 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 ();
      
      





Where iplugin is a plugin interface. And thisPascal is a pointer to a binary version of the interface implementation class in pascal.



And the wrapper code itself: 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; }
      
      





As you can see, the code calls functions from the library on Pascal and passes them a pointer to the implementation of the plug-in on Pascal that was previously saved when the class was created. getNewPlugin is called to create an instance of the plugin class in the main program.



Now let's talk about the implementation of the plug-in on 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.
      
      





This file implements almost the same interface on Pascal and wraps around the functions of the plug-in to enable the export of functions to the library. Note that all interface implementation functions contain a pointer to a class as the first parameter. This parameter is passed implicitly for the class methods by the first parameter and is needed to refer to the methods and fields of the class. The getNewPlugin function is used to get a pointer in the C ++ class. Pascal code connects as a library.



PS: I forgot to mention that the code on pascal should be / preferably wrapped in try / catch as in this method of plagging, exceptions should not be passed. The plugin must handle its exceptions and issue the results either immediately or as a separate function in the form of simple types.



PS2: Added a comment about the free function and changed its code. I will not add any changes here, so as to maintain compliance with the comments. And he added a comment about using the getNewPlugin function and deleting an object in third-party applications. Although the person who knows about the interfaces, it will be clear.



β†’ Sample sources



All Articles