c ++插件:跨越边界传递对象(模仿它)

时间:2015-02-13 13:18:13

标签: c++ dynamic dll plugins boundary

由于我们不应该通过插件边界传递除Plain Old Data-structure [1]以外的任何内容,因此我提出了以下想法以传递对象:

  • 公开插件中的所有公共方法" C"接口,在应用程序端,将插件包装在一个对象中。 (参见以下示例)

我的问题是:有更好的方法吗? [编辑]请参阅下面的编辑,使用标准布局对象提供更好的解决方案。

这是一个说明这个想法的玩具示例:

我想传递一个跨越边界的作家:

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++ plugin : Is it ok to pass polymorphic objects?


[编辑]似乎我们可以跨越边界传递标准布局。如果是这样,这样的解决方案是否正确? (它可以简化吗?)

我们希望跨越边界传递一个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 个答案:

答案 0 :(得分:1)

在客户端和库侧使用具有不同编译器(甚至语言)的DLL需要二进制兼容性(又名ABI)。

无论标准布局或POD是什么,C ++标准都不保证不同编译器之间的任何二进制兼容性。 关于可以确保这一点的类成员布局没有全面的实现独立规则(另请参阅this SO answer 关于数据成员的相对地址)。

当然,幸运的是,在实践中许多不同的编译器在标准布局对象中使用相同的逻辑进行填充和对齐,使用 CPU体系结构的特定最佳实践或要求(只要不使用打包或外来对齐编译器开关)。因此,使用POD /标准布局是相对安全的(和Yakk一样 正确地指出:“如果你信任pod,你必须信任标准布局。”

所以你的代码可能有效。其他替代方案,依赖于c ++虚拟来避免名称错位问题,似乎也适用于交叉编译器 正如this article中所述。出于同样的原因:在实践中,许多编译器在一个特定的OS +体系结构上使用了公认的方法 用于构建他们的vtable。但同样,这是从实践中观察到的,而不是绝对的保证

如果您想为您的图书馆提供交叉编码一致性保证,那么您应该只依靠真正的保证而不仅仅是通常的 实践。在MS-Windows上,对象的二进制接口标准COM。这是一个全面的C++ COM tutorial。它可能 有点旧,但没有其他人有这么多插图让它变得可以理解。

COM方法当然比你的代码片段重。但这是交叉编译器的成本,甚至是它提供的跨语言合规性。