c++插件:通过边界传递对象(模拟它)



由于我们不应该在插件边界上传递除Plain Old Data结构[1]之外的任何其他内容,为了传递对象,我想出了以下想法:

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

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

下面是一个玩具示例,说明了这个想法:

我想通过一个作家的边界:

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++插件:传递多态对象可以吗?


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

我们想通过一个作家的边界:

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++标准都不能保证不同编译器之间的二进制可通信性。关于类成员的布局,没有一个全面的独立于实现的规则可以确保这一点(另请参阅SO的答案关于数据成员的相对地址)。

当然,幸运的是,在实践中许多不同的编译器在标准布局对象中使用相同的逻辑进行填充和对齐,使用CPU架构的具体最佳实践或要求(只要不使用封装或奇异对齐编译器开关)。因此,使用POD/标准布局是相对安全的(并且作为Yakk正确地指出:"如果你信任pod,你就必须信任标准布局。"

因此,您的代码可能会工作。其他替代方案,依靠c++虚拟机来避免名称篡改问题,似乎也可以跨编译器工作如本文所述。出于同样的原因:在实践中,许多编译器在一个特定的OS+体系结构上使用一种公认的方法用于构建他们的vtables。但同样,这是从实践中观察到的,并不是绝对的保证

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

COM方法当然比您的代码段更重。但这就是它所提供的跨编译器甚至跨语言合规性保证的成本。

最新更新