ABI 稳定性
简介
应用程序二进制接口 (ABI) 是一种程序调用函数并使用来自其他已编译程序的数据结构的方式。 它是应用程序编程接口 (API) 的已编译版本。 换句话说,描述类、函数、数据结构、枚举和常量(使应用程序能够执行所需任务)的头文件,通过编译对应于一组地址以及预期的参数值和内存结构大小和布局,ABI 的提供者使用这些值进行编译。
使用 ABI 的应用程序必须进行编译,以便可用的地址、预期的参数值以及内存结构大小和布局与 ABI 提供者使用的编译值一致。 这通常通过针对 ABI 提供者提供的头文件进行编译来完成。
由于 ABI 的提供者和 ABI 的用户可能在不同的时间使用不同版本的编译器进行编译,因此确保 ABI 兼容性的部分责任在于编译器。 编译器(可能由不同的供应商提供)的不同版本都必须从具有特定内容的头文件生成相同的 ABI,并且必须为使用 ABI 的应用程序生成代码,该代码根据从头文件中的描述产生的 ABI 的约定访问给定头文件中描述的 API。 现代编译器在不破坏其编译的应用程序的 ABI 兼容性方面具有相当不错的记录。
确保 ABI 兼容性的剩余责任在于维护头文件的团队,这些头文件提供 API,这些 API 在编译后会产生要保持稳定的 ABI。 可以对头文件进行更改,但必须密切跟踪更改的性质,以确保编译后 ABI 不会以某种方式更改,从而使 ABI 的现有用户与新版本不兼容。
Node.js 中的 ABI 稳定性
Node.js 提供了由几个独立团队维护的头文件。 例如,诸如 node.h
和 node_buffer.h
之类的头文件由 Node.js 团队维护。 v8.h
由 V8 团队维护,尽管与 Node.js 团队密切合作,但它是独立的,并且有自己的时间表和优先级。 因此,Node.js 团队仅对项目中提供的头文件中引入的更改具有部分控制权。 因此,Node.js 项目采用了语义版本控制。 这确保了项目提供的 API 将为在一个主要版本中发布的所有 Node.js 次要版本和补丁版本提供稳定的 ABI。 实际上,这意味着 Node.js 项目已承诺确保针对给定的 Node.js 主要版本编译的 Node.js 原生插件在由针对其编译的主要版本中的任何 Node.js 次要版本或补丁版本加载时都将成功加载。
N-API
人们一直希望为 Node.js 配备一个 API,该 API 生成一个在多个 Node.js 主要版本中保持稳定的 ABI。 创建此类 API 的动机如下
-
自早期以来,JavaScript 语言一直与自身兼容,而执行 JavaScript 代码的引擎的 ABI 会随着 Node.js 的每个主要版本而变化。 这意味着完全用 JavaScript 编写的由 Node.js 包组成的应用程序无需重新编译、重新安装或重新部署,因为新的 Node.js 主要版本会被放入此类应用程序运行的生产环境中。 相比之下,如果应用程序依赖于包含原生插件的包,则每当将新的 Node.js 主要版本引入生产环境时,都必须重新编译、重新安装和重新部署该应用程序。 包含原生插件的 Node.js 包和完全用 JavaScript 编写的 Node.js 包之间的这种差异增加了依赖于原生插件的生产系统的维护负担。
-
其他项目已经开始生成 JavaScript 接口,这些接口本质上是 Node.js 的替代实现。 由于这些项目通常基于与 V8 不同的 JavaScript 引擎构建,因此它们的原生插件必然采用不同的结构并使用不同的 API。 然而,跨 Node.js JavaScript API 的不同实现为原生插件使用单个 API 将允许这些项目利用已围绕 Node.js 积累的 JavaScript 包的生态系统。
-
Node.js 将来可能包含不同的 JavaScript 引擎。 这意味着,从外部来看,所有 Node.js 接口都将保持不变,但 V8 头文件将不存在。 如果 Node.js 没有首先提供一种与 JavaScript 引擎无关的 API 并被原生插件采用,那么这样的步骤将导致 Node.js 生态系统(尤其是原生插件)的中断。
为此,Node.js 在 8.6.0 版本中引入了 N-API,并在 Node.js 8.12.0 版本中将其标记为项目的稳定组件。 该 API 在头文件 node_api.h
和 node_api_types.h
中定义,并提供跨越 Node.js 主要版本边界的向前兼容性保证。 该保证可以表述如下
给定版本的 N-API n 将在发布它的 Node.js 主要版本中可用,并且在 Node.js 的所有后续版本中都可用,包括后续的主要版本。
原生插件作者可以通过确保该插件仅使用 node_api.h
中定义的 API 以及 node_api_types.h
中定义的数据结构和常量来利用 N-API 的向前兼容性保证。 通过这样做,作者可以通过向生产用户表明,将原生插件添加到他们的项目中不会增加他们的应用程序的维护负担,而只是添加一个纯 JavaScript 编写的包。
N-API 是版本化的,因为会不时添加新的 API。 与语义版本控制不同,N-API 版本控制是累积的。 也就是说,N-API 的每个版本都传达与 semver 系统中的次要版本相同的含义,这意味着对 N-API 所做的所有更改都将是向后兼容的。 此外,新的 N-API 在实验标志下添加,以便让社区有机会在生产环境中对其进行审查。 实验状态意味着,尽管已采取措施确保新的 API 在未来不必以 ABI 不兼容的方式进行修改,但尚未在生产中得到充分证明,以确保其设计正确且有用,因此,在最终将其纳入即将发布的 N-API 版本之前,可能会发生 ABI 不兼容的更改。 也就是说,实验性的 N-API 尚未受到向前兼容性保证的保护。