23

我有一些编写 C 库的经验,但在编写此类库时,我从未阅读过任何描述良好实践的正式文档。我的问题主要涉及两个主题:

  1. 如何保持二进制兼容性?(我听说过 pImpl 成语,d-pointer)
  2. 如何设计保持向后兼容的接口?

从我的研究中可以看出,关于二进制兼容性的主要内容是,我可以使用 pImpl 习惯用法使库二进制兼容,但即使在使用 pImpl 时,更改结构/添加新数据成员等也会影响它的二进制兼容性。另外,有没有办法在不破坏二进制兼容性的情况下向库中添加新方法/函数?我假设添加这些东西会改变库的大小和布局,从而破坏兼容性。

有没有检查二进制兼容性的工具?

我已经阅读了这些文章。还有其他我可以阅读的文档吗?

http://en.wikipedia.org/wiki/Opaque_pointer

http://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++

此外,是否有文章在设计库接口的背景下描述了内存的所有权问题。一般约定是什么?谁拥有内存,多长时间,谁负责释放内存等?

4

4 回答 4

21

主要的兼容性问题是:

  • 函数签名
  • 库和调用者访问的任何数据的格式
  • 调用者访问的库中的全局变量
  • 由于标头中的宏/内联函数而最终在调用者中的库代码
  • #define/enum共享标头中的常量值

所以我能给出的最好的指南清单是:

  • 切勿更改任何公共接口的签名(返回/参数类型)。如果您需要扩展接口,请添加一个带有更多参数的新函数(想想dupvsdup2waitvs waitpid)。
  • 尽可能使用指向完全封装的不透明数据对象的指针,甚至不要在公共标头中公开此类结构的定义(使其不完整struct类型)。
  • 当您确实想共享一个结构时,请安排调用者从不声明该结构类型的变量,而是调用库中的显式分配/释放函数。切勿更改现有成员的类型或删除现有成员;相反,只在结构的末尾添加新成员。
  • 不要从库中公开全局变量,期间。除非您了解“复制重定位”,否则最好不要问为什么。只是不要这样做。
  • 不要将内联函数或包含代码的宏放在库的公共头文件中,除非使用将永久保存的文档化、公开的接口。如果他们戳中不透明数据对象的内部结构,那么当您决定更改内部结构时,它们就会引起问题。
  • 不要重新编号现有的#define/enum常量。仅添加具有以前未使用值的新常量。

如果您遵循这些准则,我认为您至少已覆盖 95%。

于 2011-08-27T21:12:10.627 回答
5

有没有检查二进制兼容性的工具?

ABI Compliance Checker - 一种用于检查共享 C/C++ 库 (DSO) 的向后二进制兼容性的工具。

还有其他我可以阅读的文档吗?

请参阅有关共享库的二进制兼容性的一长串文章

如何设计保持向后兼容的接口?

保留/填充字段的使用是保持C库兼容性的一般方法。但也有很多其他的。

另外,有没有办法在不破坏二进制兼容性的情况下向库中添加新方法/函数?我假设添加这些东西会改变库的大小和布局,从而破坏兼容性。

添加的C函数不会破坏 DSO 在 Linux 和 Mac 上的向后二进制兼容性。在 Windows 和 Symbian 上也是如此,但您应该只将新函数添加到 .DEF 文件的末尾。但是,前向兼容性总是被添加的功能破坏。

添加的C++方法当且仅当它们是虚拟纯虚拟的时才会破坏二进制兼容性,因为v-table的布局可能会改变。但是您的问题似乎仅与C库有关。

于 2011-08-28T07:51:33.883 回答
4

在文档方面, Ulrich Drepper 的How To Write Shared Libraries是必读的。

于 2011-08-29T05:28:28.727 回答
3

有几件事要补充到 R. 所说的内容:

因为您似乎在谈论 C ABI 而不是 C++ ABI:

即使在使用 pImpl 时,更改结构/添加新数据成员等也会影响它的二进制兼容性

使用 pIMpl 时不应该是这种情况 - 如果对象的外部用户只有一个不透明的指针/对象句柄,并且只有库处理结构的内部,那么根据定义处理内部的东西的结构与之兼容。

有没有办法在不破坏二进制兼容性的情况下向库中添加新方法/函数?我假设添加这些东西会改变库的大小和布局,从而破坏兼容性。

添加新函数或更改共享库的大小或布局不会破坏二进制兼容性。由于在共享库加载到进程中之前不会绑定到函数地址,因此更改目标函数的位置不是问题。

于 2011-08-27T22:40:00.807 回答