はじめに
- node.jsからC++関数を利用するための記事、第三弾です。
- 今回は、C++のネイティブクラスをラッピングしたり、dllを利用するための方法をまとめます。
- この記事では、node-addon-apiを利用しています。
 
環境構築がまだの方は?
- この記事の内容をトライする前に、以下の記事で環境構築を完了させてください!
目次
1. ネイティブC++クラスの追加
- node.jsで利用したいネイティブのCppファイルを追加します。
- 
前回の記事
 で作成したプロジェクトを以下のように変更します。
カレントディレクトリ
 ├── node_modules
 ├── package-lock.json
 ├── package.json
 ├── index.js
 ├── addon.cc
 ├── binding.gyp <-- 設定変更
 ├── wrapper.cc
 ├── wrapper.h
 └── native_cpp <-- フォルダ追加
     ├── classA.cpp <-- 新規作成
     └── classA.h <-- 新規作成
今回は例として、classAというサンプルクラスを追加します。
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;
};
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();
}
- 新しくCppファイルを追加したので、binding.gypを変更します。- ワイルドカードを使う方法は、こちらのStackOverflow が参考になりました。
 
{
  "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' ],
    }
  ]
}
- この時点で一旦確認を行いましょう。
$ npm install .
  >> gyp info ok と表示されればビルド完了
2. ネイティブC++クラスのラッピング
- 前章でビルドが通ったら、ネイティブクラスClassAをラッピングします。- 
前記事で作成した Wrapperクラス を書き換えます。
 
- 
前記事で作成した 
カレントディレクトリ
 ├── node_modules
 ├── package-lock.json
 ├── package.json
 ├── index.js
 ├── addon.cc
 ├── binding.gyp
 ├── wrapper.cc <-- 書き換え
 ├── wrapper.h <-- 書き換え
 └── native_cpp
     ├── classA.cpp
     └── classA.h
書き換えたコードは以下をご確認ください。
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
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);
}
3. javaScriptからC++クラスを使ってみる
- では実際に、index.jsからCppクラスを呼び出してみましょう。- 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());
- ターミナルからコマンドを実行し、下記のように表示されれば成功です。
$ node .
  >> Tanaka Taro has 1000 yen
4. dllを利用する
- dllを利用するための手順は簡単です。
- .libファイルを準備する(Windowsの場合)
- 
binding.gypにlibrariesセクションを追加し、そこに追加した.libファイルのパスを記入する
- ターミナルで npm install .を実行し、ビルドする
- ./build/Releaseフォルダに .dllファイルを追加する(Windowsの場合)
 
Example
- 
第二章 で作成したプロジェクトを以下のように変更します。 
 カレントディレクトリ
 ├── 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セクションを追加します。 
{
  "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' ],
    }
  ]
}
- 最後に、ビルド&実行して終了です。
$ npm install .
$ node .