NodeJS addon如何编写
首先我们需要安装:
安装完毕后,我们可以用:
查看已经安装对应版本的node头文件。
然后我们可以安装一下我们机器上面nodejs的版本的头文件,比如我电脑nodejs是18.15.0
1
| node-gyp install 18.15.0
|
然后我们可以去 ~/.cache/node-gyp/18.15.0/include/node
创建一个项目
1 2
| mkdir -p test && cd test npm init -y
|
会创建一个package.json
的文件。
然后在根目录下创建一个 binding.gyp
内容如下:
1 2 3 4 5 6 7 8
| { "targets": [ { "target_name": "binding", "sources": [ "src/binding.cc" ] } ] }
|
native方式
我们在创建一个src/binding.cc
的文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #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, "world2").ToLocalChecked()); }
void Initialize(Local<Object> exports) { NODE_SET_METHOD(exports, "hello", Method); }
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
}
|
然后我们可以执行一下 npm install
,然后他会生成一个makefile的项目在build
文件夹下面,并且会进行编译。结果放在build/Release/binding.node
这个文件。
然后我们可以在根目录创建一个hello.js
调用一下这个文件试一下:
1 2 3
| const t = require('./build/Release/binding.node')
console.log(t.hello())
|
然后我们调用这个hello.js
文件,如下:
然后输出结果如下:
但是由于不同版本的node的头文件里面暴露的东西会变动,到时就尴尬了。
nan方式
需要先安装依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <nan.h> void Say(const Nan::FunctionCallbackInfo<v8::Value>& info) { info.GetReturnValue().Set(Nan::New("hello world").ToLocalChecked()); } void Init(v8::Local<v8::Object> exports) { v8::Local<v8::Context> context = exports->CreationContext(); exports->Set(context, Nan::New("Say").ToLocalChecked(), Nan::New<v8::FunctionTemplate>(Say)->GetFunction(context).ToLocalChecked()); } NODE_MODULE(hello, Init)
|
NAN方式的binding.gyp文件有所不同,因为这里需要引入nan.h的第三方的库,nan具体的使用方式参考:
binding.gyp文件里面需要加一下头文件的地址:
1 2 3
| "include_dirs" : [ "<!(node -e \"require('nan')\")" ]
|
nan比native的方式的好处,就是它帮助我们封装了一层v8、libuv等,用宏的形式,让我们能在不同版本之间稍微能兼容了。
使用N-API
用NAN的话,还是需要对底下的v8、libuv等要了解,而N-API再度进行了一些封装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #define NAPI_CERSION 8 #include <node_api.h> napi_value Say(napi_env env, napi_callback_info info) { napi_value res; napi_create_string_utf8(env, "hello world", 12, &res); return res; } napi_value Init(napi_env env, napi_value exports) { napi_status status; napi_property_descriptor desc = { "Say", 0, Say, 0, 0, 0, napi_default, 0 }; status = napi_define_properties(env, exports, 1, &desc); return 0; }
NAPI_MODULE(hello, Init)
|
这个文档相对比较全,我们可以直接node官方文档里面看:
node-addon-api
这个在N-API之上,又加了一些宏,让写addon再方便了一些。
binding.gyp
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| { "targets": [ { "target_name": "hello", "cflags!": [ "-fno-exceptions" ], "cflags_cc!": [ "-fno-exceptions" ], "sources": [ "hello.cc" ], "include_dirs": [ "<!@(node -p \"require('node-addon-api').include\")" ], 'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], } ] }
|
hello.cc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <napi.h>
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, "hello"), Napi::Function::New(env, Method)); return exports; }
NODE_API_MODULE(hello, Init)
|
package.json:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "name": "hello_world", "version": "0.0.0", "description": "Node.js Addons Example #1", "main": "hello.js", "private": true, "dependencies": { "bindings": "~1.2.1", "node-addon-api": "^1.0.0" }, "scripts": { "test": "node hello.js" }, "gypfile": true }
|
hello.js:
1 2 3
| var addon = require('bindings')('hello');
console.log(addon.hello()); // 'world'
|
参考例子
这个仓库有各种类型的addon的例子,分别是nan、napi、node-addon-api三类的写法:
总结
文章介绍了 native、nan、n-api、node-addon-api等多种方式,这层学习多一些是为了写addon来提升js一些场景限制,关于v8、libuv原理才是不断要加强的。