динамический — плагин c ++: передать объект через границу (эмулируя его)

Поскольку мы не должны передавать ничего, кроме простой старой структуры данных [1], через границу плагина, я пришел к следующей идее, чтобы передать объект:

  • выставьте все открытые методы в интерфейсе плагина «C», а на стороне приложения оберните плагин в объект. (См. Следующий пример)

Мой вопрос: Есть лучший способ сделать это ?
[РЕДАКТИРОВАТЬ] См. Мое редактирование ниже с, вероятно, лучшим решением с использованием стандартного макета объекта.

Вот игрушечный пример, иллюстрирующий идею:

Я хочу передать Writer через границу:

class Writer{
Writer();
virtual void write(std::string) = 0;
~Writer(){}
};

Однако мы знаем, что этого не следует делать напрямую из-за проблем совместимости.
Идея состоит в том, чтобы представить интерфейс Writer как свободные функции в плагине:

// plugin

extern "C"{
Writer* create_writer(){
return new PluginWriterImpl{};
}

void write(Writer* this_ , const char* str){
this_->write(std::string{str});
}

void delete_writer(Writer* this_){
delete this_;
}
}

и обернуть все эти вызовы функций в объект-оболочку на стороне приложения:

// app

class WriterWrapper : public Writer{
private:
Writer* the_plugin_writer; //object being wrapped
public:
WriterWrapper() : the_plugin_writer{ create_writer() }
{}

void write(std::string str) override{
write(the_plugin_writer,  str.c_str() );
}

~WriterWrapper(){
delete_writer(the_plugin_writer);
}
};

Это приводит к большому количеству функции пересылки. Ничто иное, как POD, не пересекает границу, и приложение не знает о том факте, что текущая реализация Writer происходит из плагина.

[1] Для бинарных проблем совместимости. Для получения дополнительной информации, вы можете увидеть этот связанный вопрос SO: плагин c ++: можно ли передавать полиморфные объекты?


[РЕДАКТИРОВАТЬ] Кажется, что мы могли бы передать стандартную схему через границу. Если так, будет ли такое решение правильным? (И можно ли это упростить?)

Мы хотим передать Writer через границу:

class Writer{
Writer();
virtual void write(std::string) = 0;
~Writer(){}
};

Поэтому мы передадим объект стандартного макета из плагина в приложение и обернем его на стороне приложения.

// plugin.h
struct PluginWriter{
void write(const char* str);
};

// plugin_impl.cpp

#include "plugin.h"
extern "C"{
PluginWriter* create_writer();
void delete_writer(PluginWriter* pw);
}

void PluginWriter::write(const char* str){
// . . .
}

// app
#include "plugin.h"
class WriterWrapper : public Writer{
private:
PluginWriter* the_plugin_writer; //object being wrapped
public:
WriterWrapper() : the_plugin_writer{ create_writer() }
{}

void write(std::string str) override{
the_plugin_writer->write( str.c_str() );
}

~WriterWrapper(){
delete_writer(the_plugin_writer);
}
};

Однако я боюсь, что компоновщик будет жаловаться при компиляции приложения из-за: #include plugin.h

1

Решение

Использование DLL с разными компиляторами (или даже языками) на стороне клиента и на стороне библиотеки требует двоичной совместимости (иначе ABI).

Что бы ни говорилось о стандартной компоновке или POD, стандарт C ++ не гарантирует какой-либо двоичной совместимости между различными компиляторами.
Не существует всеобъемлющего независимого от реализации правила о расположении членов класса, которое могло бы обеспечить это (см. Также этот так ответ
на относительный адрес членов данных).

Конечно, к счастью, на практике многие разные компиляторы используют одну и ту же логику в стандартных объектах компоновки для заполнения и выравнивания, используя
конкретные рекомендации или требования к архитектуре процессора (при условии, что не используется пакет или переключатель компоновки с экзотическим выравниванием). Поэтому использование POD / стандартная верстка относительно безопасен (и как якк
правильно указал: «Если вы доверяете моду, вы должны доверять стандартному макету».)

Так что ваш код может работать. Другие альтернативы, полагающиеся на виртуалы c ++ во избежание проблем с именами, также работают кросс-компилятором
как объяснено в Эта статья. По той же причине: на практике многие компиляторы используют в одной конкретной архитектуре OS + признанный подход
для построения своих таблиц. Но опять же, это наблюдение из практики и не абсолютная гарантия.

Если вы хотите дать кросс-компиляльное соответствие гарантия для вашей библиотеки, то вы должны полагаться только на настоящие гарантии, а не только обычные
практика. На MS-Windows двоичный интерфейс стандарт для объектов COM. Вот это всеобъемлющий C ++ COM учебник. Это может
быть немного старым, но ни у кого другого нет такого количества иллюстраций, чтобы сделать его понятным.

Подход COM, конечно, тяжелее, чем ваш фрагмент. Но это стоимость кросс-компилятора и даже гарантии соответствия между языками, которую он предлагает.

1

Другие решения