はじめに
- 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 .