Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8839

Node.js C++ アドオンの開発 (作業メモ)

$
0
0

この記事について

サンプルレベルの Node.js アドオンを C++ で書いてみた際の作業メモです。

内容:

  • ソースは node-addon-examplesをベースにしてます
  • 足し算をする add という関数を JavaScript 側に見せる 2_function_argumentsにちょっと手を入れた程度です

Node.js アドオンとは

C++ addonsの説明が分かりやすいので引用。

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.

Node.js アドオンを作る方法

Node.js のアドオンを作るには以下の方法がある。

  1. 内部の V8/libuv/Node.js ライブラリを直接使う
  2. nan(Native Abstractions for Node.js) を使う
  3. N-API を使う
  4. node-addon-api を使う

それぞれの特徴をざっくりまとめると以下の通り。

  • v8/libuv/Node.js ライブラリを直接使う
    • 複雑、かつ、各ライブラリのバージョンアップと変更の影響をモロに受けるので大変 (特に V8 はリリースごとに大きく変わるらしい1)
    • 各ライブラリを直接触りたい場合以外は使うべきではない
  • nan
    • 各ライブラリのバージョン間の差異を吸収するツール(主にマクロ)を提供
  • N-API
    • アドオン開発向けの C言語 API で、ABI(Application Binary Interface)を保証する
    • Node.js 本体と同じリポジトリでメンテされてる
  • node-addon-api
    • N-API を C++ でラップして使いやすくしたもの
    • Node.js プロジェクトでメンテされてる

Node.js のドキュメントでは N-API と node-addon-api を推奨してる。
今回は、「v8/libuv/Node.jsライブラリを直接使う」「node-addon-api」を使ってみる。

環境

今回試した環境は以下の通り。
- Ubuntu 18.04
- Node.js v12.18.3
- npm 6.14.6
- Python 3.6.9

v8/libuv/Node.js ライブラリを直接使う場合

開発環境の準備

ビルドに必要なツール類をインストールする。

sudo apt install build-essential
sudo npm install-g node-gyp

開発

以下、適当な作業用ディレクトリで開発を行う。

まず main.cc を記述。

main.cc
#include <node.h>
#include <cstring>
namespacedemo{voidThrowTypeError(v8::Isolate*isolate,constchar*msg){size_tmsgSize=std::strlen(msg);v8::Local<v8::String>v8Msg=v8::String::NewFromUtf8(isolate,msg,v8::NewStringType::kNormal,static_cast<int>(msgSize)).ToLocalChecked();// Throw an Error that is passed back to JavaScriptisolate->ThrowException(v8::Exception::TypeError(v8Msg));}voidAddMethod(constv8::FunctionCallbackInfo<v8::Value>&args){v8::Isolate*isolate=args.GetIsolate();// Check the number of arguments passed.if(args.Length()<2){ThrowTypeError(isolate,"Wrong number of arguments");return;}// Check the argument typesif(!args[0]->IsNumber()||!args[1]->IsNumber()){ThrowTypeError(isolate,"Wrong arguments");return;}// Perform the operationdoublearg0=args[0].As<v8::Number>()->Value();doublearg1=args[1].As<v8::Number>()->Value();v8::Local<v8::Number>answer=v8::Number::New(isolate,arg0+arg1);// Set the return value (using the passed in FunctionCallbackInfo<Value>&)args.GetReturnValue().Set(answer);}voidInitialize(v8::Local<v8::Object>exports){NODE_SET_METHOD(exports,"add",AddMethod);}NODE_MODULE(NODE_GYP_MODULE_NAME,Initialize)}// namespace demo

ビルド

同じディレクトリにビルド用の binding.gyp を用意。

binding.gyp
{"targets":[{"target_name":"myaddon","sources":["main.cc"]}]}

上記ディレクトリからビルドを実行する。

# Makefile 等の作成。build ディレクトリ配下に出力される。
node-gyp configure

# ビルドの実行。build/Release/ に myaddon.node というファイルが作成される。
node-gyp build

実行

今回作ったアドオン myaddonを使う JavaScript を用意。成功する場合と失敗する場合の両方を試してる。

sample.js
constmyaddon=require('./build/Release/myaddon')// 成功する場合 → 8 が返る。constans1=myaddon.add(5,3)console.log(ans1)// 失敗する場合(引数に数値ではなく文字列を渡してる) → 例外が投げられる。try{constans2=myaddon.add(5,"abc")console.log(ans2)}catch(e){console.log(e.message)}

実行結果。意図した通りに動いてる。

$ node sample.js 
8
Wrong arguments

node-addon-api を使う場合

開発環境の準備

ビルドに必要なツール類をインストールする。node-gyp の代わりに CMake も使えるが2、今回はそのまま node-gyp を使った。

sudo apt install build-essential
sudo npm install-g node-gyp

開発

以下、適当な作業用ディレクトリで開発を行う。

package.json を用意。

npm init

# dependencies に node-addon-api を追加
npm install node-addon-api

# package.json に `"gypfile": true` を追加する
vi package.json

以下のようになる。

{"name":"myaddon","version":"1.0.0","description":"","main":"sample.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1"},"author":"","license":"ISC","dependencies":{"node-addon-api":"^3.0.0"},"gypfile":true}

次に main.cc を用意。内容は同じく足し算の関数 add のエクスポートだが、node-addon-api を使うと V8の複雑な型が消えてだいぶすっきりする。

なお、node-addon-api を使うには napi.hをインクルードする。v8.h, uv.h, node.hなどのライブラリのヘッダーを直接インクルードしてはいけない。

#include <napi.h>
namespacedemo{Napi::ValueAddMethod(constNapi::CallbackInfo&info){Napi::Envenv=info.Env();// Check the number of arguments passed.if(info.Length()<2){Napi::TypeError::New(env,"Wrong number of arguments").ThrowAsJavaScriptException();returnenv.Null();}// Check the argument typeif(!info[0].IsNumber()||!info[1].IsNumber()){Napi::TypeError::New(env,"Wrong arguments").ThrowAsJavaScriptException();returnenv.Null();}// Perform the operationdoublep1=info[0].As<Napi::Number>().DoubleValue();doublep2=info[1].As<Napi::Number>().DoubleValue();Napi::Numberanswer=Napi::Number::New(env,p1+p2);returnanswer;}Napi::ObjectInitialize(Napi::Envenv,Napi::Objectexports){exports.Set(Napi::String::New(env,"add"),Napi::Function::New(env,AddMethod));returnexports;}NODE_API_MODULE(NODE_GYP_MODULE_NAME,Initialize)}// namespace demo

ビルド

ビルド用の binding.gyp を用意。少し複雑になるが、C++ から JavaScript への例外を無効にする設定等をしてる。詳細はここを参照。

{"targets":[{"target_name":"myaddon","cflags!":["-fno-exceptions"],"cflags_cc!":["-fno-exceptions"],"sources":["main.cc"],"include_dirs":["<!@(node -p \"require('node-addon-api').include\")"],'defines':['NAPI_DISABLE_CPP_EXCEPTIONS'],}]}

ビルド手順は同じ。

node-gyp configure
node-gyp build

実行

実行手順と結果もまったく同じのため、簡易的に記載する。

# sample.js は同じものを用意しておく。$ node sample.js 
8
Wrong arguments

参考サイト

以上


  1. Native abstractions for Node.jsには次の記述がある: " The V8 API can, and has, changed dramatically from one V8 release to the next (and one major Node.js release to the next)." 

  2. CMake.jsより引用 : "CMake.js is an alternative build system based on CMake. CMake.js is a good choice for projects that already use CMake or for developers affected by limitations in node-gyp." 


Viewing all articles
Browse latest Browse all 8839

Trending Articles