Electronのプロセス間通信について

Electronのプロセス間通信(IPC:Inter-Process Communication)について触れる前にElectronに関する簡単な仕組みを見ていきます。


今回の話はElectronを試してみようの続きになります。


前回の内容で

.
├── index.html
├── main.js
├── node_modules
├── package.json
└── package-lock.json

のようなシンプルなファイル構造を構築しました。


エンドポイント(起動時に始めに実行されるファイル)がmain.jsで、UIに関するファイルがindex.htmlになります。


この時、Electronでは、



main.jsの方をメインプロセスと呼び、index.htmlの方をレンダラープロセスと呼びます。

メインプロセスの方ではNode.jsの機能を使用し、レンダラープロセスの方ではHTML Living Standard(上の図ではHTML5のロゴを用いています)の機能を使用し、どちらのプロセスも独立していて、お互いのプロセスを干渉し合うといったことはありません。

Node.js

HTML Standard


Electronでコードを作成していくと、レンダラープロセスの方から値を送信して、メインプロセスの方で受け取った値を元に何らかの処理をしたりとか、レンダラープロセスでメインプロセスの機能を使いたいといったことが生じます。


ここで利用するのが、



プロセス間通信(IPC:Inter-Process Communication)になります。

プロセス間通信をどのように活用するのか?を簡単なコードを用いて見ていくことにしましょう。




レンダラープロセスから何らかの値を送信して、メインプロセスで受け取った値をconsole.log()で出力することを見てみます。



レンダラープロセスの方を上のキャプチャのようになるようにindex.htmlを編集します。


index.html

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
</head>
<body>
	<input type="text" id="name">
	<button id="send">送信</button>
</body>
<script>
	document.querySelector("#send").addEventListener("click", () => {
		let name = document.querySelector("#name").value;
		// @ToDo メインプロセスに値を送信する方法を追加する
	});
</script>
</html>



続いて、メインプロセスとレンダラープロセスをつなぐためにpreload.jsを作成します。


.
├── index.html
├── main.js
├── node_modules
├── package.json
├── package-lock.json
└── preload.js

preload.js

const { ipcRenderer, contextBridge } = require('electron');

contextBridge.exposeInMainWorld("api", {
	send: (name) => ipcRenderer.invoke('receive', name)
});

electronパッケージに含まれるcontextBridgeによって、メインプロセスとレンダラープロセスをつなぐための機能を追加します。

太字で示した文字はメインプロセス(main.js)の方でreceive、レンダラープロセス(index.html)の方でapiとsendの文字列を使用します。


メインプロセス(main.js)の方でpreload.jsを読み込みます。


main.js

const { app, BrowserWindow } = require('electron')
const path = require('path')

const createWindow = () => {
	const win = new BrowserWindow({
		width: 400,
		height: 300,
		webPreferences: {
			preload: path.join(__dirname, 'preload.js')
		}
	})

	win.loadFile('index.html')
}

app.whenReady().then(() => {
	createWindow()
})

今回の変更でレンダラープロセス(index.html)の方でwindow.api.send()の関数が利用可能になりました。

※関数はpreload.jsで太字にした文字(apiとsend)を用いて組み立てています。


index.htmlを下記のように変更します。

<script>
	document.querySelector("#send").addEventListener("click", () => {
		let name = document.querySelector("#name").value;
		// @ToDo メインプロセスに値を送信する方法を追加する
	});
</script>

<script>
	document.querySelector("#send").addEventListener("click", () => {
		let name = document.querySelector("#name").value;
		window.api.send(name);
	});
</script>



最後にレンダラープロセス(index.html)からメインプロセス(main.js)に送信した値を、メインプロセスの方で受け取れるようにします。


main.js

const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')

const createWindow = () => {
	const win = new BrowserWindow({
		width: 400,
		height: 300,
		webPreferences: {
			preload: path.join(__dirname, 'preload.js')
		}
	})

	ipcMain.handle("receive", async (_e, _arg) => {
		console.log(_arg)
	})

	win.loadFile('index.html')
}

app.whenReady().then(() => {
	createWindow()
})

ipcMain.handle(apiKey, api)

を追加することによって、レンダラープロセスと通信することができるようになります。

※apiKeyはpreload.jsにあるreceiveになります。



コードを実行した後に起動したウィンドウのフォームで何らかの文字列を入力して送信した後にターミナルの方を確認して、ウィンドウで入力した内容が出力されるか?が確認してみましょう。


プロセス間通信で利用しましたcontextBridgeの詳細を知りたい方はcontextBridge | Electronをご覧ください。




余談ですが、レンダラー(renderer)という用語はプログラミングで頻繁に使用していまして、簡単に見ていきましょう。


レンダラーの元となる用語としてレンダー(render)があります。

レンダー(レンダリング)はコンピュータグラフィックス(CG)の用語で演算結果をディスプレイに描写するという意味があります。


Electronにおいて、index.htmlの方は操作画面の組み立てのプロセスになりますので、レンダラーという用語が当てられています。

HTMLレンダリングエンジン - Wikipedia

マインクラフト用ビジュアルエディタを開発しています。
詳しくはinunosinsi/mcws_blockly - githubをご覧ください。