【问题标题】:Is it a good idea to do variant handling using shared library?使用共享库进行变体处理是个好主意吗?
【发布时间】:2020-02-11 15:48:39
【问题描述】:

我已经使用 C++ 抽象类定义了一个接口。我想为我的项目的不同变体多次实例化它(变体可以是硬件变体或操作系统变体)。我的目标是为每个变体创建共享库,并为给定项目使用正确的库,而无需重新编译任何东西。

我为了检索给定变体所需的实例,我正在使用公共函数。在每个变体的 cpp 文件中,我定义了一个函数来返回该变体的实例。在主代码中,我正在对此函数进行外部声明。我打算使用返回的对象调用接口的方法。

// Interface.hpp
class Interface {
    public:
        virtual void routine() = 0;
};

// variantA.cpp
class variantA : public Interface {
    public:
        void routine() {
            printf( "We are in variantA\n" );
        }
} variant;

Interface& getVariant() {
    return variant;
}

// variantB.cpp
class variantB : public Interface {
    public:
        void routine() {
            printf( "We are in variantB\n" );
        }
} variant;

Interface& getVariant() {
    return variant;
}

// main.cpp
#include "Interface.hpp"

extern Interface& getVariant();

int main() {
    Interface& interface = getVariant();
    interface.routine();
}
// build.sh
g++ -c -fPIC -O0 variantA.cpp
g++ -c -fPIC -O0 variantB.cpp
g++ variantA.o -shared -o libvariantA.so
g++ variantB.o -shared -o libvariantB.so

ln -s libvariantA.so libvariant.so

g++ -L. -lvariant main.cpp -o main

主要问题:我的方法在理论上是否正确?这样做是个好主意吗?或者,这种方法有什么明显的缺陷吗?如果是这样,谁能给我推荐一个更好的方法?

第二个问题:我可能通过一些试验自己弄清楚。但我仍然发布此内容,因为这可能会使该方法无法使用。问题是编译错误:

In function `main':
main.cpp:(.text+0x9): undefined reference to `getVariant()'
collect2: error: ld returned 1 exit status

【问题讨论】:

标签: c++ linux


【解决方案1】:

不。这似乎是一个坏主意,而不是其他人如何使用 C++ 共享库。如果它们是不同的硬件或操作系统变体,则库可能必须是不同的格式。为每个变体编译一个具有相同 API 的库是常规方法。

【讨论】:

  • 您能详细说明您的答案吗?我认为我不太了解我的方法存在的问题以及您提出的解决方案是什么。
  • 简单来说,每个操作系统或硬件平台可能需要不同的格式库,因此只有一个库是行不通的。您需要为每个平台编译相同的代码。
【解决方案2】:

首先不要在使用前声明,它属于接口头,这正是头的好处。

问题

但我担心我们在这里得到了XY problem

我想为我的不同变体多次实例化它 项目(变体可以是硬件变体或操作系统变体)。我的目标是 为每个变体创建共享库并将正确的用于 一个给定的项目,而无需重新编译任何东西。

这是不可能的:看到您通常不能为硬件或操作系统变体 a 编译 libvarianta,而为硬件或操作系统变体 b 编译 libvariantb 并在一个同时链接到 a 和 b 的程序中一起使用它们。

问题在于,您通常每次都必须重新编译是有原因的:不同的硬件和不同的操作系统通常需要不同的机器代码。甚至您所做的printf 调用也会为不同的编译器或操作系统转换为不同的机器代码。这就是 C++ 标准和参考对运行时和实现的意义所在。

此外,您通常甚至无法调用该函数,因为在使用共享链接时,您遇到了ABI(不是 API)的问题,这也意味着即使对于相同架构的相同 Microsoft 编译器版本,您也必须编译一次用于调试,一次用于发布版本 (unless further precaution are followed)。

大多数库甚至使用预处理器在不同平台上编译不同的高级代码。

解决方案

因此,您仍然必须为您的库和库的使用者编译所有需要的不同版本,这需要复杂的逻辑才能知道要加载哪个二进制文件。换句话说:你必须找到一个共享库二进制文件,如果你只是简单地将它们编译在一起,你就会得到它。因此,简单地静态链接它并使用一个 API 会更容易,不需要抽象类或接口,至少在运行时方面是这样。您可能需要在编译时做不同的事情,因为您可能没有可移植的方式来做事情。然后您可以使用预处理器宏或compile time polymorphism。这符合 John3136 给您的建议。

回答

所以,共享链接不会解决问题,即,据我了解您的问题,实现您的尝试。它只会产生更复杂的问题。共享链接不是可移植的,甚至不是纯标准 C++ 的东西。然而,不同的操作系统如何尝试实现 ABI 稳定性有一些技巧(如 Windows 上的 COM 对象),但如果您的代码需要针对不同的硬件或运行时,这仍然无法解决问题。

【讨论】:

  • 注意,OP 构建了两个 .so,但仅与“活动”变体链接。
  • 感谢@Superlokkus 的详细解释。这对我理解问题有很大帮助。清楚地了解硬件或操作系统兼容性问题。我了解了 ABI,这对我来说是新事物。我同意我的解决方案在这种情况下不起作用。以后,我会尽量避免XY问题。
  • 只是为了让我的理解完整,如果您不介意,我想问一个后续问题。尽管我的解决方案不适用于硬件或操作系统变体,但我觉得它可以适用于同一目标的软件功能变体。例如我想将我的产品包装为具有 X+Y 功能和另一个具有 X+Z 功能的变体。在这种情况下,我可以为这两个变体编译我的主要二进制文件和共享库。在发货时,我可以将 (main+libvariantA.so) 用于 customerA,将 (main+libvariantB.so) 用于 customerB。你认为这可行吗?
  • 是的,这确实是共享库实践中的主要用例之一,因为您不想为客户提供包含他们没有付费的功能的二进制文件,并且可以通过简单地更改跳转到你的主二进制程序集(破解(。这也是你经常看到 PIMPL 模式(en.cppreference.com/w/cpp/language/pimpl)的原因。Boost DLL 有一个很好的站点用于这种插件方法,还可以让你以可移植的方式加载你的 dyn 库:boost.org/doc/libs/1_71_0/doc/html/boost_dll/…
  • 感谢@Superlokkus。这完成了我的理解。当然它回答了我的问题:-)
猜你喜欢
  • 2013-04-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-13
  • 2012-02-08
  • 1970-01-01
  • 1970-01-01
  • 2011-06-01
  • 1970-01-01
相关资源
最近更新 更多