建立并测试最小 Node-API C++ 扩展
OpenJS Foundation and Node.js contributors;node-addon-examples collaborators;中文编译与整理:未完纪。
来源:Your First Project。核对日期:2026-10-08。

最小原生扩展并不需要先接入复杂的 C++ 库。让 JavaScript 调用一个 C++ 函数,并得到字符串 world,已经能够串起注册、编译、加载与测试这条完整链路。Node.js 官方这组入门指南使用的是 node-addon-api:它在 Node-API 的 C 接口之上提供 C++ 包装。
Node-API 的 ABI 稳定性有助于扩展跨受支持的 Node.js 版本工作,但它不会自动解决外部 C++ 库、操作系统、CPU 架构或编译选项之间的二进制兼容性。本文只静态核对完整项目,不在当前环境编译或加载原生代码。
准备知识与项目结构
先具备基本 JavaScript、C/C++ 和命令行知识,明确 C++ 部分要完成什么、JavaScript 部分如何使用它。构建还依赖对应平台的编译工具链;不要把安装一个 npm 包理解为已经具备系统编译器。官方建议使用受支持的 Active LTS 或 Maintenance LTS Node.js。
hello-world/
binding.gyp
package.json
package-lock.json # npm install 后生成或更新
src/hello_world.cc
lib/binding.js
test/test_binding.js
build/ # 编译后生成
node_modules/ # 安装后生成
src 存放实现,binding.gyp 描述构建方式,build 是生成产物,lib 集中处理二进制加载,test 验证暴露给 JavaScript 的行为。锁文件与实际仓库提交应一起记录,方便以后复现。
取得最小示例
原文从官方示例库复制指定目录。以下命令保留原文流程,面向具备相应命令的 shell。它没有固定示例仓库提交;请先读下方来源锁定提示,审查固定提交后再在一次性工作目录中执行,不要对生产应用直接运行。
git clone https://github.com/nodejs/node-addon-examples.git
cp -r node-addon-examples/src/1-getting-started/a-first-project/node-addon-api hello-world
cd hello-world
npm install
npm test
来源锁定:上面的 git clone 命令未指定提交,会取得仓库当前默认分支的内容。原命令是对官方教程的忠实转述,并不是锁定依赖的复现方案;本文没有固定或核验某个仓库提交 SHA。原示例仓库当前目录里没有锁文件;上方项目树中的 package-lock.json 表示后续生成/更新的文件,不代表克隆时已经存在。若要复现或运行,请先自行选择并检查具体提交,再复制项目。检出步骤可写作 git -C node-addon-examples checkout <commit-SHA>;尖括号是占位符,本文没有提供该值,不能原样执行。若希望先检查解析后的依赖版本而不运行安装脚本,可在隔离副本中使用 npm install --package-lock-only --ignore-scripts 生成初始锁文件:按当前 npm CLI v11 文档,该选项只更新锁文件、不下载依赖包,且阻止npm运行package.json脚本;其他npm版本应对照其自身文档。它仍需registry解析元数据,故先审查来源并考虑网络访问。检查并保存锁文件后,才在无生产凭证的隔离环境中运行会触发生命周期脚本和原生构建的 npm ci 或官方流程。此锁定步骤是编者补充,不是本轮实测。详情见 npm install 文档与npm 配置说明。
安装风险:npm install 在该项目里不只是下载文件,还会触发原生构建;依赖安装也可能执行生命周期脚本。应先确认代码来源、固定已审查的提交并检查锁文件,再在不包含生产凭证或个人资料的隔离环境中构建。这里展示流程,不代表这些命令已经执行成功。
手动创建项目也是可行路径:建立目录、运行 npm init -y、添加 node-addon-api,再建立以下源文件与构建配置。但手动路径仍需补齐脚本、构建设置和平台条件,不能只装包装库就期待扩展可加载。
C++:实现函数,再注册导出
#include <napi.h>
using namespace Napi;
Napi::String Method(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
return Napi::String::New(env, "world");
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "HelloWorld"),
Napi::Function::New(env, Method));
return exports;
}
NODE_API_MODULE(addon, Init)
CallbackInfo 让函数取得当前 JavaScript 环境,也能访问调用参数;这个示例只使用 info.Env()。Method 在该环境内建立 Napi::String,返回固定字符串。Init 把 JavaScript 名称 HelloWorld 绑定到这个函数,最后的宏负责在模块加载时调用初始化逻辑。
测试虽然传入 "hello",当前 C++ 实现并没有读它。因此,“传什么都返回 world”不是输入校验充分,而是这个最小例子根本没有输入处理。后续增加参数时,应先检查数量、类型和范围,再进行转换。
binding.gyp:目标名是连接点
{
'targets': [{
'target_name': 'hello-world-native',
'sources': ['src/hello_world.cc'],
'include_dirs': ["<!@(node -p \"require('node-addon-api').include\")"],
'dependencies': ["<!(node -p \"require('node-addon-api').gyp\")"],
'cflags!': ['-fno-exceptions'],
'cflags_cc!': ['-fno-exceptions'],
'xcode_settings': {
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'CLANG_CXX_LIBRARY': 'libc++',
'MACOSX_DEPLOYMENT_TARGET': '10.7'
},
'msvs_settings': {
'VCCLCompilerTool': {'ExceptionHandling': 1}
}
}]
}
这里保留了仓库实际配置,包含异常处理选项和历史 macOS 部署目标。它不是已经验证适用于当前各平台的通用配置。通用结构文档里的 my_addon 只是另一段模板;本示例真实目标是 hello-world-native。GYP 中的 node -p 会在构建时执行,目的是取得包装库的包含目录和依赖信息;这也是为什么必须审查构建文件。
项目的 package.json 把入口设为 lib/binding.js,声明 gypfile: true,依赖范围为 node-addon-api: ^8.1.0,测试脚本为 node --napi-modules ./test/test_binding.js。private: true 降低误发布风险,但不代替代码安全审查。模板里的 Your name goes here 不是真实作者;仓库的 MIT 许可也应随复制材料保留。
JavaScript 加载层与断言
官方加载层省略了二进制扩展名。下例显式写出 .node,便于看出这是原生二进制;这是本文的说明性改写,导出方式保持一致。
const addon = require("../build/Release/hello-world-native.node");
module.exports = addon.HelloWorld;
注意 module.exports 是函数本身,不是整个 addon 对象。调用方应直接调用返回的函数,而不是再访问一次 .HelloWorld。
const HelloWorld = require("../lib/binding.js");
const assert = require("assert");
assert(HelloWorld, "The expected function is undefined");
function testBasic() {
const result = HelloWorld("hello");
assert.strictEqual(result, "world", "Unexpected value returned");
}
assert.doesNotThrow(testBasic, undefined, "testBasic threw an expection");
console.log("Tests passed- everything looks OK!");
以上是原测试的逻辑与消息。最后一行只是源代码中的日志,不是本文的运行结果。第一个断言仅检查真值;可将其增强为 assert.strictEqual(typeof HelloWorld, "function"),让失败原因更明确。这是建议改动,未执行验证。
这组断言能检查模块是否可加载、调用是否抛错以及返回值是否符合最小预期;它不覆盖内存安全、并发、参数转换、错误路径或外部库 ABI。遇到失败,应分别确认系统构建条件、构建目标名称、产物目录和加载层导出形状,避免一开始就怀疑 Node-API 注册机制。
继续扩展前,先观察调用链
原文建议在调试器里逐步执行测试,观察 JavaScript 对象;再尝试让测试直接加载编译产物,与经 binding.js 加载比较。最后才把 C++ 函数改为读取 JavaScript 参数。这样每次只增加一个变量,更容易判断问题发生在加载、注册还是数据转换阶段。
来源、版本与检查说明
核对日期2026-10-08。正文及示例仓库 main 均为可变来源;示例 package.json 使用 node-addon-api ^8.1.0。应固定仓库提交、依赖锁文件及受支持的 Node.js 版本,不把此版本范围当作可复现锁定。原 binding.gyp 的 macOS 10.7 部署目标属于历史配置,不能当作当前工具链支持承诺。
Node.js Learn 页面页脚标注 OpenJS Foundation and Node.js contributors;其内容仓库 LICENSE 为 MIT,版权为 Node.js contributors。示例仓库 LICENSE.md 也采用 MIT,版权为 Node.js node-addon-examples collaborators。下方分别完整附上这两个来源对应的 MIT 许可文本,保留各自的版权声明、许可条件与免责声明。示例 package.json 的 ISC 与 Your name goes here 为模板元数据,不作为实际作者署名。
本文仅对源代码和配置做静态检查,未进行安装、运行或性能测试。文中的期望结果属于原文说明或逻辑推导,不能当作本次实测结果。
- https://nodejs.org/learn/node-api/getting-started/your-first-project
- https://nodejs.org/learn/node-api/getting-started/project-structure
- https://nodejs.org/learn/node-api/getting-started/prerequisites
- https://raw.githubusercontent.com/nodejs/node-addon-examples/main/src/1-getting-started/a-first-project/node-addon-api/src/hello_world.cc
- https://raw.githubusercontent.com/nodejs/node-addon-examples/main/src/1-getting-started/a-first-project/node-addon-api/lib/binding.js
- https://raw.githubusercontent.com/nodejs/node-addon-examples/main/src/1-getting-started/a-first-project/node-addon-api/test/test_binding.js
- https://raw.githubusercontent.com/nodejs/node-addon-examples/main/src/1-getting-started/a-first-project/node-addon-api/package.json
- https://raw.githubusercontent.com/nodejs/node-addon-examples/main/src/1-getting-started/a-first-project/node-addon-api/binding.gyp
- https://github.com/nodejs/node-addon-examples/blob/main/LICENSE.md
- https://github.com/nodejs/learn/blob/main/LICENSE
Node.js Learn 内容仓库 MIT 许可全文
MIT License Copyright Node.js contributors. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
node-addon-examples 仓库 MIT 许可全文
The MIT License (MIT) Copyright (c) 2017 Node.js node-addon-examples collaborators Collaborators: https://github.com/nodejs/node-addon-examples/graphs/contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.











暂无评论内容