创建共享库

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

使用共享库中的符号

包含在共享库中的符号(如函数、变量或类)是用于由客户端(例如应用或其他库)使用的,必须以特殊方式标记。这些符号称为公共符号,是导出或公开可见的。

其余符号不应对外可见。在大多数平台中,编译器将默认隐藏它们。在某些平台上,需要特殊编译选项来隐藏这些符号。

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

根据目标平台,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 Creator 和 Qt VS Tools 中的库向导会提供这些设置的基础骨架。

头文件注意事项

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

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

#include <footronics/device.h>

class MyDevice {
private:
    FOOTRONICS_DEVICE_HANDLE handle;
};

使用聚合或多重继承使用 Qt 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++。这些问题应该在库设计初期就被考虑。我们建议尽可能使用信息隐藏原则和实现指针技术。

© 2024 Qt公司有限公司。本文档中的文档贡献属于其相应所有者的版权。本提供的文档根据由自由软件基金会发布的GNU自由文档许可证版本1.3的条款进行许可。Qt及其相应标志是芬兰和/或全世界Qt公司有限公司的商标。所有其他商标均为其相应所有者的财产。