Node.jsからC++クラス、dllを使う

## はじめに
- node.jsからC++関数を利用するための記事、第三弾です。
    - 今回は、C++のネイティブクラスをラッピングしたり、dllを利用するための方法をまとめます。
    - この記事では、`node-addon-api` を利用しています。


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

## 目次
- [1. ネイティブC++クラスの追加](#1-ネイティブcクラスの追加)
- [2. ネイティブC++クラスのラッピング](#2-ネイティブcクラスのラッピング)
- [3. javaScriptからC++クラスを使ってみる](#3-javascriptからcクラスを使ってみる)
- [4. dllを利用する](#4-dllを利用する)

##1. ネイティブC++クラスの追加
- node.jsで利用したいネイティブのCppファイルを追加します。
- [**前回の記事**](https://qiita.com/NA_simple/items/dc31f9ae9519602f9f50)
で作成したプロジェクトを以下のように変更します。

カレントディレクトリ
　├── node_modules
　├── package-lock.json
　├── package.json
　├── index.js
　├── addon.cc
　├── binding.gyp **<-- 設定変更**
　├── wrapper.cc
　├── wrapper.h
　└── native_cpp **<-- フォルダ追加**
　　   ├── classA.cpp **<-- 新規作成**
　　   └── classA.h **<-- 新規作成**


#### 今回は例として、classAというサンプルクラスを追加します。

<details><summary>classA.hを見る</summary><div>

```C++:classA.h
#pragma once
#include <iostream>
#include <string>

// 所持金を登録・表示するクラスです。
class ClassA
{
public:
	ClassA();	//コンストラクタ
	~ClassA();	// デストラクタ

	// メンバ関数
	void set_name(std::string name);
	void set_money(long money);
	std::string show_money();

private:
	// メンバ変数
	std::string m_name;
	long m_money;
};

```

</div></details>

<details><summary>classA.cppを見る</summary><div>

```C++:classA.cpp
//#include "pch.h"
#include "classA.h"
#include <iostream>
#include <string>
#include <sstream>


ClassA::ClassA() {
	m_name = "None";
	m_money = 0;
}

ClassA::~ClassA()
{
}

// 名前を登録する
void ClassA::set_name(std::string name) {
	m_name = name;
}

// 金額を代入する
void ClassA::set_money(long money) {
	m_money = money;
}

// 金額を表示する
std::string ClassA::show_money() {
	std::stringstream ss;
	ss << m_name << " has " << m_money << " yen " << std::endl;
	return ss.str();
}
```

</div></details>


- 新しくCppファイルを追加したので、`binding.gyp`を変更します。
    - ワイルドカードを使う方法は、[こちらのStackOverflow]( https://stackoverflow.com/questions/15397582/is-there-wildcard-mechanism-for-listing-sources-in-node-gyp) が参考になりました。

```py:binding.gyp
{
  "targets": [
    {
      # ↓addon.cc内の NODE_API_MODULE(addon, InitAll) と同名にする
      "target_name": "addon",
      "cflags!": [ "-fno-exceptions" ],
      "cflags_cc!": [ "-fno-exceptions" ],
      # ↓必要な.ccファイルを全て記述する
      # ワイルドカードを使って、native_cpp内のファイルを全て読み込む
      "sources": [ "addon.cc", "wrapper.cc", 
                  "<!@(node -p \"require('fs').readdirSync('./native_cpp').map(f=>'native_cpp/'+f).join(' ')\")"],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      "defines": [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
    }
  ]
}
```

- この時点で一旦確認を行いましょう。


```bash:terminal
$ npm install .

  >> gyp info ok と表示されればビルド完了
```

##2. ネイティブC++クラスのラッピング
- 前章でビルドが通ったら、ネイティブクラス`ClassA`をラッピングします。
    - [前記事で作成した `Wrapper`クラス](https://qiita.com/NA_simple/items/dc31f9ae9519602f9f50#2-cppファイルを追加する) を書き換えます。

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

#### 書き換えたコードは以下をご確認ください。

<details><summary>wrapper.hを見る</summary><div>

```C++:wrapper.h
#ifndef WRAPPER
#define WRAPPER

#include <napi.h> // 必要なヘッダ
#include "./native_cpp/classA.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();

    // クラスAのラッピング関数
    Napi::Value setName(const Napi::CallbackInfo &info);
    Napi::Value setMoney(const Napi::CallbackInfo &info);
    Napi::Value showMoney(const Napi::CallbackInfo &info);

private:
    ClassA* m_classA;
};

#endif
```
</div></details>

<details><summary>wrapper.cppを見る</summary><div>

```C++:wrapper.cpp
#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("setName", &Wrapper::setName),
            InstanceMethod("setMoney", &Wrapper::setMoney),
            InstanceMethod("showMoney", &Wrapper::showMoney),
        });

    Napi::FunctionReference *constructor = new Napi::FunctionReference();
    *constructor = Napi::Persistent(func);
    env.SetInstanceData(constructor);

    exports.Set("Wrapper", func);
    return exports;
}

// ---------------------------------------------------------- //
// ---------- これより下で ClassAのラッピングを定義する ----------- //
// ---------------------------------------------------------- //

// コンストラクタ
Wrapper::Wrapper(const Napi::CallbackInfo &info)
    : Napi::ObjectWrap<Wrapper>(info)
{
    m_classA = new ClassA();
};

Wrapper::~Wrapper()
{
    delete m_classA;
    m_classA = nullptr;
};

// メンバ関数
Napi::Value Wrapper::setName(const Napi::CallbackInfo &info)
{
    Napi::Env env = info.Env();
    std::string name = info[0].As<Napi::String>().ToString();
    m_classA->set_name(name);
    return env.Null();
}

Napi::Value Wrapper::setMoney(const Napi::CallbackInfo &info)
{
    Napi::Env env = info.Env();
    int money = info[0].As<Napi::Number>().Int32Value();
    m_classA->set_money(money);
    return env.Null();
}

Napi::Value Wrapper::showMoney(const Napi::CallbackInfo &info)
{
    Napi::Env env = info.Env();
    std::string ans = m_classA->show_money();
    return Napi::String::New(env, ans);
}
```
</div></details>

##3. javaScriptからC++クラスを使ってみる
- では実際に、`index.js`からCppクラスを呼び出してみましょう。
    - 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内でラッピングしたClassAの関数が使える
obj.setName("Tanaka Taro");
obj.setMoney(1000);
console.log(obj.showMoney());
```

- ターミナルからコマンドを実行し、下記のように表示されれば成功です。

```bash:terminal
$ node .
  >> Tanaka Taro has 1000 yen
```

##4. dllを利用する
- dllを利用するための手順は簡単です。
    - .libファイルを準備する（Windowsの場合）
    - `binding.gyp`にlibrariesセクションを追加し、そこに追加した.libファイルのパスを記入する
    - ターミナルで `npm install .`を実行し、ビルドする
    - ./build/Releaseフォルダに .dllファイルを追加する（Windowsの場合）

#### Example
- [**第二章**](#2-ネイティブcクラスのラッピング) で作成したプロジェクトを以下のように変更します。
カレントディレクトリ
　├── node_modules
　├── package-lock.json
　├── package.json
　├── index.js
　├── addon.cc
　├── binding.gyp
　├── wrapper.cc
　├── wrapper.h
　├── native_cpp
　　   ├── 
classA.cpp
 **<-- 削除**
　　   ├── classA.lib **<-- 追加**
　　   └── classA.h
　├── build
　　   ├── Release
　　   　　├── classA.dll **<-- 追加** (Windowsの場合）

- 次に、binding.gypを編集し、include_directoriesセクションを追加します。

```py:binding.gyp
{
  "targets": [
    {
      "target_name": "addon",
      "cflags!": [ "-fno-exceptions" ],
      "cflags_cc!": [ "-fno-exceptions" ],
      "sources": [ "addon.cc", "wrapper.cc", 
                  "<!@(node -p \"require('fs').readdirSync('./native_cpp').map(f=>'native_cpp/'+f).join(' ')\")"],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      # 【追加】ここにライブラリファイルを登録する
      "libraries": [
        "<(module_root_dir)/native_cpp/libclassA.dylib"
      ],
      "defines": [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
    }
  ]
}
```

- 最後に、ビルド&実行して終了です。

```bash:terminal
$ npm install .
$ node .
```