Electronのプロセス間の通信
MainProcess(BrowserProcess)とRendererProcess間の通信方法についてメモ。
プロセス間の通信方法は何通りかありますが、順番に見ていきます。
remoteモジュール使ってMainProcess側としてモジュールを読み込む方法
remote経由でのrequire。これもある意味プロセスをまたいだ処理、ということで。。
MainProcess用のモジュールをRendererProcessから使いたい時や、重たい処理をするコードをreqireするときに使ったりします。
今までも普通に書いてたけど、こういうヤツです。
index.ts(RendererProcessのコード)
var remote = require('remote'); var app = remote.require('app'); var BrowserWindow = remote.require('browser-window');
remote.require(xxxx)
とすると、そのモジュールはMainProcess側のプロセスで動作します。
また、こうすることで、「app」や「browser-window」のようなMainProcess用のモジュールも、RendererProcessから利用することができるようになります。
RendererProcess⇒MainProcessへの通信(ipcモジュール)
今度はより汎用的なプロセス間の通信をしてみます。
まずはRendererProcessで何かの操作をしたら、そのタイミングでMainProcessに通知してみます。
RendererProcessとMainProcessの間で汎用的なプロセス間通信を行うには、「ipc」モジュールを利用します。
ipcモジュールでRendererProcess⇒MainProcessの通信をするときには、同期/非同期の二通りの通信方法が用意されてます。
非同期な通信
非同期な通信では、送信側からでipc.send
メソッドでメッセージを送り、受信側ではipc.on
でハンドラを登録してメッセージ受信時の動作を定義します。
こんな感じ。
ここでは、htmlで適当にボタンを用意しておき、onclickでhello()メソッドを呼ぶようにしておきます。
そして、hello()メソッドでは、Renderer⇒Mainへとasync-message
というメッセージを送り、Mainプロセスでは処理が完了したら、RendererProcessへasync-reply
というメッセージで処理の完了を通知しています。
送信側
index.ts(RendererProcessのコード)
var ipc:any = require('ipc'); function hello(){ ipc.send('async-message', 'ping'); } // MainProcessでの処理完了通知をハンドルするため ipc.on('async-reply', function(arg) { alert(arg); });
受信側
main.ts(MainProcessのコード)
ipc.on('async-message', (event, arg) => { console.log(arg); event.sender.send('async-reply', 'pong'); });
同期的な通信
同期的な通信を行う場合、MainProcess側での処理が完了するまでRendererProcessがブロックされてしまうため、基本的に非推奨となってます。
なので、あまり使うことはないかと思いますが、一応使い方をメモだけしときます。
同期通信では、処理の完了の返し方が違うので注意。
MainPrcessでevent.returnValueプロパティに値をセットするだけで、同期通信の完了通知となります。
index.ts(RendererProcessのコード)
var ipc:any = require('ipc'); function hello(){ var result = ipc.sendSync('sync-message', 'ping'); alert(result); }
main.ts(MainProcessのコード)
ipc.on('sync-message', (event, arg) => { console.log(arg); event.returnValue = 'pong'; });
MainProcess⇒RendererProcessへの通信(webContents.sendメソッド)
今度はMainProcessからRendererProcessへとメッセージを送ってみます。
例えば、MainProcessでメニューとか作った時に、メニューのclickイベントでRendererProcess側に何かメッセージを送って、処理をする、といった場面で使えるかと思います。
RendererProcessへメッセージを送るには、目的のBrowserWindowのインスタンスのwebContentsプロパティを取得し、webContents.send
メソッドでメッセージを送ります。
受信側は、先ほどの例と同じようにipc.on
で受け付けます。
また、この通信方法では、同期的な通信はサポートされておらず、非同期な通信のみサポートされています。
同期的な処理はデッドロックの原因になったり、なにかと問題を起こすので、同期通信は非サポートとのこと。
送信側
sendメソッドで送るだけ。RendererProcessに何か引数をつけて通知することもできます。 main.ts(MainProcessのコード)
mainWindow.webContents.send('menu-clicked', 'メッセージの引数');
受信側
on
で登録するコールバック関数には、sendで指定した引数が渡ってきます。
index.ts(RendererProcessのコード)
ipc.on('menu-clicked', (msg) => { alert(msg); });
サンプル
こんな内容のサンプルです。
- MainProcessでアプリケーションメニューを作る
- そのメニューの項目をクリックしたら、クリックした項目に応じたメッセージをRendererProcessに通知
- RendererProcess側では、メッセージを受け取ったら、その内容をalertで表示
全体のコードはこんな感じ。
main.ts(MainProcessのコード)
import app = require('app'); import BrowserWindow = require('browser-window'); import Menu = require('menu'); import ipc = require('ipc'); require('crash-reporter').start(); // メインウィンドウの参照をグローバルに持っておく。 var mainWindow: GitHubElectron.BrowserWindow = null; var menu = Menu.buildFromTemplate([ { label: 'File', submenu: [ {label: 'New File', click: () => onMenuClicked('New File')}, {label: 'Save', click: () => onMenuClicked('Save')} ] }, { label: 'Edit', submenu: [ {label: 'Copy', click: () => onMenuClicked('Copy')}, {label: 'Paste', click: () => onMenuClicked('Paste')}, ] } ]); Menu.setApplicationMenu(menu); // すべてのウィンドウが閉じられた際の動作 app.on('window-all-closed', function() { // OS X では、ウィンドウを閉じても一般的にアプリ終了はしないので除外。 if (process.platform != 'darwin') { app.quit(); } }); app.on('ready', function() { // 新規ウィンドウ作成 mainWindow = new BrowserWindow({ width: 800, height: 600 }); // index.htmlを開く mainWindow.loadUrl('file://' + __dirname + '/index.html'); // ウィンドウが閉じられたら、ウィンドウへの参照を破棄する。 mainWindow.on('closed', function() { mainWindow = null; }); }); function onMenuClicked(itemName: string){ // クリックされた項目の情報を、RendererProcessに通知 mainWindow.webContents.send('menu-clicked', itemName); }
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello World!</title> <script src="index.js"></script> </head> <body> <h1>Hello World!</h1> <hr/> </body> </html>
index.ts(RendererProcessのコード)
var ipc:any = require('ipc'); ipc.on('menu-clicked', (msg) => { alert(msg + ' clicked!!'); });