C++ 插件:跨边界传递对象(模拟它)

2023-12-01

由于我们不应该跨插件边界传递除普通旧数据结构[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


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

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

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

所以你的代码可能会工作。其他替代方案,依靠 C++ 虚拟来避免名称修改问题,似乎也可以使用交叉编译器 如中所解释的本文。出于同样的原因:实际上,许多编译器在一个特定的操作系统+架构上使用一种公认的方法 用于构建他们的 vtable。但同样,这是从实践中观察到的并非绝对保证.

如果你想提供交叉编译一致性保证对于您的图书馆,那么您应该只依赖真正的保证,而不仅仅是通常的保证 实践。在 MS-Windows 上,二进制接口standard对于对象来说是COM。这里有一个全面的C++ COM 教程。它可能 有点老了,但没有其他人有这么多插图可以理解。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 插件:跨边界传递对象(模拟它) 的相关文章

随机推荐