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