基于Node.js c++ Addons机制对接动态链接库,以实现现有Node.js接口外的底层访问
2026-07 修订:Electron 原生模块的核心风险不是“能不能写 C++”,而是 ABI(Application Binary Interface)、Node.js/Electron 版本、平台工具链、签名和分发。新代码优先使用 N-API /
node-addon-api,尽量避免直接绑定 V8/NAN;已有历史包才考虑nan、ffi-napi、ref-*这类兼容成本较高的方案。“Chromium和Node.js大部分的代码都是用c++实现的,所以理所当然地也可以用C++为它们开发插件。” ——— <给electron做c开发的那些坑>
node-gyp
GYP is short for ‘Generate Your Projects’,顾名思义,GYP工具用于生成在相应平台上的项目,如在windows平台上生成Visual Studio解决方案(.sln), Mac下则是XCode项目配置以及Scons工具。
node-gyp is a cross-platform command-line tool written in Node.js for compiling native addon modules for Node.js. It contains a fork of the gyp project that was previously used by the Chromium team, extended to support the development of Node.js native addons.
node-gyp 用于编译nodejs原生addon模块的,跨平台的命令行工具,fork了Chromium team使用的gyp项目,该项目用户开发Node.js C++插件
1
2 npm install -g node-gyp
npm install --global --production windows-build-tools
2026-07 修订:windows-build-tools属于旧时代经验,不建议作为新项目默认方案。Windows 上优先安装 Visual Studio Build Tools 的 “Desktop development with C++” 工作负载,并使用当前 node-gyp 文档要求的 Python 和 MSVC 工具链。
Caution!安装过程中出现 issue#147 Hangs on Python is already installed, not installing again. 原因是VisualStudio有进程占用了相关工具链,结束VS进程并重新安装windows-build-tools即可
当前版本node-gyp使用 vs2017 build tools而不支持2019版本,下文中记述了workaround
2026-07 修订:这条只适用于当年的 node-gyp/Node/Electron 组合。现在应按当前 Node.js 与 node-gyp 支持矩阵选择 VS Build Tools 版本,通常不再需要 VS2017 shim。遇到问题先确认
node -v、npm -v、node-gyp -v、Electron 版本、Python 路径和 MSVC 工具链。
python依赖
支持v2.7, v3.5, v3.6, or v3.7 v3.8 (QQs已实践2.7,3.8)
2026-07 修订:Python 2.7 已不应进入新项目工具链。现代 node-gyp 使用 Python 3,具体最低版本以当前 node-gyp 官方文档为准。
如果安装了多个版本 应使用下述命令注明python路径1
node-gyp <command> --python /path/to/executable/python
抑或修改npm调用设置1
npm config set python /path/to/executable/python
否则会调用默认版本(环境变量Path中指向的版本)
binding.gyp1
2
3
4
5
6
7
8{
"targets": [
{
"target_name": "hello",
"sources": [ "src/hello.cc" ]
}
]
}
关于gyp配置,但凡要比hello world走的远,都需要阅读下列文档
- “Going Native” a nodeschool.io tutorial
- “Hello World” node addon example
- gyp user documentation
- gyp input format reference
- “binding.gyp” files out in the wild wiki page
执行配置和构建为当前平台生成相应的项目构建文件。 这会在 build/ 目录下生成一个 Makefile 文件(Unix 平台)或 vcxproj 文件(Windows平台)。1
node-gyp configure
生成编译后的 *.node 的文件。 它会被放进 build/Release/ 目录。1
node-gyp build
Electron-rebuild
开源社区提供,基于 node-gyp 进一步封装的工具,用于 Electron 原生模块的编译,不需要 node-gyp 的一些额外配置(头文件下载地址、版本映射等)。
2026-07 修订:包名已经从旧的
electron-rebuild迁移到 scoped 包@electron/rebuild。很多历史文章仍写electron-rebuild,新项目优先使用@electron/rebuild。
1 | npm i -D @electron/rebuild |
package.json1
2
3
4"scripts": {
...
"rebuild": "electron-rebuild -f -w yourmodule"
}
2026-07 新增:如果使用 Electron Forge,也可以通过 Forge 的相关插件/生命周期集成 rebuild。关键是保证 native addon 针对 Electron 的 Node ABI 编译,而不是针对系统 Node.js 编译。
node-pre-gyp
node-pre-gyp makes it easy to publish and install Node.js C++ addons from binaries.
node-pre-gyp stands between npm and node-gyp and offers a cross-platform method of binary deployment.
Features :
- 使用node-pre-gyp命令行工具安装依赖二进制C++模块(install your package’s C++ module from a binary)
- 使用node-pre-gyp模块动态引入js模块 require(‘node-pre-gyp’).find
- 其他开发命令如 test, publish (详见—help)
配置package.json
- 依赖 + node-pre-gyp
- 开发依赖 + aws-sdk
- install命令脚本 node-pre-gyp install —fallback-to-build
- 声明需要的二进制模块这就是为什么英国人的项目使用npm install —build-from-source来打包addon的原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14"dependencies" : {
"node-pre-gyp": "0.6.x"
},
"devDependencies": {
"aws-sdk": "2.x"
}
"scripts": {
"install": "node-pre-gyp install --fallback-to-build"
},
"binary": {
"module_name": "your_module",
"module_path": "./lib/binding/",
"host": "https://your_module.s3-us-west-1.amazonaws.com"
}
导学列表:
- 编译一个hello QQs C++ Addon的实践
- 可以交互的manager插件
为什么CSActivation可以用 npm install —build-from-source 打包插件
编译方法
nan: C++-based abstraction between Node and direct V8 APIs.历史项目常见,但新项目不优先。- napi: C-based API guaranteeing ABI stability across different node versions as well as JavaScript engines.
- node-addon-api: header-only C++ wrapper classes which simplify the use of the C-based N-API.
2026-07 修订:新项目优先
node-addon-api/ N-API,因为它能降低 Node.js/Electron 版本升级时的 ABI 重编译压力。直接使用 V8 或 NAN 更容易被运行时升级影响。
文件结构
hello.cc
binding.gyp
package.json
编译后的二进制插件的文件扩展名是 .node
所有的 Node.js 插件必须导出一个如下模式的初始化函数:1
2void Initialize(Local<Object> exports);
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
使用 node-gyp 构建插件时,使用宏 NODE_GYP_MODULE_NAME 作为 NODE_MODULE() 的第一个参数将确保会将最终二进制文件的名称传给 NODE_MODULE()。
1 | node-gyp configure |
为当前平台生成相应的项目构建文件。 这会在 build/ 目录下生成一个 Makefile 文件(Unix 平台)或 vcxproj 文件(Windows平台)。1
node-gyp build
生成编译后的 *.node 的文件。 它会被放进 build/Release/ 目录。
关于VS2019 找不到C++ build tool的问题
私以为比较好的workaround是创建一个用2017替代2019的shim,详见node-gyp issue#1663经实践可行
2026-07 修订:该 workaround 仅保留作历史排障记录。新环境应优先安装当前 VS Build Tools、确认
npm config get msvs_version、Python 路径和 node-gyp 版本,不建议为新项目伪装旧版 Visual Studio。
关于rebuild ffi error的问题
需求:使用Crypt32加密(编码/解码)
1 | npm i ffi-napi ref ref-struct |
后面两个包是映射C语言类型的接口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46const fs = require("fs");
const ref = require("ref");
const ffi = require("ffi-napi");
const Struct = require("ref-struct");
const DATA_BLOB = Struct({
cbData: ref.types.uint32,
pbData: ref.refType(ref.types.byte)
});
const PDATA_BLOB = new ref.refType(DATA_BLOB);
const Crypto = new ffi.Library('Crypt32', {
"CryptUnprotectData": ['bool', [PDATA_BLOB, 'string', 'string', 'void *', 'string', 'int', PDATA_BLOB]],
"CryptProtectData" : ['bool', [PDATA_BLOB, 'string', 'string', 'void *', 'string', 'int', PDATA_BLOB]]
});
function encrypt(plaintext) {
let buf = Buffer.from(plaintext, 'utf16le');
let dataBlobInput = new DATA_BLOB();
dataBlobInput.pbData = buf;
dataBlobInput.cbData = buf.length;
let dataBlobOutput = ref.alloc(DATA_BLOB);
let result = Crypto.CryptProtectData(dataBlobInput.ref(), null, null, null, null, 0, dataBlobOutput);
let outputDeref = dataBlobOutput.deref();
let ciphertext = ref.reinterpret(outputDeref.pbData, outputDeref.cbData, 0);
return ciphertext.toString('base64');
};
function decrypt(ciphertext) {
let buf = Buffer.from(ciphertext, 'base64');
let dataBlobInput = new DATA_BLOB();
dataBlobInput.pbData = buf;
dataBlobInput.cbData = buf.length;
let dataBlobOutput = ref.alloc(DATA_BLOB);
let result = Crypto.CryptUnprotectData(dataBlobInput.ref(), null, null, null, null, 0, dataBlobOutput);
let outputDeref = dataBlobOutput.deref();
let plaintext = ref.reinterpret(outputDeref.pbData, outputDeref.cbData, 0);
return plaintext.toString('utf16le');
};
let text = "有死之荣,无生之耻";
let ciphertext = encrypt(text);
let plaintext = decrypt(ciphertext);
console.log("text:", text);
console.log("ciphertext:", ciphertext);
console.log("plaintext:", plaintext);
以上代码白嫖自Github node-ffi Issue#355 comments from Wackerberg 侵删
2026-07 现代最佳实践:Electron 原生能力的正确做法
2026-07 新增:现在做 Electron + C/C++ 原生能力,不建议从
ffi-napi或直接 V8/NAN 开始。优先级应是:先找现成 Node/Electron API,其次封装本地服务/CLI,再用 N-API /node-addon-api写稳定 ABI 的 addon。只有在快速验证 DLL 调用、历史包袱或小范围内部工具中,才考虑ffi-napi。
方案选择
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 调系统能力,如文件、加密、通知、托盘 | Electron / Node.js / OS API | 少一层 native addon,维护成本最低 |
| 调已有 exe / Python / C++ 程序 | 主进程 child_process 或本地服务 |
进程隔离更清楚,崩溃不拖垮 Electron |
| 调稳定 C/C++ SDK 或算法库 | N-API + node-addon-api |
ABI 更稳定,适合长期维护 |
| 只想快速验证 DLL 函数 | ffi-napi |
快,但类型、内存、兼容性风险较高 |
| 高性能图像处理 / 设备 SDK / 加密模块 | 独立 native addon 或本地 daemon | 便于测试、签名、崩溃恢复和权限隔离 |
推荐项目结构
1 | my-electron-app/ |
native/device-addon 最好作为独立 npm package 管理,原因是:
- 可以单独跑 C++ 单元测试和
node-gyp rebuild。 - 可以独立声明平台工具链、头文件、动态库和 postinstall。
- Electron 主项目只依赖它,不把业务 UI 和 native 编译问题揉在一起。
最小 N-API / node-addon-api 示例
安装依赖:
1 | npm i node-addon-api |
binding.gyp
1 | { |
src/addon.cc
1 |
|
package.json
1 | { |
index.js
1 | const path = require('path') |
Electron 中的正确集成方式
原生模块应由主进程加载,渲染进程通过 preload 暴露最小 API:
1 | // main.js |
1 | // preload.js |
1 | // renderer.js |
关键点:不要在 renderer 中直接
require('.node'),也不要把任意 native 方法透传给页面。主进程负责权限、参数校验、错误处理和崩溃隔离。
Electron ABI 重编译
Electron 自带 Node.js,native addon 不能只按系统 node 编译。安装或升级 Electron 后,需要针对 Electron 版本重编译:
1 | npm i -D @electron/rebuild |
如果使用 Electron Forge,Forge 官方流程会在打包时自动处理 native module rebuild;复杂项目仍建议显式确认构建日志中 native addon 是针对 Electron ABI 编译的。
排查顺序:
- 确认
process.versions.electron和process.versions.modules。 - 删除 native 包的
build/后重新 rebuild。 - 确认 Python 3、Visual Studio Build Tools / Xcode / build-essential 已安装。
- 确认
.node文件被 electron-builder / Forge 打进最终产物。 - 如果依赖外部
.dll/.so/.dylib,确认运行时搜索路径和签名。
工业/机器人软件里的建议边界
- 设备 SDK、相机 SDK、运动控制卡 SDK:优先封装在主进程或独立本地服务,不让 UI 直接碰指针、句柄、线程。
- 图像处理算法:高频大数据量可用 native addon;低频批处理可先用 Python/CLI 服务验证。
- 长时间采集或控制:建议 native 层只做薄封装,状态机、日志、错误恢复、权限控制放在可测试的应用层。
- 安全相关能力:所有命令都要有白名单、参数校验、超时、取消、审计日志,避免 renderer 一条 IPC 直接触发危险硬件动作。