ElectronとC++開発環境

![screen](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76058/c951422f-ed2b-e43d-6cee-2ac9d8ee56e0.png)

19/07/01 Qiitaに画像ファイルがアップロードしにくい（やたら時間がかかってる）ので、あとでアップロードします。

## 今回のコンセプト

- C++部分はElectronと連動しながらVisual Studio 2017でデバッグする。（C++11に対応していたら別バージョンでも使えるらしい）
- node.jsはnodist仮想環境を使う（node.jsのバージョンが合うなら他の方法でも可能）
- ビルド時にpython2.7使うのでAnacondaの仮想環境を使う（python2.7が動けば他の方法でも可）
- node.jsパッケージはプロジェクトディレクトリのnode_modulesに保存して、そこから使う。
- `electron-rebuild`は使わず`node-gyp`を使う。
- Electronは5.0.5（新しいバージョンでやってみたいだけ）
- 作りやすく、やり直しやすい環境構築を目指す。

## 開発環境

- Windows 10 (64bit)
- Visual Studio 2017
- [nodist](https://github.com/nullivex/nodist) : node.js環境作成
- [Anaconda](https://www.anaconda.com/) : 一部python2.7バージョンを使うため

## C++で開発する2種類の方法

### native addon

node-gypを使ってC++をビルド。こちらを使う。

### ffi

`npm install ffi`でインストールする方法。
この方法は、かなり制限（WinではElectronがバージョン3（nodeのバージョンのため）までしか対応してないとか、エラー地獄にあったりとか）があるので今回は使用しない。

## nodistの設定

今回はElectron5.0.5を使うのでnode.js12以上が必要です。
とりあえず以下を実行して使いたいバージョンを用意。
Electronに必要な対応するnode.jsのバージョンは[Release note](https://electronjs.org/releases/stable)に書いてあります。

```bash
rem 使用可能なnodeバージョン一覧
nodist dist
rem 使いやすそうなnode.jsのバージョンを選択してダウンロード
nodist + 12.4.0
rem ダウンロード済みの中で使用したいバージョンを指定
nodist use 12.4.0
rem 恒久的に今後同じバージョンを指定する場合はglobalで設定する
nodist global 12.4.0
```


## Anacondaの設定

`node-gyp`や`electron-rebuild`とかのライブラリでpython2.7を使うのでAnacondaにインストール


```bash
rem py27という名前で作成
conda create -n py27 python=2.7
```


## プロジェクトの作成

![undefined]()


普通にフォルダ作ってるだけです。

```bash
rem 例としてexampleという名前
mkdir examples
cd examples
```


## プロジェクト作成

### package.json作成

```bash
rem conda でpython27に変更
activate py27
rem nodistでnode.jsのバージョンを指定
nodist use 12.4.0
rem node初期化
rem 対話型で内容を設定
npm init
```


### Electronとnode-gypをインストール

```bash
rem プロジェクトに保存してそこから実行する予定
npm install --save-dev electron@5.0.5
npm install --save-dev node-gyp@5.0.1
```

なんかエラー出たときは、package-lock.jsonを削除するとうまくいくかも

## ファイルを用意


Hello Worldします。

[node/test/addons/hello-world at master · nodejs/node · GitHub](https://github.com/nodejs/node/tree/master/test/addons/hello-world)

スクリプトはこちらを参考にしました。

[node-gypまたはnanを使ってnative addonを作る方法 - DJ lemon-Sour's diary (prod.hisasann)](https://hisasann.github.io/2017/02/26/node-native-addon/)

### HTML

ファイル名は`index.html`

```html:index.html
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Example</title></head>
<body>
<div id='test'></div>
<button onclick="testFunc()">test button</button>
<script>
    function testFunc(){
        var remote = require('electron').remote;
        var testC = require('./build/Debug/testFunction');
        document.getElementById('test').innerText = "hello " + testC.hello();
    }
</script>
</body>
</html>
```


### Electron用JS

ファイル名は`index.js`


```js:index.js
"use strict";
const electron = require("electron");
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
let mainWindow;

// 閉じたときのイベント
app.on('window-all-closed', function() {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

// 初期化後
app.on('ready', function() {
  mainWindow = new BrowserWindow({
      width: 800, height: 600,
      webPreferences: {
          // Electron5以降ではこれがFalseになっていて、index.html側でrequireが使えないのでTrueにする（後述）
        nodeIntegration: true
  }
  });
  mainWindow.loadURL('file://' + __dirname + '/index.html');
  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});
```


#### 注意

今回は簡単に説明したいのでnodeIntegrationという値をtrueにしてますが、セキュリティ的に宜しく無いらしいので、適当に変更してください。

[Electron Webviewのセキュリティで注意すべきこと - Qiita](https://qiita.com/RyosukeCla/items/6f072947bfcba3819ef0)

### package.json

package.jsonは先の初期化時に生成されているはずですが、` "main": "index.js",`となっていない場合（index.jsがメインファイルでない場合）は、そのように直しておく。

### binding.gyp

node-gypが使用するビルド用の設定ファイル。
ファイル名は`binding.gyp`で固定

```python:binding.gyp
{
  "targets": [
    {
      "target_name": "testFunction",
      "sources": [
        "test.cc"
      ]
    },
  ]
}
```


### test.cc

```c++:test.cc
#include <node.h>
#include <v8.h>

void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  v8::HandleScope scope(isolate);
  args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "world"));
}

void init(v8::Local<v8::Object> exports) {
  NODE_SET_METHOD(exports, "hello", Method);
}

NODE_MODULE(binding, init);
```


## ビルド

ファイルができたらビルドしてみる。

[Using Native Node Modules \| Electron](https://electronjs.org/docs/tutorial/using-native-node-modules)

`electron-rebuild`を使う方法もあるが、環境によってはうまく使いたいnode-gypのバージョンで使えなかったのでnode-gypでビルドする。

```bash
rem conda でpython27に変更
activate py27
rem nodistでnode.jsのバージョンを指定
nodist use 12.4.0
rem プロジェクトディレクトリに保存したnode_modules内のnode-gypを使ってビルド
.\node_modules\.bin\node-gyp.cmd rebuild --target=5.0.5 --arch=x64 --msvs_version=2017 --dist-url=https://electronjs.org/headers --debug
```


引数は以下

|引数|値|内容|
|:--|:--|:--|
|--target|5.0.5|Electronのバージョン|
|--arch=x64|x64|32bitか64bitか|
|--msvs_version|2017|Visual Studioのバージョン|
|--dist-url|`https://electronjs.org/headers`|この引数を加えることでElectronのビルドであることをnode-gypに伝える|
|--debug||デバッグでビルド|

[引数一覧](https://github.com/nodejs/node-gyp/blob/master/README.md#commands)

## 実行

以下で実行。
ウィンドウが表示されて、ボタンを押したらHelloWorldが表示されればOK

```bash
.\node_modules\.bin\electron .
```


## デバッグ

Visual Studio でデバッグしたいのでデバッグ用のプロジェクトを新たに作成します。

Visual Studio 2017で新しく「空のプロジェクト」を作成します。

### 対象のファイルを追加

プロジェクト名を右クリック->追加->既存の項目(G)...を選択して、デバッグの対象にしたいC++ファイル`test.cc`を選択して追加。

追加されたファイルを右クリックしてプロパティを開く。

構成プロパティ->全般->ビルドから除外

を「はい」に設定。

### デバッグ用のコマンドを設定

プロジェクトのプロパティ->構成プロパティ->デバッグ->コマンド

```bash
$(ProjectDir)..\..\node_modules\.bin\electron.cmd
```


同じく、コマンド引数

```bash
$(ProjectDir)..\..\.
```


に設定。

### 実行

設定後、普通にデバッグ->デバッグの開始を選択か、「ローカル Windows デバッガー」ボタンをクリック

### アタッチ

`デバッグ->プロセスにアタッチ`を選択して、`electron`という項目を探して、「タイトル」が空白のものを選択してアタッチする。(２個項目があるが、恐らくUIDが後の方だと思うが、シフトキーを押しながら複数選択のほうが確実かも)

ソリューションエクスプローラーの`testFunction/test.cc`の`Method`関数のどこかにブレークポイントを設定して、Electron上のボタンを押すと、そのブレークポイントで停止します。これでデバッグができるようになりました。

#### アタッチについて

デバッグ実行ごとに上記アタッチの作業が必要になる。
`node_modules\electron\dist\electron.exe`をデバッグ時に自動的にアタッチできればもう少し楽できるかも。

### その他設定

絶対ではないけど、やっておくと便利程度

#### デバッグ時にビルドも

今回のデバッグの設定はビルドをしていない。
ビルドもしておきたい場合は以下のようなバッチファイルを`build.cmd`という名前で用意して、

```
cd %~dp0
call activate py27
call nodist use 12.4.0
node_modules\.bin\node-gyp.cmd rebuild --target=5.0.5 --arch=x64 --msvs_version=2017 --dist-url=https://electronjs.org/headers --debug
```

ビルド前コマンドに以下のような内容を書いておけばビルドもされる。

```
..\..\build.cmd
```


## 説明

### Electronとnode.jsとNODE_MODULE_VERSIONについて

Electronの指定をしないで普通のnode.jsでnode-gypを使ってビルドすると、`NODE_MODULE_VERSION`が違う旨のエラーが表示される。
`NODE_MODULE_VERSION`はABI（アプリケーションバイナリインタフェース）のバージョン番号で、普通のnode.jsとelectronと違うものが振られており、たとえ同じnode.jsとnpmのバージョンを使用してnode-gypでビルドしても上手く動作してくれません。（どこかに[コレではないリストで](https://nodejs.org/ja/download/releases/)Electronも含めたNODE_MODULE_VERSION一覧表があったはずだがURLを忘れた）

[Electron から native module を使うときの NODE_MODULE_VERSION のエラー - 理系学生日記](https://kiririmode.hatenablog.jp/entry/20190216/1550245950)


## 参考

[Using Native Node Modules \| Electron](https://electronjs.org/docs/tutorial/using-native-node-modules)

[リリース一覧 \| Node.js](https://nodejs.org/ja/download/releases/)

[Electron から native module を使うときの NODE_MODULE_VERSION のエラー - 理系学生日記](https://kiririmode.hatenablog.jp/entry/20190216/1550245950)

### その他参考

[Electronアプリを公開するまでにやったことノウハウまとめ - Qiita](https://qiita.com/toshi3221@github/items/2420a6188dc18acc2c64)

## とはいえ

デバッグ毎にアタッチするのはめんどくさいので、C++側はCLIとかで別途デバッグして、UI部分との結合用に使う程度が良いかも。