创建共享库#

如何创建共享库。

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

使用共享库中的符号#

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

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

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

根据目标平台,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 Designer创建表单时也可能出现类似情况

#include "ui_widget.h"

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

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

可以通过使用各种C++编程书籍中描述的指针到实现习语来避免这种情况。对于值语义类,请考虑使用QSharedDataPointer。

二进制兼容性#

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

如果客户端是一个包含所有所需库的独立软件包,这通常不是问题。

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

维持二进制兼容性对您可以进行的类变更有限制。相关的良好说明可以在KDE - 政策/与C++的二进制兼容性问题找到。这些问题应该在库设计之初就考虑。我们建议尽可能使用信息隐藏原则和指向实现技术。