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><metacharset="UTF-8"><title>Hello World!</title><scriptsrc="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!!'); });