LoginSignup
5
8

More than 3 years have passed since last update.

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

Posted at

はじめに

  • 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を見る
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を見る
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を変更します。
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' ],
    }
  ]
}
  • この時点で一旦確認を行いましょう。
terminal
$ npm install .

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

2. ネイティブC++クラスのラッピング

カレントディレクトリ
 ├── 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を見る
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を見る
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を以下のように書き直します。
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());
  • ターミナルからコマンドを実行し、下記のように表示されれば成功です。
terminal
$ 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セクションを追加します。

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' ],
    }
  ]
}
  • 最後に、ビルド&実行して終了です。
terminal
$ npm install .
$ node .
5
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
8