创建共享库

如何创建共享库。

以下部分列出了在创建共享库时应考虑的一些事项。

使用共享库中的符号

符号 - 函数、变量或类 - 包含在共享库中,旨在供客户端(如应用程序或其他库)使用,必须以特殊方式标记。这些符号被称为公共符号,它们被导出或公开可见。

剩余的符号不应该从外部可见。在大多数平台上,编译器会默认隐藏它们。在某些平台上,需要特殊的编译器选项来隐藏这些符号。

在编译共享库时,必须标记为导出。要从客户端使用共享库,某些平台可能还需要特殊的导入声明。

根据您的目标平台,Qt 提供了包含必要定义的特殊宏:

  • 在编译共享库时,必须在符号的声明中添加Q_DECL_EXPORT。

  • 在编译使用共享库的客户端时,必须在符号声明中添加 Q_DECL_IMPORT。

现在,我们需要确保调用正确的宏——无论我们是编译共享库本身,还是仅编译使用共享库的客户端。通常,这可以通过添加一个特殊的头文件来解决。

让我们假设我们想要创建一个名为mysharedlib的共享库。这个库的特殊头文件,mysharedlib_global.h,看起来像这样:

#include <QtCore/QtGlobal>

#if defined(MYSHAREDLIB_LIBRARY)
#  define MYSHAREDLIB_EXPORT Q_DECL_EXPORT
#else
#  define MYSHAREDLIB_EXPORT Q_DECL_IMPORT
#endif

在库的每个头文件中,我们指定以下内容:

#include "mysharedlib_global.h"

MYSHAREDLIB_EXPORT void foo();
class MYSHAREDLIB_EXPORT MyClass...

然后我们需要确保在构建库本身时为编译器定义了MYSHAREDLIB_LIBRARY。这是在库的构建系统中完成的。如果我们使用CMake,我们增强共享库目标:

target_compile_definitions(mysharedlib PRIVATE MYSHAREDLIB_LIBRARY)

如果我们使用 qmake ,我们添加

DEFINES += MYSHAREDLIB_LIBRARY

到共享库的.pro文件。

注意

Qt CreatorQt VS Tools 中的库向导为您提供了一个骨架,为您设置这些内容。

头文件注意事项

通常,客户端只会包含共享库的公共头文件。这些库在部署时可能安装在不同的位置。因此,排除构建共享库时使用的其他内部头文件非常重要。

例如,库可能提供一个类,该类封装了一个硬件设备,并包含由某个第三方库提供的该设备的句柄:

#include <footronics/device.h>

class MyDevice {
private:
    FOOTRONICS_DEVICE_HANDLE handle;
};

在使用聚合或多重继承时,由Qt Widgets Designer创建的表单也会出现类似的情况:

#include "ui_widget.h"

class MyWidget : public QWidget {
private:
    Ui::MyWidget m_ui;
};

部署库时,不应依赖内部头文件 footronics/device.hui_widget.h

这可以通过利用各种C++编程书籍中描述的指向实现的指针惯用法来避免。对于具有值语义的类,考虑使用QSharedDataPointer。

二进制兼容性

对于加载共享库的客户端来说,为了正常工作,所使用的类的内存布局必须与用于编译客户端的库版本的内存布局完全匹配。换句话说,客户端在运行时找到的库必须与编译时使用的版本二进制兼容

如果客户端是一个自包含的软件包,并且包含了所有需要的库,这通常不是问题。

然而,如果客户端应用程序依赖于属于不同安装包或操作系统的共享库,那么我们需要考虑共享库的版本控制方案,并决定在哪个级别上保持二进制兼容性。例如,相同主版本号的Qt库保证是二进制兼容的。

维护二进制兼容性对您对类所做的更改施加了一些限制。可以在KDE - Policies/Binary Compatibility Issues With C++找到一个很好的解释。这些问题应该从库设计的一开始就考虑。我们建议尽可能使用信息隐藏原则和指向实现的指针技术。