c ++插件:跨边界传递对象(模拟它)
c++ plugin : pass object across boundary (emulating it)
由于我们不应该跨插件边界传递普通旧数据结构[1]以外的任何其他内容,因此我想出了以下想法来传递对象:
- 暴露插件"C"接口中的所有public方法,在应用端,将插件包裹在一个对象中。 (见下面的例子)
我的问题是:有更好的方法吗?
[编辑] 请参阅下面的编辑,使用标准布局对象可能是更好的解决方案。
这是一个说明这个想法的玩具示例:
我想跨界传一个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 问题:
[编辑] 看来我们可以通过标准布局跨越边界。如果是这样,这样的解决方案是否正确? (可以简化吗?)
我们想跨界传递一个 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
在客户端和库端使用具有不同编译器(甚至语言)的 DLL 需要二进制兼容性(又名 ABI)。
无论关于标准布局或 POD,C++ 标准不保证不同编译器之间的任何二进制兼容性。
class 成员的布局没有全面的独立实施规则可以确保这一点(另见
在数据成员的相对地址上)。
当然,幸运的是,在实践中许多不同的编译器在标准布局对象中使用相同的逻辑来填充和对齐,使用
CPU 体系结构的特定最佳实践或要求(只要不使用打包或奇异对齐编译器开关)。因此使用POD/standard布局相对安全(和Yakk一样
正确指出:"if you trust pod, you must trust standard layout.")
因此您的代码可能有效。其他替代方案,依靠 c++ 虚拟来避免名称混淆问题,似乎也适用于交叉编译器
如 this article 中所述。出于同样的原因:在实践中,许多编译器在一个特定的 OS+ 架构上使用一种公认的方法
用于构建他们的 vtable。但同样,这是实践中的观察结果,并非绝对保证。
如果你想为你的库提供交叉编译一致性保证,那么你应该只依赖真正的保证,而不仅仅是通常的保证
实践。在 MS-Windows 上,对象的二进制接口 标准 是 COM. Here is a comprenhensive C++ COM tutorial。它可能
有点老了,但没有其他人有这么多的插图可以理解。
COM 方法当然比您的代码段更重。但这是交叉编译器的成本,甚至是它提供的跨语言合规性保证。
由于我们不应该跨插件边界传递普通旧数据结构[1]以外的任何其他内容,因此我想出了以下想法来传递对象:
- 暴露插件"C"接口中的所有public方法,在应用端,将插件包裹在一个对象中。 (见下面的例子)
我的问题是:有更好的方法吗? [编辑] 请参阅下面的编辑,使用标准布局对象可能是更好的解决方案。
这是一个说明这个想法的玩具示例:
我想跨界传一个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 问题:
[编辑] 看来我们可以通过标准布局跨越边界。如果是这样,这样的解决方案是否正确? (可以简化吗?)
我们想跨界传递一个 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
在客户端和库端使用具有不同编译器(甚至语言)的 DLL 需要二进制兼容性(又名 ABI)。
无论关于标准布局或 POD,C++ 标准不保证不同编译器之间的任何二进制兼容性。
class 成员的布局没有全面的独立实施规则可以确保这一点(另见
当然,幸运的是,在实践中许多不同的编译器在标准布局对象中使用相同的逻辑来填充和对齐,使用 CPU 体系结构的特定最佳实践或要求(只要不使用打包或奇异对齐编译器开关)。因此使用POD/standard布局相对安全(和Yakk一样 正确指出:"if you trust pod, you must trust standard layout.")
因此您的代码可能有效。其他替代方案,依靠 c++ 虚拟来避免名称混淆问题,似乎也适用于交叉编译器 如 this article 中所述。出于同样的原因:在实践中,许多编译器在一个特定的 OS+ 架构上使用一种公认的方法 用于构建他们的 vtable。但同样,这是实践中的观察结果,并非绝对保证。
如果你想为你的库提供交叉编译一致性保证,那么你应该只依赖真正的保证,而不仅仅是通常的保证 实践。在 MS-Windows 上,对象的二进制接口 标准 是 COM. Here is a comprenhensive C++ COM tutorial。它可能 有点老了,但没有其他人有这么多的插图可以理解。
COM 方法当然比您的代码段更重。但这是交叉编译器的成本,甚至是它提供的跨语言合规性保证。