发现了个好玩的东西,来学一手(实际上就是跟文档随便过一下再拿中文随便记录一下):https://nodejs.org/dist/latest-v18.x/docs/api/addons.html
Addons are dynamically-linked shared objects written in C++. The
require()
function can load addons as ordinary Node.js modules. Addons provide an interface between JavaScript and C/C++ libraries.
共享模块,一眼看上去好像 wasm 的说 |・ω・`)
使用 C++ 编写源码,编译成 addon.node
这样的二进制文件供 node 调用。
似乎要先面熟一些东西:
V8:node 用来执行 js 的 C++ 库,萌新阶段就听说的牛逼玩意儿 QwQ
libuv:用来实现 node 的 event loop, worker, asynchronous 的 C++ 库
Addon authors should avoid blocking the event loop with I/O or other time-intensive tasks by offloading work via libuv to non-blocking system operations, worker threads, or a custom use of libuv threads.
Internal Node.js libraries:node 自身提供一些 addon 可以使用的 C++ API,最重要比如
node::ObjectWrap
只有
libuv, OpenSSL, V8, and zlib symbols
被 node 重新暴露出来,其他似乎要手动链接 qwq
# 编写 Hello World
先跟官网来粘一份代码:
// hello.cc | |
#include <node.h> | |
namespace demo | |
{ | |
using v8::FunctionCallbackInfo; | |
using v8::Isolate; | |
using v8::Local; | |
using v8::Object; | |
using v8::String; | |
using v8::Value; | |
void Method(const FunctionCallbackInfo<Value> &args) | |
{ | |
Isolate *isolate = args.GetIsolate(); | |
args.GetReturnValue().Set(String::NewFromUtf8( | |
isolate, "world") | |
.ToLocalChecked()); | |
} | |
void Initialize(Local<Object> exports) | |
{ | |
NODE_SET_METHOD(exports, "hello", Method); | |
} | |
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) | |
} // namespace demo |
所有 node addon 必须像这样暴露一个初始化函数:
void Initialize(Local<Object> exports); | |
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) // 后面没分号 |
The module_name
must match the filename of the final binary (excluding the .node
suffix).
上面例子中,the initialization function is Initialize
and the addon module name is addon
.(先比着抄就完事了)
# 构建
这里使用 node-gyp 来编译写好的 C++ 成一个二进制文件 addon.node
node-gyp 作为 npm 内置的一部分,被设计为正常情况下只有在 npm install 装一些 node addons 的时候才可以用内置的 node-gyp 来针对平台编译 addon,自己用的话需要手动安装 node-gyp,参考 github 仓库。
小插曲:不知道为啥遇到一堆网络问题,npm, yarn, tencent 都试了一遍,还是 cnpm 最顶(顺带安利一个叫 nrm 的工具,顺带半个晚上进去了 QAQ)
在项目根目录新建一个叫 binding.gyp
的配置文件,供 node-gyp 来编译 addon
binding.gyp:
{ | |
"targets": [ | |
{ | |
"target_name": "addon", | |
"sources": ["hello.cc"] // 这里注意 cc 文件的路径 | |
} | |
] | |
} |
然后使用 node-gyp configure
会发现新增了一个 build
文件夹,这边 mac 平台里面有个 Makefile,然后再 node-gyp build
会发现新增了 build/Release/addon.node
,就编译完了
# 在 node 中使用 addon
直接在 node 中使用 require
即可引入刚刚编译的模块(像调用普通模块一样调用二进制文件,怎么这么像 wasm..)
hello.js:
const addon = require('./build/Release/addon'); | |
console.log(addon.hello()); | |
// Prints: 'world' |
编译的二进制文件路径可能因为编译方式不同而不同,可以使用 node-bindings 来确定这个路径。
yarn add bindings
import bindings from 'bindings';
const addon = bindings('addon.node');
console.log(addon.hello());
# 传递参数
还是官网的例子:
addon.cc:
#include <node.h> | |
namespace demo | |
{ | |
using v8::Exception; | |
using v8::FunctionCallbackInfo; | |
using v8::Isolate; | |
using v8::Local; | |
using v8::Number; | |
using v8::Object; | |
using v8::String; | |
using v8::Value; | |
// This is the implementation of the "add" method | |
// Input arguments are passed using the | |
// const FunctionCallbackInfo<Value>& args struct | |
void Add(const FunctionCallbackInfo<Value> &args) | |
{ | |
Isolate *isolate = args.GetIsolate(); | |
// Check the number of arguments passed. | |
if (args.Length() < 2) | |
{ | |
// Throw an Error that is passed back to JavaScript | |
isolate->ThrowException(Exception::TypeError( | |
String::NewFromUtf8(isolate, | |
"Wrong number of arguments") | |
.ToLocalChecked())); | |
return; | |
} | |
// Check the argument types | |
if (!args[0]->IsNumber() || !args[1]->IsNumber()) | |
{ | |
isolate->ThrowException(Exception::TypeError( | |
String::NewFromUtf8(isolate, | |
"Wrong arguments") | |
.ToLocalChecked())); | |
return; | |
} | |
// Perform the operation | |
double value = | |
args[0].As<Number>()->Value() + args[1].As<Number>()->Value(); | |
Local<Number> num = Number::New(isolate, value); | |
// Set the return value (using the passed in | |
// FunctionCallbackInfo<Value>&) | |
args.GetReturnValue().Set(num); | |
} | |
void Init(Local<Object> exports) | |
{ | |
NODE_SET_METHOD(exports, "add", Add); | |
} | |
NODE_MODULE(NODE_GYP_MODULE_NAME, Init) | |
} // namespace demo |
# 总结
这个看上去像 wasm 的东西大概优点就跟 wasm 差不多叭,能在 node 里跑 C++ 的话对性能提升不用说,还有些 js 实现不了的奇奇怪怪的操作(比如获取某个变量在内存中的表示形式 qwq)
顺带隔壁 deno 什么时候出个 Rust addons 呀