Skip to content

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.hnode_buffer.h 等头文件由 Node.js 团队维护。v8.h 由 V8 团队维护,尽管他们与 Node.js 团队密切合作,但 V8 团队是独立的,并且有自己的时间表和优先级。因此,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.hnode_api_types.h 中定义,并提供了跨越 Node.js 主版本边界的前向兼容性保证。该保证可以表述如下:

给定版本的 N-API 将在发布它的 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 在实验性标志下添加,以便让社区有机会在生产环境中对它们进行审查。实验状态意味着,尽管已经采取了预防措施以确保将来不必以 ABI 不兼容的方式修改新 API,但尚未在生产中充分证明其设计和用途的正确性,因此,在最终将其合并到即将发布的 N-API 版本之前,可能会进行 ABI 不兼容的更改。也就是说,实验性 N-API 尚未包含在前向兼容性保证中。