C++ 插件 - Node-api

node中除了内建原生模块和链接模块外,还有一种使用原生的语言编写的模块,node的c++扩展模块。node的原生扩展模块是一种动态扩展node的功能,因为它使用的是原生语言区编写,所以对比用户模块,它执行的速度更快。更重要的是,它可以执行系统调用。这是它最重要的一点。 插件是用 C++ 编写的动态链接共享对象。 require() 函数可以将插件加载为普通的 nodejs 模块。插件提供了 js 与 c/c++ 库之间的接口。 实现插件的三种选择:

  • node-api
  • nan(node.js 原生抽象)
  • 直接使用内部 v8、libuv 和 nodejs 库 除非需要直接访问 Node-API 未暴露的功能,否则请使用 Node-API。 插件特点 上下文感知插件 在某些环境中,可能需要在多个上下文中多次加载 Node.js 插件。例如,Electron 运行时在单个进程中运行多个 Node.js 实例。 当 Node.js 嵌入到另一个应用中时,应用的主线程也可能在应用的生命周期中多次构造和销毁 Node.js 环境 每个实例都有自己的 require() 缓存,因此当通过 require() 加载时,每个实例都需要原生插件才能正确运行。这意味着插件必须支持多个初始化。 为了从多个 Node.js 环境(例如主线程和工作线程)加载,插件需要:
  • 成为 Node-API 插件,
  • 或如上所述使用 NODE_MODULE_INIT() 声明为上下文感知 为了支持 Worker 线程,插件需要在这样的线程退出时清理它们可能分配的任何资源。 使用 require() 加载插件 已编译的插件二进制文件的文件扩展名是 .node(与 .dll 或 .so 相反)。require() 函数用于查找具有 .node 文件扩展名的文件并将它们初始化为动态链接库。 调用 require() 时,通常可以省略 .node 扩展名,Node.js 仍会找到并初始化插件。但是,有一个注意事项,Node.js 将首先尝试定位和加载碰巧共享相同基本名称的模块或 JavaScript 文件。例如,如果在与二进制 addon.node 相同的目录中有一个文件 addon.js,那么 require('addon') 将优先于 addon.js 文件并加载它。 Node-api 在没有出现node-api的时候。我们编写的node原生扩展使用的是v8的提供的api。随着javaScript标准的变化和v8版本升级不可避免地对api进行破坏式升级。虽然v8对外提供的api只是对v8内部的api的一个代理,但v8也不保证对外提供的api的稳定性。这样就会导致一个问题。当我们使用不同版本的v8进行原生扩展的开发,随着node使用v8版本的升级。会出现一种情况就是编写的原生扩展需要为不同的node版本提供不同的版本。因为他们使用的v8的ABI(应用程序二进制接口)不一致。为了解决这个问题。出现了node-api。 Node-API 是用于构建原生插件的 API。它独立于底层 JavaScript runtime(例如 V8),并作为 Node.js 自身的一部分进行维护。 node-api 是 Node.js 的各个版本中保持稳定的应用二进制接口 (ABI)。它旨在将插件与底层 JavaScript 引擎中的更改隔离开来,并允许已编译的模块无需重新编译即可在更高版本的 Node.js 上运行。 插件是使用标头为 C++ 插件 的部分中概述的相同方法/工具构建/打包的。唯一的区别是插件使用的 API 集不是 V8 或 Node.js 的原生抽象 API,而是使用 Node-API 中可用的函数。

来源整理自:vue3js.cn 面试官系列