【问题标题】:Loading Stages from external code从外部代码加载阶段
【发布时间】:2015-03-06 00:07:55
【问题描述】:

我编写了一个基于管道和过滤器的架构。为避免混淆,过滤器在我的代码中称为“阶段”。这是基本的想法:

我希望其他开发人员能够实现他们自己的 Stage 类,然后我可以将其添加到运行时已经存在的 Stage 列表中。

我已经阅读了一段时间,似乎它们是动态代码加载的限制。我当前的 Stage 课程如下所示:

class Stage
{
public:
    void Process();
    const uint16_t InputCount();
    const uint16_t OutputCount();

    void SetOutputPipe(size_t idx, Pipe<AmplitudeVal> *outputPipe);
    void SetInputPipe(size_t idx, Pipe<AmplitudeVal> *inputPipe);

protected:
    Stage(const uint16_t inputCount, const uint16_t outputCount);
    virtual void init() {}; 
    virtual bool work() = 0;
    virtual void finish() {};

protected:
    const uint16_t mInputCount;
    const uint16_t mOutputCount;
    std::vector< Pipe<AmplitudeVal>* > mInputs;
    std::vector< Pipe<AmplitudeVal>* > mOutputs;
};

AmplitudeVal 只是 float 的别名。此类仅包含对其连接的管道(mInputs 和 mOutputs)的引用,它不处理任何算法活动。为了便于外部开发人员使用,我想尽可能少地公开。现在这个类只依赖于管道头和一些基本的配置文件。大多数处理加载 DLL 的示例都提出了一个只有纯虚函数且几乎没有任何成员变量的类。我不确定我应该怎么做。

【问题讨论】:

  • 如果我理解得很好,您的目标是将 Stage 作为 DLL 提供,用户可以导入它并进一步派生它。这是你的意思吗?
  • 不完全清楚你的问题是什么。但我要指出在“数据管道”中越来越流行使用something like ZeroMQ。与“插件”相比,这使您可以以一种方式定义事物,即它们可以是进程内、进程外,甚至可以让这些片段以不同的语言在不同的网络位置运行。如果您发现您正在设计一个管道接口并期望不同的作者将事物连接在一起,那么可能值得一看。 (但我不知道你的目的。)
  • @Christophe 是的,确实。

标签: c++ inheritance dll


【解决方案1】:

我了解您希望在 DLL 中拥有阶段,并让用户用户在您的 DLL 上派生他们的工作。

场景 1:消费者和 DLL 使用相同的编译器和相同的标准库构建

如果消费者使用相同的编译器、兼容的编译选项,并且双方都使用相同的共享标准库(默认使用 MSVC),那么您的解决方案应该按原样工作

(见related SO question。)

场景 2:消费者和 DLL 使用相同的编译器但不同的库构建

如果一方使用不同的标准库或其链接选项(例如,如果您为 DLL 使用静态链接库,为使用者使用共享库),那么您必须确保所有对象始终在同一侧(因为 DLL 和应用程序将各自使用自己的分配函数和不同的内存池)。

这将非常困难,因为:

  • 数据继承
  • 虚拟析构函数
  • 标准容器的存储管理(在 DLL 和使用者中会有所不同,尽管源代码可能给人的印象是相同的)

朝着正确方向迈出的第一步是将所有数据隔离到私有部分,并确保通过 getter 和 setter 进行干净的访问。好在这种设计对于继承来说是一种不错的设计方式,所以即使不需要也值得使用。

场景 3:不同的编译器或不兼容的编译选项

如果你使用不同的编译器,或者不兼容的编译选项,那么真正的问题就开始了:

  • 您不能假设双方对内存布局有相同的理解。因此成员的读/写可能发生在不同的位置;一团糟!这就是为什么这么多 DLL 类没有数据成员的原因。许多人还使用PIMPL idiom 来隐藏私有内存布局。但是在这种继承情况下,PIMPL 与使用私有数据非常相似(*this 将是指向私有实现的隐式指针)
  • 编译器/链接器使用“损坏”的函数名称。不同的编译器可能使用不同的修饰,并且不会理解彼此的符号定义(即客户端不会找到SetOutputPipe(),尽管它在那里)。这就是为什么大多数 DLL 将所有成员函数都设置为 virtual 的原因:这些函数是通过 vtable 中的偏移量调用的,幸运的是,它在实践中跨编译器使用相同的布局。
  • 最后,不同的编译器可以使用不同的调用约定。但我认为在成熟平台上的实践中,这不应该是一个重大风险

this answer 的一个关于具有不同编译器(也没有继承)的 DLL 的问题中,我提供了一些可能与这种混合场景相关的额外解释和参考。

再次使用 private 成员数据而不是 protected 将使您处于更安全的一边。使用 extern "C" 链接说明符公开 getter/setter(无论是受保护的还是公共的)将避免非虚函数的名称修改问题。

最后一句话:

在库中公开类时,应特别注意设计类,将数据设为私有。无论您处于何种情况,这种良好做法都值得多加思考。

【讨论】:

  • @Pat 对不起,已经很晚了!我已经编辑并完成了信息。
猜你喜欢
  • 2020-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-17
相关资源
最近更新 更多