今回は「Electronにおけるプロセス間通信」についてまとめます。
Electronにおけるプロセス
Electronはブラウザ実装と似ていて「メインプロセス
」と「レンダラープロセス
」の2つのプロセスが存在しています。
この2つのプロセス間で「IPC (Inter-Process Communication)
」という仕組みを使って情報をやりとりしながらイベント駆動のアプリケーションを開発するものになります。
本記事では、この「メインプロセス」⇔「レンダラ―プロセス」間の情報のやり取りをどのように実装していくか、についてまとめます。
レンダラープロセス起点
デスクトップアプリケーションなので、基本的にはGUIの要素に対する操作(イベント)をトリガーに処理が行われます。 「画面側のイベント→メインプロセスで処理→画面側に反映」の流れが基本です。
ここでは、同期通信と非同期通信のそれぞれについて実装例を見ていきます。
同期通信(sendSync() → event.returnValue)
画面側の操作に対して同期的にメインプロセスへ処理を渡し、同期的に応答を受け取る実装です。
画面からは ipcrenderer.sendSync()
でメインプロセスへイベント送信し、メインプロセス側は ipcMain.on()
であらかじめイベントハンドラを定義しておき受信します。
再度、メインプロセス側から画面側へ同期的にデータを返す時は event.returnValue
に値を設定して返します。
/src/index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Electron</title> <link rel="stylesheet" href="./third_party/bootstrap/4.5.0/css/bootstrap.min.css"> </head> <body> <div class="container"> <input id="btnExecSync" type="button" class="btn btn-primary" value="同期実行" /> <input id="txtMessage" type="text" class="form-control" /> </div> <script type="text/javascript"> window.$ = window.jQuery = require("./third_party/jquery/3.5.1/jquery.min.js"); var { ipcRenderer } = require("electron"); $("#btnExecSync").on("click", (event) => { var retval = ipcRenderer.sendSync("btnExecSync_onclick", { message: "hello" }); $("#txtMessage").val(retval.message); }); </script> </body> </html>
/src/main.js
const { app, BrowserWindow, ipcMain } = require("electron"); var initialize = function () { var win = new BrowserWindow({ width: 1024, height: 768, webPreferences: { nodeIntegration: true } }); ipcMain.on("btnExecSync_onclick", (event, arg) => { console.log(arg.message); event.returnValue = { message: "sync event" }; }); win.loadFile("./index.html"); }; app.whenReady().then(initialize);
非同期通信(send() → event.reply() → on())
画面側から非同期でメインプロセス側へ通信する方法です(非同期と言いながらもメインプロセスが同期的に重たい処理を実行すると画面が固まるので注意必要ですが…)。
画面側からメインプロセス側へ通信する際は ipcRenderer.send()
でイベントを送り、
メインプロセス側はあらかじめ ipcMain.on()
でイベントハンドラを設定しておいて受信します。
メインプロセス側から画面側へ返す時は event.reply()
に設定することで非同期で情報を戻すことができます。
/src/index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Electron</title> <link rel="stylesheet" href="./third_party/bootstrap/4.5.0/css/bootstrap.min.css"> </head> <body> <div class="container"> <input id="btnExecAsync" type="button" class="btn btn-primary" value="非同期実行" /> <input id="txtMessage" type="text" class="form-control" /> </div> <script type="text/javascript"> window.$ = window.jQuery = require("./third_party/jquery/3.5.1/jquery.min.js"); var { ipcRenderer } = require("electron"); $("#btnExecAsync").on("click", (event) => { ipcRenderer.send("btnExecAsync_onclick", { message: "hello" }); }); ipcRenderer.on("btnExecAsync_oncompleted", (event, arg) => { $("#txtMessage").val(arg.message); }); </script> </body> </html>
/src/main.js
const { app, BrowserWindow, ipcMain } = require("electron"); var initialize = function () { var win = new BrowserWindow({ width: 1024, height: 768, webPreferences: { nodeIntegration: true } }); ipcMain.on("btnExecAsync_onclick", (event, arg) => { console.log(arg.message); event.reply("btnExecAsync_oncompleted", { message: "async event" }); }); win.loadFile("./index.html"); }; app.whenReady().then(initialize);
非同期通信(invoke() → handle())
こちらも非同期で画面側からメインプロセス側へ通信する方法です。
メインプロセスで重たい処理すると画面が固まるのはこちらも同様です。
画面が固まらないようにするには、メインプロセス側でマルチプロセスや setTimeout()
の利用が必要ですね。
さて、画面側から発生したイベントに対して簡単な処理をして戻すケースの場合、前述の send()
→ event.reply()
は少し実装が面倒です。
なので、単純なものであればここで紹介する ipcRenderer.invoke()
、 ipcMain.handle()
を利用すると便利です。
ipcRenderer.invoke()
と ipcMain.handle()
はセットで利用しないといけない点には注意してください。
/src/index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Electron</title> <link rel="stylesheet" href="./third_party/bootstrap/4.5.0/css/bootstrap.min.css"> </head> <body> <div class="container"> <input id="btnExecInvoke" type="button" class="btn btn-primary" value="Invoke実行" /> <input id="txtMessage" type="text" class="form-control" /> </div> <script type="text/javascript"> window.$ = window.jQuery = require("./third_party/jquery/3.5.1/jquery.min.js"); var { ipcRenderer } = require("electron"); $("#btnExecInvoke").on("click", async (event) => { var retval = await ipcRenderer.invoke("btnExecInvoke_onclick", { message: "hello" }); $("#txtMessage").val(retval.message); }); </script> </body> </html>
/src/main.js
const { app, BrowserWindow, ipcMain } = require("electron"); var initialize = function () { var win = new BrowserWindow({ width: 1024, height: 768, webPreferences: { nodeIntegration: true } }); ipcMain.handle("btnExecInvoke_onclick", async (event, arg) => { console.log(arg.message); return Promise.resolve({ message: "invoke event" }); }); win.loadFile("./index.html"); }; app.whenReady().then(initialize);
メインプロセス起点
ファイル監視やサーバープッシュなどあればメインプロセス起点でイベント発火させるケースもあると思います。 そんなときはここで紹介する「メインプロセス起点」のイベント処理をじっそうします。
非同期通信(send() → on())
メインプロセス起点で画面側へイベント発火する場合、 BrowserWindow
のインスタンスにある WebContents
の send(
) メソッドを利用します(たどり着くまでが長い…!)。
画面側は ipcRenderer.on() でイベントハンドラ設定しておけば、イベント受信して処理ができます。
/src/index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Electron</title> <link rel="stylesheet" href="./third_party/bootstrap/4.5.0/css/bootstrap.min.css"> </head> <body> <div class="container"> <input id="txtMessage" type="text" class="form-control" /> </div> <script type="text/javascript"> window.$ = window.jQuery = require("./third_party/jquery/3.5.1/jquery.min.js"); var { ipcRenderer } = require("electron"); ipcRenderer.on("timer_tick", (event, arg) => { $("#txtMessage").val(arg.message); }); </script> </body> </html>
/src/main.js
const { app, BrowserWindow, ipcMain } = require("electron"); var initialize = function () { var win = new BrowserWindow({ width: 1024, height: 768, webPreferences: { nodeIntegration: true } }); global.setTimeout(() => { win.webContents.send("timer_tick", { message: "Hello World !" }); }, 1000); win.loadFile("./index.html"); }; app.whenReady().then(initialize);
今回は「Electronにおけるプロセス間通信」についてまとめました。 参考になったでしょうか? 本記事がお役に立っていると嬉しいです!!