5
0

More than 3 years have passed since last update.

Node.js からC++関数への引数、返り値まとめ

Last updated at Posted at 2021-02-13

はじめに

  • 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

// ---- 本記事で追加した関数 ---- //

その他

  • 返り値にユーザー定義型やオブジェクトを返す場合は このあたり が参考になるかもしれません。
5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0