Electron で Windows アプリを作ろうとして node-ffi でハマった話

# やりたかったこと

Electron で Windows アプリを作り、 Electron 側から既成の dll が使えることを確認することです。

ゴールとしては、

1. C++ で "Hello, world!" という文字列を返却する HelloWorld 関数を作る
2. Electron(JavaScript)側から HelloWorld 関数を呼び出し、alert で表示する

というものです。

# ハマったポイント

npm で node-ffi をインストール後、JavaScript 側で require('ffi') をやるとエラーが発生します。これがなかなか解消できず、ハマりました。

↓こういうエラー

```
Uncaught Error: A dynamic link
library (DLL) initialization routine failed.
```

厄介なのは、JavaScript として、【require('ffi')】を呼び出した段階でエラーになってしまうこと。なぜなら、ffi を **npm でインストールしただけ** だから。
こうなると、**インストールしただけではダメなのか？** という疑問が出てくる。

# いろいろ調べると・・・

**npm でインストールしただけでは使えなさそう** な臭いがプンプンしてきます。
で、だいたい出てくる情報は、

### Electron で使っている V8 エンジンが Node.js の V8 エンジンと違うので、Electron 用にパッケージをリビルドしないといけない

という情報です。
で、それについて調べると出てくるのが、
### [electron-rebuild](https://github.com/electron/electron-rebuild)
　
です。

StackOverflow でも「解決策見つけたよ」と言っていた人が [ここ](https://github.com/electron/electron/blob/master/docs/tutorial/using-native-node-modules.md) を紹介していて、
ページの中段辺りの **Installing modules and rebuilding for Electron** で思いっきり書いてある、と。

そうなると期待を膨らませて実行するわけですよ。

```
$ npm install electron-rebuild
$ ./node_modules/.bin/electron-rebuild
$ ﾚRebuild Complete
```

よし！**リビルド完了！**(★注目★)

```
$ ./node_modules/.bin/electron .
```
Electron 実行！

```
Uncaught Error: A dynamic link
library (DLL) initialization routine failed.
```

ぐぬぬ！

割と最近の記事で、↓の記事の人も electron-rebuild やっている・・・。うーん・・・。
[【Electron】Felica連携デスクトップアプリケーションの開発](https://qiita.com/yosida001/items/3051565dce7eff620273)

というわけで、真にハマったところは、この [electron-rebuild](https://github.com/electron/electron-rebuild) なのです。


# そして解決へ

こういうダダハマリした場合って、けっこう手前(前提的なところ)に落とし穴があって、
今回もその例から外れることなく、結果から見ると非常に単純な理由でした。

【(★注目★)】と書いたところの **electron-rebuild**。
#### こいつ、リビルドしてないぞ！

リビルドって言ってる割には一瞬で終わるのですよ、**electron-rebuild** コマンド。

で、いろいろ試した結果、どうすれば **electron-rebuild** でリビルドしてくれるのかというと、

### npm でインストールするときに -D(--save-dev) を付けない

ということです。
そして残念なことに

### -g でのグローバルインストールで統一してもダメ

です。(一番解決できそうな組み合わせなのに・・・)

で、結論です。

### ★ node-ffi だけは npm install で -g, -D(--save-dev) を付けない ★

です。


# 実際にやったことなど

## まず、状況の確認

- Windows 10 Pro 1803(April 2018 Update)
- Visual Studio 2017 Community(VC++インストール済み)
- Node.js 8.11.4(64ビット版)

↓Node バージョンやパッケージの状態↓

```
$ node -v
v8.11.4
$ npm -v
6.4.1
$ npm list -g --depth=0
npm
$ npm list --depth=0
なし
```

# パッケージをインストールする

```
$ npm i electron
$ npm i electron-rebuild
$ npm i ffi
```

↓インストールした結果の package.json↓

``` package.json
{
  "name": "electron_ffi_sample",
  "main": "main.js",
  "author": "odorry",
  "scripts": {
    "start": "node_modules/.bin/electron ."
  },
  "dependencies": {
    "electron": "^2.0.9",
    "electron-rebuild": "^1.8.2",
    "ffi": "^2.2.0"
  }
}
```

npm i(install) でインストールしたので、
dependencies のところにインストールしたものが書かれている。

# Electron の V8 エンジン用にリビルドする

これですね。肝は。
このコマンドで、リビルドにある程度の時間がかかれば OK です。
(5 〜 10 秒くらいで、少なくとも一瞬ではない。)

```
$ ./node_modules/.bin/electron-rebuild
ﾚ Rebuild Complete
```

# Visual Studio の VC++ で dllSample.dll を作成する

Visual Studio の使い方になってしまうので、詳細は割愛しますが、
Visual Studio でビルドして **DllSample.dll** を手に入れます。

``` DllSample.h
#include <iostream>

extern "C" __declspec(dllexport) const char * HelloWorld();
```

``` DllSample.cpp
#include "stdafx.h"
#include "DllSample.h"

const char * HelloWorld()
{
	const char *cstring = "Hello, World!";
	return cstring;
}
```

これをビルドして DllSample.dll を手に入れてください。

# Electron アプリ(html)側を用意する

### ファイル構造はこのような感じです
```
任意のフォルダ
┣ node_modules      // npm install でインストールされたパッケージ
┣ DllSample.dll     // Visual Studio 側で作成した C++ の DLL
┣ index.html        // Electron アプリの最初の画面
┣ main.js           // Electron アプリの起動スクリプト
┣ package.json      // Node パッケージ用のファイル
┗ package-lock.json // npm install で勝手に作られるファイル
```


``` main.js
const {app, BrowserWindow} = require('electron')

let win

app.on('ready', () => {
  win = new BrowserWindow({width: 800, height: 600})

  win.loadURL('file://' + __dirname + '/index.html')

  win.on('closed', () => {
    win = null
  })
})

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  if (win === null) {
    createWindow()
  }
})
```

``` index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>DLL呼び出しサンプル</title>
</head>
<body>
    <script>
        var ffi = require('ffi');

        var dllSamplePath = "./DllSample.dll";
        var dllSample = ffi.Library(dllSamplePath, {
            'HelloWorld': ['string', []]
        });

        var result = dllSample.HelloWorld();
        alert(result);
    </script>
</body>
</html>
```

# Electron アプリとして実行してみる

```
$ ./node_modules/.bin/electron .
```

↓実行結果です↓
![electron_ffi_sample.png](https://qiita-image-store.s3.amazonaws.com/0/1339/c7e4f922-ede4-11db-f8e1-b92dfa481500.png)

ちゃんと狙い通りの **【Hello, world!】** が表示されています。

# まとめ

- ffi をインストールするときは -g, -D(--save-dev) を付けない
- ffi をインストール後に electron-rebuild でリビルドする

# おまけ

NAN の場合も同様で electron-rebuild によるリビルドが必要です。
その際、node-gyp で clean, configure を行い、
その後に electron-rebuild すれば OK です。

```
$ ./node_modules/.bin/node-gyp clean
$ ./node_modules/.bin/node-gyp configure
$ #./node_modules/.bin/node-gyp build // ←これをやらずに
$ ./node_modules/.bin/electron-rebuild // ←代わりにこっちを実行する
```