Node.jsからC++のコードを呼び出したい

Electronを使ってwindowsのデスクトップアプリを作ってみたくなったので、取り敢えずNode.jsからC++を呼び出してみることにしました。node-addon-apiというNode.jsから簡単にC++を呼び出せるものを使います。Node.jsは何となくでしか使ったことないし、Windowsに環境構築するのは8年ぶりぐらいなので、環境構築時の状況を理解するのが思ったよりも大変でした。四苦八苦した記録を残しておきます。

結論としては、node-addon-apiのgithubにあるセットアップ方法をみて始めるのが、変な穴にはまらずに早く動かせました。仕事じゃないし遊びだからと思ってその辺の読みやすいブログの記事を見ながらやっていたら、上手くいかなくてかえって時間がかかってしまいました…🤤やっぱり公式サイトに書いてあるやり方は、無駄がなくて色んなことに配慮されて書かれていると思います。

📕node-addon-api

今回の記事はElectronで作った画面からC++のメソッドを呼び出す流れを書いています。タイトルにある「Node.jsからC++のコードを呼び出す」ところは「node-addon-apiでHello World」の節から始まります。

環境構築に必要なもの

  • Visual Studio Code
  • git
  • Python
  • Node.js(nvmインストーラより)
    • Electron
    • node-addon-api
    • node-gyp
  • visualstudio build tool

Node.jsのインストール

以下のドキュメントを参考にNode.jsをインストールします。ドキュメント通りにVisual Studio Codeとgitも入れます。

📕Windows での NodeJS のインストール

Electronのインストール

Electronの公式ドキュメントでQuick Startを見ながらElectronをインストールします。

📕Quick Start|Electron

ElectronでHello World

QuickStartでHello Worldを表示するアプリを作りますが、公式の説明がいまいち分かりにくかったので以下のブログを参照しつつ進めました。

📕【入門】Electron完全に理解した

Hello Worldが問題なく動いたので、取り敢えずここまでは順調です。

node-addon-apiでHello World

ここからが今回メインの目的であるNode.jsからC++を呼び出すところなので詳しく書いていきます。
以下のnode-addon-apiのSetupのドキュメントを参照します。

📕Set Up|node-addon-api

Node.jsは既にインストールしてあるので、ビルドツールのnode-gypをnpmで入れます。

$npm install -g node-gyp

以下のExamplesのHelloWorldを試します。

📕node-addon-examples/src/1-getting-started/1_hello_world
/node-addon-api/

私はtestaddonというフォルダを作って、その中にリンク先にあるbinding.gyp,hello.cc,hello.js,package.jsonの4つのファイルをコピーしました。コピーできたらtestaddonフォルダの中で以下のコマンドを打ち実行します。C++のメソッドから返ってきたworldという文字列を表示できました。

npm install
node ./ 
world ←実行結果

node-addon-apiのビルドが失敗する

自分の場合、node-addon-apiのビルドが失敗する原因は主に2つありました。

  1. visualstudio build toolをインストールしていない
  2. gypを手動でインストールしていない

それぞれどういうことだったのか説明します。

visualstudio build toolをインストールしていない

ビルドを実行してみると以下のようなエラー(必要な部分だけ抜粋)が出て失敗します。

gyp ERR! find VS
gyp ERR! find VS **************************************************************
gyp ERR! find VS You need to install the latest version of Visual Studio
gyp ERR! find VS including the "Desktop development with C++" workload.
gyp ERR! find VS For more information consult the documentation at:
gyp ERR! find VS https://github.com/nodejs/node-gyp#on-windows
gyp ERR! find VS **************************************************************

これはログにある通りvisualstudio build toolをインストールするとエラーがなくなりサンプルを実行できます。
以下のサイトからインストーラをダウンロードしました。サイトの下の方にTools for Visual Studioというメニューがあるのでそこを開いてBuild Tools for Visual Studio 2022というものをダウンロードしました。

Visual Studio Tools のダウンロード - Windows、Mac、Linux 用の無料インストール

インストーラを起動したら以下の画面で「C++によるデスクトップ開発」を選択してインストールすればオッケーです。

似たものでwindos-build-toolを入れるという話もあるのですが、今はwindows-build-toolは廃止になっているのでわざわざnpmで入れる必要はないようです。

📕windows-build-tools

で、調べてみるとwindos-build-toolの機能はNode.jsのWindows用のインストーラーで初回のみインストールを選択できるようですが、結論から言うとこれは不要です。(私の場合はインストーラーを使わずにnvmでNode.jsを入れてるので)

↓この画面でチェックを入れるとインストールできるようです

一応実際に試したところ、windos-build-toolではなくてvisualstudio2019buildtoolsがインストールされるようでした。Node.jsのインストーラーがchocolateyというWindows版のパッケージ管理ソフトをインストールして、そこからvisualstudio2019buildtoolsをインストールしようとしていたのですが、下のような依存関係の解決ができずにエラーになってしまいました。

 - visualstudio2019-workload-vctools - Unable to resolve dependency 'kb2919355': Unable to find a version of 'kb2919355' that is compatible with 'dotnetfx 4.7.2 constraint: kb2919355 (>= 1.0.20160915)', 'KB2999226 1.0.20161023 constraint: kb2919355 (>= 1.0.20160915)', 'vcredist140 14.0.23026 constraint: kb2919355 (>= 1.0.20160915)', 'visualstudio2019buildtools 16.0.0 constraint: kb2919355 (>= 1.0.20160915)'.
Type ENTER to exit:

しかもこのパッケージは2019年ですが現在の最新バージョンは2022年です。手動で2022年のバージョンを入れようとしたのですが、こちらも同様の依存関係のエラーで入らず。chocolateyのマニュアルにこのタイプのエラーの解決方法が載っているのですが、面倒になってしまったので今回はパスすることにしました。chocolateyは介さずに普通にvisualstudio build toolを入れておしまい。

gypを手動でインストールしていない

node-gypをインストールせずにbindingsとnode-addon-apiのみをインストールした状態でビルドしてみると、また違ったエラーになりました。これはbindingsに含まれているnode-pre-gypというバイナリから実行されている古いnode-gypを使っていることが原因のようです。調べてみるとnode-gyp@9.4.0とありました。最新はnode-gyp@10.0.0です。古いgypでpython側で廃止になったコードが使われているためにエラーが出るようですが、最新のgypではこれに対応しているので問題なくビルドできます。最新のnode-gypを入れて解決。(他にきれいなやり方があるのかもしれないですけど…🙄)

distutils is removed in Python 3.12

パッケージのバージョンが古くて気になる

HelloWorldサンプルが利用しているnode-addon-apiとbindingsのバージョンがかなり古いものだったのでpackage.jsonでそれぞれのバージョンを最新のものに指定して実行しました。問題なく動いたのでオッケーそうです👌

◇古い
hello_world@0.0.0 C:\Users\Fukari\testaddon
├── bindings@1.2.1
└── node-addon-api@1.7.2
ーーーーーーーーーーーーーーーーーーーーーーーーーーーー
◇新しい
PS C:\Users\Fukari\testaddon> npm ls
hello_world@0.0.0 C:\Users\Fukari\testaddon
├── bindings@1.5.0
└── node-addon-api@7.0.0

ElectronからC++をコールする

最後にElectronのHelloWorldのサンプルとnode-addon-apiのサンプルを合体してElectronからC++側のworldという文字列を返すメソッドを呼び出してみたいと思います。ElectronのHelloWorldのサンプルにボタンを付けて、ボタンを押したらworldと書かれたメッセージボックスが出てくるようにしてみようかな。

ボタンの付け方はいろんな人が解説されているのでそちらを見てもらうことにして、要点だけ。

node-addon-apiのHello Worldサンプルでbuild/Releaseのフォルダにhello.nodeというファイルが出来ているのでこれをElectronのサンプルの方にコピーしてきます。どこにどういうフォルダ名で入れておくのが作法的に良いのか分からなかったので、プロジェクトのフォルダの直下にbuild/Releaseのフォルダを作ってそこにhello.nodeを入れました。

そして、ボタンがクリックされたときのイベントハンドラでこのhello.nodeの場所を相対パスで指定します。下の例だとrequireメソッドで指定しているところです。変数addonからC++のhello()メソッドが呼び出せるようになりました。

// IPC handler
ipcMain.handle('click-event', async (_e, _arg) => {
  

  var addon=require('../build/Release/hello.node'); 
  console.log(addon.hello());


  const options = {
    type: 'info',
    title: 'quick start',
    message: addon.hello(),
    detail: 'description'
  };

  dialog.showMessageBox(options);
});

実行してボタンを押してみるとC++のメソッドが返した文字列worldが表示されます。
なんとかできました~🎉

まとめ

久しぶりにWindowsで環境構築してみたけれど、正気の沙汰じゃなかった。

参考

distutils is removed in Python 3.12 #2869

Build cross-platform desktop apps with JavaScript, HTML, and CSS | Electron

Electron App with C++ backend as Native Addon (Napi)

Electronでデスクトップアプリをサクッと作る(ボタンイベント付き)