LoginSignup
63
62

More than 5 years have passed since last update.

Native Addonを作ってElectronで使ってみた

Posted at

はじめに

Electron触ってみようの会でやってみたことのまとめです。
当日は全然時間が足りなかったので持ち帰ってから完成させました。

とりあえずElectronは初めてでしたが、せっかくなので普通のWebアプリではできないNative Addonを自分で作って使ってみようと思いました。
Native AddonとはC++などのネイティブ言語で実装されたNode.jsモジュールです。

Electronのバージョンは0.32です。
今回作成したソースコードは https://github.com/likr/20150915electron に置いています。

Native Addonを作る

何か重めの処理をさせようということで、雑にマンデルブロ集合を計算してみます。
Canvasと連携することを想定して、JavaScript側からビットマップを表すUint8ClampedArrayを受け取り、ピクセルごとに色を決める計算を行うようにします。

C++のソースコード全体は以下の通りです。
マンデルブロ集合の計算を行う部分と、JavaScriptのオブジェクトからポインタの取り出しを行っている部分、モジュールの登録を行う部分で成り立っています。
パラメータは決め打ちで、エラー処理などもすっ飛ばしています。

mandelbrot.cc
#include <node.h>

using namespace v8;

void runMandelbrot(unsigned char* array, int width, int height, double pixel_size, double x0, double y0) {
  for (int j = 0; j < height; ++j) {
    for (int i = 0; i < width; ++i) {
      double cr = x0 + pixel_size * i;
      double ci = y0 + pixel_size * j;
      double zr = 0.0f;
      double zi = 0.0f;
      double zrzi, zr2, zi2;
      int k;
      for(k = 0; k < 256; k++) {
          zrzi = zr * zi;
          zr2 = zr * zr;
          zi2 = zi * zi;
          zr = zr2 - zi2 + cr;
          zi = zrzi + zrzi + ci;
          if(zi2 + zr2 >= 2.0) {
              break;
          }
      }
      if(k > 255){
          k = 255;
      }

      int index = j * width + i;
      array[4 * index] = array[4 * index + 1] = array[4 * index + 2] = 255 - k;
      array[4 * index + 3] = 255;
    }
  }
}


void Method(const FunctionCallbackInfo<Value>& args) {
  Local<Uint8ClampedArray> array = args[0].As<Uint8ClampedArray>();
  unsigned char* ptr = (unsigned char*)array->Buffer()->GetContents().Data();
  runMandelbrot(ptr, 300, 200, 0.01, -2.0, -1.0);
}

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

NODE_MODULE(addon, init)

Native AddonのAPIは何度か大幅な変更が入っていて、Electronで使用されているnode.jsのバージョンと利用するAPIを合わせる必要があります。
Electron 0.32ではio.js 3.3.0が使われているので、C++の実装もそれに合わせて行っています。
C++のコードはio.js DocumentationV8 API Reference Guideを参考にしながら書きました。

Native Addonはnode-gypを使ってビルドすることができます。
今回は、以下のような設定ファイルになります。

binding.gyp
{
  "targets": [
    {
      "target_name": "mandelbrot",
      "sources": ["mandelbrot.cc"]
    }
  ]
}

node-gypコマンドを実行するだけでビルドができます。

$ node-gyp

ビルドに成功すると./build/Release/mandelbrotにモジュールが配置され、以下のように通常のモジュールと同じように使うことができます。。

test-mandelbrot.js
var mandelbrot = require('./build/Release/mandelbrot');
var b = new Uint8Array(300 * 200 * 4);
mandelbrot.mandelbrot(b);
console.log(b);

しかし、残念ながらこのままではElectronから読み込むことができないので、以下のようにElectron向けにビルドをします。
他にもelectron-rebuildを使う方法などがあります。(Using Native Node Modules)

$ HOME=~/.electron-gyp node-gyp rebuild --target=0.32.0 --arch=x64 --dist-url=https://atom.io/download/atom-shell

Electronから呼び出す

Electronアプリを作っていきます。
package.jsonを作成し、Quick Startを参考にし、以下のようなmain.jsを作りました。

main.js
var app = require('app');
var BrowserWindow = require('browser-window');

require('crash-reporter').start();

var mainWindow = null;

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

app.on('ready', function() {
  mainWindow = new BrowserWindow({width: 800, height: 600});
  mainWindow.loadUrl('file://' + __dirname + '/index.html');
  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});

以下が画面側のコードです。
ボタンを押すとマンデルブロ集合を計算した結果をCanvasに描画します。

index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Native Mandelbrot</title>
</head>
<body>
<div>
  <button onclick="run()">Run</button>
</div>
<div>
  <canvas id="screen" width="300" height="200"></canvas>
</div>
<script>
var remote = require('remote');
var mandelbrot = require('./build/Release/mandelbrot');

function run() {
  var canvas = document.getElementById('screen');
  var context = canvas.getContext('2d');
  var imageData = context.createImageData(300, 200);
  mandelbrot.mandelbrot(imageData.data);
  context.putImageData(imageData, 0, 0);
}
</script>
</body>
</html>

Native Addonの使い方はtest-mandelbrot.jsと特に変わっていません。
imageData.dataがピクセルを表すUint8ClampedArrayです。
Native Addon内の処理が終わったらcontext.putImageDataでCanvasに描画します。
Native AddonはMain Processだけでしか使えないのかと思いきやRender Processでもrequireしてしまえば普通に使えるんですね。

実行してボタンを押すとこんな感じになります。

kobito.1442388864.608266.png

まとめ

ElectronもNative Addonもどちらも意外と簡単にできました。
Native Addonで科学技術計算などのJavaScriptでは追いつかないような重い処理をさせながら、UIをWebベースで作るといったことも実現できそうでした。

63
62
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
63
62