はじめに
- node.jsからC++関数を利用する際に、引数・返り値の渡し方について困ったことはありませんか?
- この記事では、
node-addon-api
を使う場合の、引数・返り値の受け渡し方をまとめています。
- この記事では、
環境構築がまだの方は?
- この記事の内容をトライする前に、以下の記事で環境構築を完了させてください!
目次
##1. 関数の基本形
- node-addon-apiを利用する場合、ラップされるC++の関数は以下のように定義します。
-
Napi::Value 関数名(const Napi::CallbackInfo& info);
- 引数の個数、型に関係なく、引数は
const Napi::CallbackInfo& info
と記述 - 返り値は、型に関係なく
Napi::Value
と記述
- 引数の個数、型に関係なく、引数は
-
- 以下は、関数宣言と関数定義の例です。
- 引数、返り値ともに
void
です。
- 引数、返り値ともに
void.h
#include <napi.h>
Napi::Value func(const Napi::CallbackInfo& info);
void.cpp
#include <napi.h>
Napi::Value func(const Napi::CallbackInfo& info) {
// Do nothing.
return env.Null();
}
##2. 値の受け渡し
- js ↔︎ C++でやり取りするにあたって必要な引数、返り値の処理についてまとめました。
引数の型対応表
C++型 | napi型 |
---|---|
int | .As<Napi::Number>().Int32Value() |
double | .As<Napi::Number>().DoubleValue() |
std::string | .As<Napi::String>().ToString() |
返り値の型対応表
C++型 | napi型 |
---|---|
int, double | return Napi::Number::New(env, C++変数名) |
std::string | return Napi::String::New(env, C++変数名) |
- jsは数値型が1種類しかないため、数値であればNapi::Number型で良い
関数例
- 例として、jsから2つの引数(a,b)をC++で受け取り、その和をjsに返却する関数add()を考えてみましょう。
example.cpp
#include <napi.h>
Napi::Value add(const Napi::CallbackInfo& info) {
// お約束
Napi::Env env = info.Env();
// 引数は、配列infoから取り出す。
double a = info[0].As<Napi::Number>().DoubleValue();
double b = info[1].As<Napi::Number>().DoubleValue();
// C++で行いたい処理を行う
double ans = a + b;
// 返り値は、Napi::○○ 型にキャストして返却する
return Napi::Number::New(env, ans);
}
- jsファイルからは次のように見えます
example.js
// (前処理は省略)
let ans = add(1,2)
console.log(ans);
// >> expected: 3
##3. 配列の受け渡し
### 引数に配列を渡す( js → C++ )
array.cpp
Napi::Value setArr(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::Array arr = info[0].As<Array>();
// C++の配列
std::vector<double> vec(arr.Length(), 0.0);
// for文で要素を順に代入
for(size_t i = 0; i < arr.Length(); i++){
Napi::Value val = arr[i];
vec[i] = val.As<Napi::Number>().DoubleValue();
}
return env.Null();
}
### 返り値に配列を渡す( C++ → js )
array.cpp
Napi::Value getArr(const CallbackInfo& info){
Napi::Env env = info.Env();
// C++の配列
std::vector<double> vec = {1.0, 0.5, 0.25};
// for文で要素を順に代入
Napi::Array outArr = Napi::Array::New(env, vec.size());
for (size_t i = 0; i < vec.size(); i++) {
outArr[i] = Napi::Number::New(env, vec[i]);
}
return outArr;
}
- jsファイルからは次のように利用します。
array.js
// (前処理は省略)
setArr([1,2,3,4,5]);
var arr = getArr();
console.log(arr);
// >> expected: [1.0, 0.5, 0.25]
####4. (応用)異なるプリミティブ型を配列に入れて返却する
- C++で複数の返り値を返したい場合に有効です。
- リターンコードと計算結果の組み合わせなどを返却できます。
array2.cpp
Napi::Value getReturns(const CallbackInfo& info){
Napi::Env env = info.Env();
// do Something C++
// 返り値として、 1.0 と "aabbcc" を返却する例
const int returnArgNums = 2;
int zero = 0;
int one = 1;
Napi::Array retArr = Napi::Array::New(env, returnArgNums);
retArr[zero] = Napi::Number::New(env, 1.0);
retArr[one] = Napi::String::New(env, "aabbcc");
return retArr;
}
array2.js
// (前処理は省略)
var arr = getReturns();
console.log("ret1 =", arr[0], "ret2 =", arr[1]);
// >> expected: ret1 = 1 ret2 = aabbcc
##5. 試してみよう
-
前回の記事
で作成したプロジェクトのwrapper.h
,wrapper.cpp
,index.js
を下記のコードで上書きすると、本記事の内容を試すことができます!
├── node_modules
├── package-lock.json
├── package.json
├── index.js <-- 上書き
├── addon.cc
├── wrapper.cc <-- 上書き
├── wrapper.h <-- 上書き
└── binding.gyp
サンプルコードはこちら
wrapper.hを開く
wrapper.h
#ifndef WRAPPER
#define WRAPPER
#include <napi.h> // 必要なヘッダ
class Wrapper : public Napi::ObjectWrap<Wrapper> {
public:
static Napi::Object Init(Napi::Env env, Napi::Object exports);
static Napi::Object NewInstance(Napi::Env env, const Napi::CallbackInfo& info);
Wrapper(const Napi::CallbackInfo& info);
~Wrapper();
Napi::Value getNum(const Napi::CallbackInfo& info);
Napi::Value add(const Napi::CallbackInfo& info); // <-- 追加
Napi::Value setArr(const Napi::CallbackInfo& info); // <-- 追加
Napi::Value getArr(const Napi::CallbackInfo& info); // <-- 追加
Napi::Value getReturns(const Napi::CallbackInfo& info); // <-- 追加
private:
double m_value;
};
#endif
wrapper.ccを開く
wrapper.cc
#include "wrapper.h"
#include <napi.h>
using namespace Napi;
// ---------------------------------------------------------- //
// ---------------------のり付け部分--------------------------- //
// ---------------------------------------------------------- //
// new() の定義
Napi::Object Wrapper::NewInstance(Napi::Env env, const Napi::CallbackInfo &info)
{
Napi::EscapableHandleScope scope(env);
// jsからコンストラクタに渡されるArgsは infoに配列として入っている
const std::initializer_list<napi_value> initArgList = {info[0]};
// ここでWrapper:::Wrapper()が呼ばれる
Napi::Object obj = env.GetInstanceData<Napi::FunctionReference>()->New(initArgList);
// gcにメモリ解放されないようにスコープを除外する
return scope.Escape(napi_value(obj)).ToObject();
}
// メンバ関数のバインド
Napi::Object Wrapper::Init(Napi::Env env, Napi::Object exports)
{
Napi::Function func = DefineClass(
env, "Wrapper", {
// ここにメソッドを登録する
InstanceMethod("getNum", &Wrapper::getNum),
InstanceMethod("add", &Wrapper::add), // <-- 追加
InstanceMethod("setArr", &Wrapper::setArr), // <-- 追加
InstanceMethod("getArr", &Wrapper::getArr), // <-- 追加
InstanceMethod("getReturns", &Wrapper::getReturns), // <-- 追加
});
Napi::FunctionReference *constructor = new Napi::FunctionReference();
*constructor = Napi::Persistent(func);
env.SetInstanceData(constructor);
exports.Set("Wrapper", func);
return exports;
}
// ---------------------------------------------------------- //
// --------------- Wrapperクラスの定義はこれより下 --------------- //
// ---------------------------------------------------------- //
// コンストラクタ
Wrapper::Wrapper(const Napi::CallbackInfo &info)
: Napi::ObjectWrap<Wrapper>(info)
{
m_value = 0.0;
};
Wrapper::~Wrapper(){};
// メンバ関数
Napi::Value Wrapper::getNum(const Napi::CallbackInfo &info)
{
Napi::Env env = info.Env();
return Napi::Number::New(env, this->m_value);
}
// 引数・返り値の受け渡し
Napi::Value Wrapper::add(const Napi::CallbackInfo& info) {
// お約束
Napi::Env env = info.Env();
// 引数は、配列infoから取り出す。
double a = info[0].As<Napi::Number>().DoubleValue();
double b = info[1].As<Napi::Number>().DoubleValue();
// C++で行いたい処理を行う
double ans = a + b;
// 返り値は、Napi::○○ 型にキャストして返却する
return Napi::Number::New(env, ans);
}
// 配列の受け取り
Napi::Value Wrapper::setArr(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::Array arr = info[0].As<Array>();
// C++の配列
std::vector<double> vec(arr.Length(), 0.0);
// for文で要素を順に代入
for(size_t i = 0; i < arr.Length(); i++){
Napi::Value val = arr[i];
vec[i] = val.As<Napi::Number>().DoubleValue();
}
return env.Null();
}
// 配列の返却
Napi::Value Wrapper::getArr(const CallbackInfo& info){
Napi::Env env = info.Env();
// C++の配列
std::vector<double> vec = {1.0, 0.5, 0.25};
// for文で要素を順に代入
Napi::Array outArr = Napi::Array::New(env, vec.size());
for (size_t i = 0; i < vec.size(); i++) {
outArr[i] = Napi::Number::New(env, vec[i]);
}
return outArr;
}
// プリミティブ型が混在した配列の返却
Napi::Value Wrapper::getReturns(const CallbackInfo& info){
Napi::Env env = info.Env();
// do Something C++
// 返り値として、 1.0 と "aabbcc" を返却する例
const int returnArgNums = 2;
int zero = 0;
int one = 1;
Napi::Array retArr = Napi::Array::New(env, returnArgNums);
retArr[zero] = Napi::Number::New(env, 1.0);
retArr[one] = Napi::String::New(env, "aabbcc");
return retArr;
}
index.jsを開く
index.js
// addon.cc内の NODE_API_MODULE(addon, InitAll) が呼ばれる
var Wrapper = require('bindings')('addon');
// addon.cc内の CreateObject() が呼ばれる
var obj = new Wrapper()
// wrapper.cc内で登録した getNum()が呼ばれる
console.log(obj.getNum());
// ---- 本記事で追加した関数 ---- //
let ans = obj.add(1,2)
console.log(ans);
// >> expected: 3
obj.setArr([1,2,3,4,5]);
var arr = obj.getArr();
console.log(arr);
// >> expected: [1.0, 0.5, 0.25]
var arr = obj.getReturns();
console.log("ret1 =", arr[0], "ret2 =", arr[1]);
// >> expected: ret1 = 1 ret2 = aabbcc
// ---- 本記事で追加した関数 ---- //
その他
- 返り値にユーザー定義型やオブジェクトを返す場合は このあたり が参考になるかもしれません。