はじめに
- node.jsからC++関数を利用する際に、引数・返り値の渡し方について困ったことはありませんか?
- この記事では、
node-addon-api
を使う場合の、引数・返り値の受け渡し方をまとめています。
- この記事では、
環境構築がまだの方は?
- この記事の内容をトライする前に、以下の記事で環境構築を完了させてください!
目次
1. 関数の基本形
- node-addon-apiを利用する場合、ラップされるC++の関数は以下のように定義します。
Napi::Value 関数名(const Napi::CallbackInfo& info);
- 引数の個数、型に関係なく、引数は
const Napi::CallbackInfo& info
と記述 - 返り値は、型に関係なく
Napi::Value
と記述
- 引数の個数、型に関係なく、引数は
- 以下は、関数宣言と関数定義の例です。
- 引数、返り値ともに
void
です。
- 引数、返り値ともに
#include <napi.h>
Napi::Valuefunc(constNapi::CallbackInfo&info);
#include <napi.h>
Napi::Valuefunc(constNapi::CallbackInfo&info){// Do nothing.returnenv.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()を考えてみましょう。
#include <napi.h>
Napi::Valueadd(constNapi::CallbackInfo&info){// お約束Napi::Envenv=info.Env();// 引数は、配列infoから取り出す。doublea=info[0].As<Napi::Number>().DoubleValue();doubleb=info[1].As<Napi::Number>().DoubleValue();// C++で行いたい処理を行うdoubleans=a+b;// 返り値は、Napi::○○ 型にキャストして返却するreturnNapi::Number::New(env,ans);}
- jsファイルからは次のように見えます
// (前処理は省略)letans=add(1,2)console.log(ans);// >> expected: 3
3. 配列の受け渡し
引数に配列を渡す( js → C++ )
Napi::ValuesetArr(constNapi::CallbackInfo&info){Napi::Envenv=info.Env();Napi::Arrayarr=info[0].As<Array>();// C++の配列std::vector<double>vec(arr.Length(),0.0);// for文で要素を順に代入for(size_ti=0;i<arr.Length();i++){Napi::Valueval=arr[i];vec[i]=val.As<Napi::Number>().DoubleValue();}returnenv.Null();}
返り値に配列を渡す( C++ → js )
Napi::ValuegetArr(constCallbackInfo&info){Napi::Envenv=info.Env();// C++の配列std::vector<double>vec={1.0,0.5,0.25};// for文で要素を順に代入Napi::ArrayoutArr=Napi::Array::New(env,vec.size());for(size_ti=0;i<vec.size();i++){outArr[i]=Napi::Number::New(env,vec[i]);}returnoutArr;}
- jsファイルからは次のように利用します。
// (前処理は省略)setArr([1,2,3,4,5]);vararr=getArr();console.log(arr);// >> expected: [1.0, 0.5, 0.25]
4. (応用)異なるプリミティブ型を配列に入れて返却する
- C++で複数の返り値を返したい場合に有効です。
- リターンコードと計算結果の組み合わせなどを返却できます。
Napi::ValuegetReturns(constCallbackInfo&info){Napi::Envenv=info.Env();// do Something C++// 返り値として、 1.0 と "aabbcc" を返却する例constintreturnArgNums=2;intzero=0;intone=1;Napi::ArrayretArr=Napi::Array::New(env,returnArgNums);retArr[zero]=Napi::Number::New(env,1.0);retArr[one]=Napi::String::New(env,"aabbcc");returnretArr;}
// (前処理は省略)vararr=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を開く
#ifndef WRAPPER
#define WRAPPER
#include <napi.h> // 必要なヘッダ
classWrapper:publicNapi::ObjectWrap<Wrapper>{public:staticNapi::ObjectInit(Napi::Envenv,Napi::Objectexports);staticNapi::ObjectNewInstance(Napi::Envenv,constNapi::CallbackInfo&info);Wrapper(constNapi::CallbackInfo&info);~Wrapper();Napi::ValuegetNum(constNapi::CallbackInfo&info);Napi::Valueadd(constNapi::CallbackInfo&info);// <-- 追加Napi::ValuesetArr(constNapi::CallbackInfo&info);// <-- 追加Napi::ValuegetArr(constNapi::CallbackInfo&info);// <-- 追加Napi::ValuegetReturns(constNapi::CallbackInfo&info);// <-- 追加private:doublem_value;};#endif
wrapper.ccを開く
#include "wrapper.h"
#include <napi.h>
usingnamespaceNapi;// ---------------------------------------------------------- //// ---------------------のり付け部分--------------------------- //// ---------------------------------------------------------- //// new() の定義Napi::ObjectWrapper::NewInstance(Napi::Envenv,constNapi::CallbackInfo&info){Napi::EscapableHandleScopescope(env);// jsからコンストラクタに渡されるArgsは infoに配列として入っているconststd::initializer_list<napi_value>initArgList={info[0]};// ここでWrapper:::Wrapper()が呼ばれるNapi::Objectobj=env.GetInstanceData<Napi::FunctionReference>()->New(initArgList);// gcにメモリ解放されないようにスコープを除外するreturnscope.Escape(napi_value(obj)).ToObject();}// メンバ関数のバインドNapi::ObjectWrapper::Init(Napi::Envenv,Napi::Objectexports){Napi::Functionfunc=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=newNapi::FunctionReference();*constructor=Napi::Persistent(func);env.SetInstanceData(constructor);exports.Set("Wrapper",func);returnexports;}// ---------------------------------------------------------- //// --------------- Wrapperクラスの定義はこれより下 --------------- //// ---------------------------------------------------------- //// コンストラクタWrapper::Wrapper(constNapi::CallbackInfo&info):Napi::ObjectWrap<Wrapper>(info){m_value=0.0;};Wrapper::~Wrapper(){};// メンバ関数Napi::ValueWrapper::getNum(constNapi::CallbackInfo&info){Napi::Envenv=info.Env();returnNapi::Number::New(env,this->m_value);}// 引数・返り値の受け渡しNapi::ValueWrapper::add(constNapi::CallbackInfo&info){// お約束Napi::Envenv=info.Env();// 引数は、配列infoから取り出す。doublea=info[0].As<Napi::Number>().DoubleValue();doubleb=info[1].As<Napi::Number>().DoubleValue();// C++で行いたい処理を行うdoubleans=a+b;// 返り値は、Napi::○○ 型にキャストして返却するreturnNapi::Number::New(env,ans);}// 配列の受け取りNapi::ValueWrapper::setArr(constNapi::CallbackInfo&info){Napi::Envenv=info.Env();Napi::Arrayarr=info[0].As<Array>();// C++の配列std::vector<double>vec(arr.Length(),0.0);// for文で要素を順に代入for(size_ti=0;i<arr.Length();i++){Napi::Valueval=arr[i];vec[i]=val.As<Napi::Number>().DoubleValue();}returnenv.Null();}// 配列の返却Napi::ValueWrapper::getArr(constCallbackInfo&info){Napi::Envenv=info.Env();// C++の配列std::vector<double>vec={1.0,0.5,0.25};// for文で要素を順に代入Napi::ArrayoutArr=Napi::Array::New(env,vec.size());for(size_ti=0;i<vec.size();i++){outArr[i]=Napi::Number::New(env,vec[i]);}returnoutArr;}// プリミティブ型が混在した配列の返却Napi::ValueWrapper::getReturns(constCallbackInfo&info){Napi::Envenv=info.Env();// do Something C++// 返り値として、 1.0 と "aabbcc" を返却する例constintreturnArgNums=2;intzero=0;intone=1;Napi::ArrayretArr=Napi::Array::New(env,returnArgNums);retArr[zero]=Napi::Number::New(env,1.0);retArr[one]=Napi::String::New(env,"aabbcc");returnretArr;}
index.jsを開く
// addon.cc内の NODE_API_MODULE(addon, InitAll) が呼ばれるvarWrapper=require('bindings')('addon');// addon.cc内の CreateObject() が呼ばれるvarobj=newWrapper()// wrapper.cc内で登録した getNum()が呼ばれるconsole.log(obj.getNum());// ---- 本記事で追加した関数 ---- //letans=obj.add(1,2)console.log(ans);// >> expected: 3obj.setArr([1,2,3,4,5]);vararr=obj.getArr();console.log(arr);// >> expected: [1.0, 0.5, 0.25]vararr=obj.getReturns();console.log("ret1 =",arr[0],"ret2 =",arr[1]);// >> expected: ret1 = 1 ret2 = aabbcc// ---- 本記事で追加した関数 ---- //
その他
- 返り値にユーザー定義型やオブジェクトを返す場合は このあたりが参考になるかもしれません。