Node.jsのアドオンをN-APIで実装する

Node.js 8.0からはN-APIというものが試験的にサポートされている。
これはNode.js APIのABI(Application Binary Interface)-stableな中間レイヤでこれを使うことでネティブアドオンの開発が効率的になる。

概要

Node.jsをネイティブ拡張するにはNANV8に依存しているため、
その内部実装に関しての知識が少なからず必要になってくるということが問題となっている。

また、これらはバージョンごとでAPIは安定性が保証されてないので、
ネイティブな機能のリリースには各バージョンでのコンパイルが必要となり、
開発者の負担のかかる状況とのこと。

そこでNode.js8.0からABI-stableなN-APIが試験的に追加されている。

N-APIをつかうことで、一度コンパイルしているモジュールはN-APIをサポートしているNode.jsのどのバージョンでも利用できる。
(現在は8.0以降のサポートだけど、今後は会のバージョンへの移植も検討されている)

N-APIを使うことでV8に依存していたネイティブアドオンをプラットフォームやアーキテクチャごとにビルドする必要がなくなる。

とういうことで、V8とN-APIでネイティブアドオンを作ってみた。

node-gyp

まずはじめにnode-gypを使うのでインストールしておく必要がある。
ChromiumやV8同様にGYP(Generate Your Projects)をつかってプロジェクトファイルを生成し、アドオンを作ります。
(もちろん利用するにはpython <= 2.7も必要)

1
npm i -g node-gyp

今回使うC++プログラム。

1
2
3
4
5
6
7
8
9
10
#ifndef _PRIME_NUMBER_H_
#define _PRIME_NUMBER_H_

class PrimeNumber
{
// accessor
public:
int isPrime(int number);
};
#endif // _PRIME_NUMBER_H_
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdbool.h>
#include "prime_number.hpp"

// function definition
int PrimeNumber::isPrime(int number)
{
bool result = true;
int i;

for(i = 2; i < number; ++i) {
if (number % i == 0) {
result = false;
break;
}
}

return result;
}

最終的に以下のようにJavaScriptから呼び出せれば良い。

1
2
3
4
// test.js
const primeNumber = require('./build/Release/primeNumber.node');
console.log(primeNumber.isPrimeNumber(5)); // => true
console.log(primeNumber.isPrimeNumber(6)); // => false

V8をそのまま使う

binding.gyp を作成

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
{
'targets': [
{
'target_name': 'primeNumber',
'sources': ['primeNumber.cpp', '../prime_number/prime_number.cpp'],
'conditions': [
[
'OS=="linux"',
{
"defines": [ "_GNU_SOURCE" ],
"cflags": [ "-g", "-O2", "-std=c++11", ],
}
],
[
'OS=="win"',
{
'libraries': [
'dbghelp.lib',
'Netapi32.lib'
],
'dll_files': [
'dbghelp.dll',
'Netapi32.dll'
],
}
],
[
'OS=="mac"',
{
'xcode_settings': {
'CLANG_CXX_LIBRARY': 'libc++',
'CLANG_CXX_LANGUAGE_STANDARD':'c++11',
}
}
],
]
}
]
}

V8をそのまま使っている。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <node.h>
#include <v8.h>
#include "../prime_number/prime_number.hpp"

void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();

double number = args[0]->NumberValue();
bool data;
PrimeNumber primeNumber;

data = primeNumber.isPrime(number);
v8::Local<v8::Boolean> result = v8::Boolean::New(isolate, data);

args.GetReturnValue().Set(result);
}

void init(v8::Local<v8::Object> exports) {
NODE_SET_METHOD(exports, "isPrimeNumber", Method);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, init)

プロジェクト作成とビルド

1
2
node-gyp configure
node-gyp build

実行する

1
node test.js

N-API

同じくbinding.gyp を作成

1
2
3
4
5
6
7
8
{
"targets": [
{
"target_name": "primeNumber",
"sources": [ "primeNumber.cpp", "../prime_number/prime_number.cpp" ]
}
]
}

そして、N-APIを使う。

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
46
47
48
49
50
51
52
#include <node_api.h>
#include <assert.h>
#include "../prime_number/prime_number.hpp"

napi_value Method(napi_env env, napi_callback_info info)
{
napi_status status;
size_t argc = 1;
napi_value args[0];

status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
assert(status == napi_ok);

// 引数の数をチェック
if (argc == 0) {
napi_throw_type_error(env, "Wrong number of arguments");
return nullptr;
}

// 引数の型をチェック
napi_valuetype valuetype;
status = napi_typeof(env, args[0], &valuetype);
if (valuetype != napi_number) {
napi_throw_type_error(env, "Wrong arguments");
return nullptr;
}

int32_t number;
status = napi_get_value_int32(env, args[0], &number);
assert(status == napi_ok);

bool data;
PrimeNumber primeNumber;
data = primeNumber.isPrime((int)number);

napi_value result;
status = napi_get_boolean(env, data, &result);
assert(status == napi_ok);

return result;
}

void Init(napi_env env, napi_value exports, napi_value module, void* priv) {
napi_status status;
napi_value fn;
status = napi_create_function(env, NULL, Method, NULL, &fn);
if (status != napi_ok) return;
status = napi_set_named_property(env, exports, "isPrimeNumber", fn);
if (status != napi_ok) return;
}

NAPI_MODULE(isPrimeNumber, Init)

後は同じく、プロジェクト作成してビルド。

1
2
node-gyp configure
node-gyp build

実行する際にはオプションが必要。

1
node --napi-modules test.js

まとめ

実際に仕事でこんなことないんですが、サービスによっては複数の言語で動いているとかや、
すでにC/C++で完成している機能をJS側から呼び出したいというとき。
またC/C++のプロジェクトの移植でモジュール開発する際などで使う際はN-APIサポートしておくと捗りそう。

今回作成したサンプル

参考にしたページ

N-API
N-API: Next generation Node.js APIs for native modules

Comments