发布于 

NodeJS addon如何编写

首先我们需要安装:

1
npm install node-gyp -g

安装完毕后,我们可以用:

1
node-gyp list

查看已经安装对应版本的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文件,如下:

1
node ./hello.js

然后输出结果如下:

1
world2

但是由于不同版本的node的头文件里面暴露的东西会变动,到时就尴尬了。

nan方式

需要先安装依赖:

1
npm install --save 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原理才是不断要加强的。


如果你有什么意见和建议,可以点击: 反馈地址 进行反馈。