【Step-By-Step】Node.jsからC++クラスを利用するための環境構築


## はじめに
この記事は、javascriptからC++を呼び出す処理が必要になった時の備忘録です。
[node-addon-api](https://www.npmjs.com/package/node-addon-api)というラッピングライブラリを活用します。
この記事の内容を「マネすれば動く」ように意識して書いています。
Electronなどのデスクトップアプリに応用できます。

#### 事前準備
- 下記のパッケージは事前にインストールしておいてください
    - npm
    - node.js

## 応用記事はこちら！
#### 1. [Node.js からC++関数への引数、返り値まとめ](https://qiita.com/NA_simple/items/21deb835a12a6027bb11)
#### 2. [Node.jsからC++クラス、dllを使う](https://qiita.com/NA_simple/items/27999c8c0a605ef3314b)
## 目次
- [1. プロジェクトの新規作成](#1-プロジェクトの新規作成)
- [2. Cppファイルを追加する](#2-cppファイルを追加する)
- [3. Cppファイルをビルドする](#3-cppファイルをビルドする)
- [4. JavaScriptからビルドしたCppクラスを使う](#4-javascriptからビルドしたcppクラスを使う)

##1. プロジェクトの新規作成
- 空のフォルダを新規作成します。今回は例として、`napi_sample`というフォルダ名にしました。
- 作成したフォルダに移動し、下記のコマンドを実行し、必要モジュールをインストールします。

```bash:terminal
$ npm init -y
$ npm install node bindings node-addon-api
```

- コマンド実行後、下記のようなフォルダ構成となります。

　カレントディレクトリ
　　├── node_modules
　　├── package-lock.json
　　└── package.json

- さらに、下記のようなpackage.jsonが自動的に作成されます。

```json:package.json
{
  "name": "doit_myself",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",  //<-- 開始時にこの.jsファイルが読み込まれる
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bindings": "^1.5.0",
    "node": "^15.8.0",
    "node-addon-api": "^3.1.0" // <-- バージョンに注意。3.x.x　を使用すること
  }
}
```

### index.jsを作成する
カレントディレクトリ
　├── node_modules
　├── package-lock.json
　├── package.json
　└── index.js **<-- 新規作成**

- 起動時に読み込まれるindex.jsを追加しましょう。
    - 下記のようなサンプルとします。

```js:index.js
console.log("Hello! node.js");
```

### ターミナルで動作確認する
- ここまでの環境構築がうまくいっているか確認します。
    - index.jsがあるディレクトリで下記のコマンドを入力してください。

```bash:terminal
$ node .
 >> Hello! node.js
```

##2. Cppファイルを追加する
- ここからは、jsで利用するためのCppラッパークラスを作成していきます。
    - wrapper.h, wrapper.cc, addon.ccの順に説明します。

カレントディレクトリ
　├── node_modules
　├── package-lock.json
　├── package.json
　├── index.js
　├── addon.cc  **<-- 新規作成**
　├── wrapper.cc **<-- 新規作成**
　└── wrapper.h **<-- 新規作成**
※ 拡張子.cc はC++ファイルのことです。本質は .cppと変わりません。 

### ラッパークラスの作成
- ネイティブC++をラッピングするクラスを作成します。
    - このクラスの目的は、jsから渡された引数をC++で解釈できる形にし、C++の返り値をjsが利用できる形式に変換して渡すことです。
- 下記のwrapper.h, wrapper.ccをテンプレとしてお使いください。

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

private:
    double m_value;
};

#endif
```



```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("jsから呼び出す際の関数名", "呼び出したいC++メンバ関数名"),
        });

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

```
- 補足
    - wrapper.cpp内の関数`Napi::Function func = DefineClass()`において、自作のC++メンバ関数を登録する必要があります。サンプルコード`InstanceMethod("getNum", &Wrapper::getNum),`のように、`InstanceMethod("jsから呼び出す際の関数名", "呼び出したいC++メンバ関数名")`として登録しなければなりません。

### jsとの結合用cppファイルを作る
- 次に、Wrapperクラスをjsモジュールとしてエクスポートするための処理をaddon.ccに記述します。
    - こちらも詳細説明は省略します。テンプレとしてお使いください。

```c++:addon.cc
#include <napi.h>
#include "wrapper.h"
#include <iostream>

// jsオブジェクトが初期化された時 new()の呼び出し
Napi::Object CreateObject(const Napi::CallbackInfo& info) {
    return Wrapper::NewInstance(info.Env(), info);
}

// js内でexport()が呼び出されたとき
Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
    // 関数定義
    Napi::Object new_exports = Napi::Function::New(env, CreateObject);
    return Wrapper::Init(env, new_exports);
}

// jsへバインドするためのマクロ
// jsで、 export('bindings')('addon')と記述したとき、上記のInitAll()が呼び出される
NODE_API_MODULE(addon, InitAll)
```

##3. Cppファイルをビルドする
- 作成してたcppをビルドするための設定ファイルを作ります。
    - binding.gyp というファイルです。
    - VisualStudioのprojectのプロパティ設定、CMakeLists.txtと似たような設定をします。

### binding.gypの追加

カレントディレクトリ
　├── node_modules
　├── package-lock.json
　├── package.json
　├── index.js
　├── addon.cc
　├── wrapper.cc 
　├── wrapper.h
　└── binding.gyp **<-- 新規作成**

```py:binding.gyp
{
  "targets": [
    {
      # ↓addon.cc内の NODE_API_MODULE(addon, InitAll) と同名にする
      "target_name": "addon",
      "cflags!": [ "-fno-exceptions" ],
      "cflags_cc!": [ "-fno-exceptions" ],
      # ↓必要な.ccファイルを全て記述する
      "sources": [ "addon.cc", "wrapper.cc"],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      "defines": [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
    }
  ]
}
```
- 上記ファイルをコピペいただければ問題ないです。
    - 注意点として sources セクションには、使用する .cc (or .cpp) 拡張子のファイルを全て登録してください。


### ビルド実行
- 下記コマンドを実行し、ビルドしてください。

```bash:terminal
$ npm install .

  >> gyp info ok と表示されればビルド完了
```
#### エラーが出る場合
- package.jsonの node_addon_apiのバージョンを確認して下さい。(私は、 3.0.1でした）
- 以下のコマンドでリビルドができます

```bash:bash
npx node-gyp configure  // 設定を反映させるとき
npx node-gyp rebuild   // プロジェクトをリビルドするとき
npx node-gyp build     // C++ソースコードの変更を反映させるとき
```

##4. JavaScriptからビルドしたCppクラスを使う
- お待たせしました。最後に index.jsから wrapper.ccのクラスを使ってみましょう。

### index.jsの書き換え
カレントディレクトリ
　├── node_modules
　├── package-lock.json
　├── package.json
　├── index.js **<-- 書き換え**
　├── addon.cc
　├── wrapper.cc 
　├── wrapper.h
　└── binding.gyp

- index.jsを以下のように書き換えてください。

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

### index.jsの実行
ターミナルで次のように実行します。

```bash:terminal

$ node .
>> 0 と表示されれば成功
>> Wrapper.m_valueの値が表示されています。

```

お疲れ様でした。
node-addon-api は"お約束ごと"が多いので、
私はこの記事のようなテンプレを作り、使いまわしています。
参考になれば幸いです。

## 参考リンク
- [ドキュメント](https://github.com/nodejs/node-addon-api)
- [node-addon-examples](https://github.com/nodejs/node-addon-examples)
    - 本格的に学びたい方は、このexampleを順にやっていくと良いでしょう


## Others
- コンストラクタに複数の引数を渡す
- メンバ関数の引数、返り値について
- 別のC++クラスを利用する