Как быть если компилятор не поддерживает интерфейсы с нулевым смещением VMT

в 10:37, , рубрики: c++, FPC, linux, Pascal, Программирование, Софт

Для чего это надо

Часто бывает необходимо писать плагины для программ. Но из-за бинарной несовместимости классов эти плагины придётся писать на том же языке, что и основная программа. В С++ принято располагать таблицу виртуальных функций первой в классе. Если пользоваться определенными правилами (не использовать множественное наследование интерфейсов) и использовать абстрактные классы-то можно добиться возможности запуска плагинов, скомпилированных под разными компиляторами С++.

В этой статье я покажу как использовать плагин написанный с использованием компилятора Free Pascal Compiler в программе на с++ (только общая идея, а не реальный плагин).

Что такое VMT

Таблица виртуальных методов (англ. virtual method table, VMT) — координирующая таблица или vtable — механизм, используемый в языках программирования для поддержки динамического соответствия (или метода позднего связывания).

В стандартах C++ нет четкого определения как должна реализовываться динамическая координация, но компиляторы зачастую используют некоторые вариации одной и той же базовой модели.

Обычно компилятор создает отдельную vtable для каждого класса. После создания объекта указатель на эту vtable, называемый виртуальный табличный указатель или vpointer (также иногда называется vptr или vfptr), добавляется как скрытый член данного объекта (а зачастую как первый член). Компилятор также генерирует «скрытый» код в конструкторе каждого класса для инициализации vpointer'ов его объектов адресами соответствующей vtable.
(Абзацы взяты из википедии.)

Реализация.

Для начала нам надо создать обертку вокруг кода на паскале.

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 ();

Где IPlugin это интерфейс плагина. А thisPascal это указатель на бинарный вариант класса реализации интерфейса в паскале.

И сам код обёртки: 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;
}

Как видно код вызывает функции из библиотеки на паскале и передает им заранее сохраненный при создании класса указатель на реализацию плагина на паскале. getNewPlugin вызывается для создания экземпляра класса плагина в основной программе.

Теперь поговорим о реализации плагина на паскале.

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.

В данном файле реализуется почти этот же самый интерфейс на паскале и делается обертка вокруг функций плагина для возможности экспорта функций в библиотеку. Заметьте все функции реализации интерфейса содержат первым параметром указатель на класс. Этот параметр передаётся неявно для методов класса первым параметром и нужен для обращения к методам и полям класса. Функция getNewPlugin используется для получения указателя в С++ классе. Код на паскале подключается как библиотека.

PS: Забыл упомянуть что код на паскале должен быть/желательно обернут в try/catch так как в данном способе плагирования исключения проходить не должны. Плагин должен обрабатывать свои исключения и выдавать результаты либо сразу либо отдельной функцией в виде простых типов.

Исходники примера

Автор: darkprof

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js