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).

In C ++ standards, there is no clear definition of 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' vpointerov 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"classIPlugin 
{public:
  virtualvoid APIENTRY free()= 0;
  virtualvoid APIENTRY print()= 0;
};
classPlugin :public IPlugin
{
public:
  virtualvoid APIENTRY free();
  virtualvoid APIENTRY print();
  Plugin ();
  virtual ~Plugin ();
private:
  void* thisPascal;
};
extern"C"IPlugin* APIENTRY getNewPlugin();

Where iplugin is the 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);
  deletethis;
}
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 sends them a pointer to the implementation of the plug-in on Pascal that was previously saved when creating the class. 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 = interfaceprocedure _release();cdecl;
    procedureprint();cdecl;
  end;
  TPlugin = class (TInterfacedObject, IPlugin)
  publicprocedure _release();cdecl;
    procedureprint();cdecl;
    constructorCreate();destructorFree();end;
  PPlugin = ^TPlugin;
procedureTPlugin._release();cdecl; 
begin
  Free;
end;
procedureTPlugin.print();cdecl; 
begin
  writeln ('Hello World');
end;
procedure _release(this: PPlugin);cdecl;
begin
  this^._release ();
end;
procedureprint(this: PPlugin);cdecl; 
begin
  this^.print ();
end;
constructorTPlugin.Create();begininherited;
  writeln ('TPlugin.Create');
end;
destructorTPlugin.Free();begin
  writeln ('TPlugin.Free');
end;
functiongetNewPlugin(): 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';
beginend.

In this file, almost the same interface is implemented in Pascal and a wrapper is made 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’m not going to add any changes here to keep the correspondence with the comments. And he added a comment about using the getNewPlugin function and deleting an object in third-party applications. Although a person who knows about the interfaces, it will be clear anyway.

Sample sources

Also popular now: