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

## はじめに
- node.jsからC++関数を利用する際に、引数・返り値の渡し方について困ったことはありませんか？
    - この記事では、`node-addon-api` を使う場合の、引数・返り値の受け渡し方をまとめています。


## 環境構築がまだの方は？
- この記事の内容をトライする前に、以下の記事で環境構築を完了させてください！
    - [【Step-By-Step】Node.jsからC++クラスを利用する](https://qiita.com/NA_simple/items/dc31f9ae9519602f9f50)

## 目次
- [1. 関数の基本形](#1-関数の基本形)
- [2. 値の受け渡し](#2-値の受け渡し)
- [3. 配列の受け渡し](#3-配列の受け渡し)
- [4.（応用）異なるプリミティブ型を配列に入れて返却する](#4-応用異なるプリミティブ型を配列に入れて返却する)
- [5. 試してみよう](#5-試してみよう)

##1. 関数の基本形
- node-addon-apiを利用する場合、ラップされるC++の関数は以下のように定義します。
    - `Napi::Value 関数名(const Napi::CallbackInfo& info);`
        - 引数の個数、型に関係なく、引数は `const Napi::CallbackInfo& info`と記述
        - 返り値は、型に関係なく`Napi::Value`と記述
- 以下は、関数宣言と関数定義の例です。
    - 引数、返り値ともに`void`です。

```C++:void.h
#include <napi.h>
Napi::Value func(const Napi::CallbackInfo& info);
```

```C++: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()を考えてみましょう。

```C++: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ファイルからは次のように見えます

```js:example.js
// (前処理は省略)
let ans = add(1,2)
console.log(ans);
// >> expected: 3
```

##3. 配列の受け渡し
###　引数に配列を渡す( js → C++ )

```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 )

```c++: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ファイルからは次のように利用します。

```js:array.js
// (前処理は省略)
setArr([1,2,3,4,5]);

var arr = getArr();
console.log(arr);
// >> expected: [1.0, 0.5, 0.25]
```

####4. （応用）異なるプリミティブ型を配列に入れて返却する
- C++で複数の返り値を返したい場合に有効です。
    - リターンコードと計算結果の組み合わせなどを返却できます。

```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;
}
```

```js:array2.js
// (前処理は省略)
var arr = getReturns();
console.log("ret1 =", arr[0], "ret2 =", arr[1]);
// >> expected: ret1 = 1 ret2 = aabbcc
```

##5. 試してみよう
- [**前回の記事**](https://qiita.com/NA_simple/items/dc31f9ae9519602f9f50)
で作成したプロジェクトの`wrapper.h`, `wrapper.cpp`, `index.js` を下記のコードで上書きすると、本記事の内容を試すことができます！

　├── node_modules
　├── package-lock.json
　├── package.json
　├── index.js  <-- **上書き**
　├── addon.cc
　├── wrapper.cc <-- **上書き**
　├── wrapper.h  <-- **上書き**
　└── binding.gyp

### サンプルコードはこちら

<details><summary>wrapper.hを開く</summary><div>

```c++: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
```

</div></details>

<details><summary>wrapper.ccを開く</summary><div>

```c++: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;
}

```
</div></details>

<details><summary>index.jsを開く</summary><div>

```javascript: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

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

</div></details>

### その他
- 返り値にユーザー定義型やオブジェクトを返す場合は [このあたり](https://github.com/nodejs/node-addon-examples/tree/main/8_passing_wrapped) が参考になるかもしれません。