Creating Shared Libraries

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

Using Symbols from Shared Libraries

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

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

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

根据你的目标平台, 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

在共享库的 .pro 文件中, 我们添加:


  DEFINES += MYSHAREDLIB_LIBRARY

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


  #include "mysharedlib_global.h"

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

这确保了库和客户端都能看到正确的宏. 我们也在 Qt 的源代码中使用了这种技术.

Header File Considerations

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

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


  #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++ 编程书籍中描述的 Pointer to implementation 习惯用法可以避免这种情况. 对于具有值语义的类, 请考虑使用 QSharedDataPointer.

Binary Compatibility

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

如果客户端是一个独立的软件包, 并且提供了它需要的所有库, 那么这通常不是问题.

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

维护二进制兼容性, 需要你在更改类时, 设置一些限制. 可以在 KDE - Policies/Binary Compatibility Issues With C++ 中找到很好的解释. 这些问题应该从库设计一开始就考虑到. 我们建议尽可能使用信息隐藏原则和指针实现技术.