概要
Electron において、プロセス間通信 (IPC) を使用して、メインプロセスとレンダラープロセスでデータをやり取りする方法について解説します。
本記事で紹介するコード全体は以下を参照ください。
electron-examples/06_electron-ipc-renderer-to-main-async at main
レンダラープロセス起点でやり取りする (非同期通信)
非同期でレンダラープロセスからメインプロセスにデータを送り、メインプロセスで受信し、レンダラープロセスに結果を返すサンプルについて紹介します。Electron では、複数のプロセス間が混在しても区別できるように、プロセス間通信を行う API を使用する際にチャンネル名を指定します。
1. プリロードスクリプトを作成する
ipcRenderer.send()
を使用して、チャンネルsendAsync
で"ping"
というデータをレンダラープロセスからメインプロセスに対して送信する関数send()
を作成します。ipcRenderer.on()
を使用して、チャンネルsendAsync
でデータを受信した場合のコールバック関数を登録する関数recieved()
を作成します。contextBridge.exposeInMainWorld()
を使用して、これらの関数をレンダラープロセスからElectron
プロパティを通してアクセスできるようにします。
preload.ts
import { contextBridge, ipcRenderer, IpcRendererEvent } from "electron";
contextBridge.exposeInMainWorld("Electron", {
// Renderer -> Main
send: (...args: any[]) => ipcRenderer.send("sendAsync", ...args),
// Main -> Renderer
recieved: (listener: (...args: any[]) => void) => {
ipcRenderer.on("sendAsync", (event: IpcRendererEvent, ...args: any[]) =>
listener(...args)
);
},
});
型アノテーションで少し見づらいですが、それを削除すると以下のようになります。
contextBridge.exposeInMainWorld("Electron", {
// Renderer -> Main
send: (...args) => ipcRenderer.send("sendAsync", ...args),
// Main -> Renderer
recieved: (listener) => ipcRenderer.on("sendAsync", (even, ...args) => listener(...args));,
});
2. メインプロセスでデータを受信した場合の処理を作成する
ipcMain.on()
でチャンネル sendAsync
のデータを受信した場合のコールバック関数を登録します。
WebContents.send()
を使用して、チャンネル sendAsync
で "pong"
というデータをメインプロセスからレンダラープロセスに対して送信します。
送信元の WebContents
オブジェクトは、event.sender
で参照できます。
main.ts
import { app, BrowserWindow, ipcMain, IpcMainEvent } from "electron";
import * as path from "path";
const createWindow = () => {
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
});
win.webContents.openDevTools();
win.loadFile("index.html");
};
ipcMain.on("sendAsync", (event: IpcMainEvent, ...args: any[]) => {
console.log(`[Main] ${args} received.`);
// Main -> Renderer
event.sender.send("sendAsync", "pong");
});
app.whenReady().then(createWindow);
3. レンダラープロセスで応答を受信した場合の処理を作成する
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<button onclick="Electron.send('ping')">Send To Main</button>
<p id="recieve"></p>
</body>
</html>
<script>
Electron.recieved((...args) => {
console.log(`[Renderer] ${args} received.`);
document.getElementById("recieve").textContent = JSON.stringify(args);
});
</script>
データを受け取った場合に実行する関数を作成し、プリロードスクリプト内で定義した Electron.recieved()
で登録します。
レンダラープロセス起点でやり取りする (同期通信)
同期でレンダラープロセスからメインプロセスにデータを送り、メインプロセスで受信し、レンダラープロセスに結果を返すサンプルについて紹介します。
1. プリロードスクリプトを作成する
ipcRenderer.sendSync()
を使用して、チャンネルsendSync
で"ping"
というデータをレンダラープロセスからメインプロセスに対して送信する関数send()
を作成します。この関数は、同期処理なので、メインプロセス側からの応答を待ち、それを返します。contextBridge.exposeInMainWorld()
を使用して、この関数をレンダラープロセスからElectron
プロパティを通してアクセスできるようにします。
preload.ts
import { contextBridge, ipcRenderer } from "electron";
contextBridge.exposeInMainWorld("Electron", {
send: () => ipcRenderer.sendSync("sendSync", "ping"),
});
2. メインプロセスでデータを受信した場合の処理を作成する
ipcMain.on()
でチャンネル sendSync
のデータを受信した場合のコールバック関数を登録します。
返り値は、event.returnValue
に設定します。
main.ts
import { app, BrowserWindow, ipcMain, IpcMainEvent } from "electron";
import * as path from "path";
const createWindow = () => {
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
});
win.webContents.openDevTools();
win.loadFile("index.html");
};
ipcMain.on("sendSync", (event: IpcMainEvent, ...args: any[]) => {
console.log(`[Main] ${args} received.`);
// Main -> Renderer
event.returnValue = "pong";
});
app.whenReady().then(createWindow);
3. レンダラープロセスで応答を受信した場合の処理を作成する
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<button onclick="send()">Send To Main</button>
<p id="recieve"></p>
</body>
</html>
<script>
function send() {
const ret = Electron.send();
document.getElementById("recieve").textContent = JSON.stringify(ret);
console.log(`[Renderer] ${ret} received.`);
}
</script>
Electron.send()
でデータを送り、応答を受け取り、HTML 上で表示します。
レンダラープロセス起点でやり取りする (非同期通信/invoke)
非同期通信で、レンダラープロセスからメインプロセスにデータを送り、メインプロセスでそれを受信して、レンダラープロセスに結果を返すサンプルの別の書き方を紹介します。
1. プリロードスクリプトを作成する
ipcRenderer.invoke()
を使用して、チャンネルinvoke
で"ping"
というデータをレンダラープロセスからメインプロセスに対して送信する関数send()
を作成します。この関数は、メインレンダラーと通信し、応答を受け取る Promise オブジェクトを返します。contextBridge.exposeInMainWorld()
を使用して、この関数をレンダラープロセスからElectron
プロパティを通してアクセスできるようにします。
preload.ts
import { contextBridge, ipcRenderer } from "electron";
contextBridge.exposeInMainWorld("Electron", {
send: (...args: any[]) => ipcRenderer.invoke("invoke", ...args),
});
2. メインプロセスでデータを受信した場合の処理を作成する
ipcMain.handle()
で、チャンネル invoke
のデータを受信した場合のコールバック関数を登録します。
このコールバック関数の返り値は、Promise オブジェクトの履行ハンドラーに渡す値になります。
main.ts
import { app, BrowserWindow, ipcMain, IpcMainInvokeEvent } from "electron";
import * as path from "path";
const createWindow = () => {
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
});
win.webContents.openDevTools();
win.loadFile("index.html");
};
ipcMain.handle("invoke", (event: IpcMainInvokeEvent, ...args: any[]) => {
console.log(`[Main] ${args} received.`);
// Main -> Renderer
return "pong";
});
app.whenReady().then(createWindow);
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<button onclick="clicked()">Send To Main</button>
<p id="recieve"></p>
</body>
</html>
<script>
function clicked() {
Electron.send().then((args) => {
document.getElementById("recieve").textContent = JSON.stringify(args);
console.log(`[Renderer] ${args} received.`);
});
}
</script>
3. レンダラープロセスで応答を受信した場合の処理を作成する
データを受け取った場合に実行する関数を定義し、Electron.send()
でデータ送信処理を行う Promise オブジェクトを作成します。Promise.then()
の履行ハンドラーで、メインレンダラーから値を受け取った際の処理を記述します。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<button onclick="clicked()">Send To Main</button>
<p id="recieve"></p>
</body>
</html>
<script>
function clicked() {
Electron.send().then((...args) => {
console.log(`[Renderer] ${args} received.`);
document.getElementById("recieve").textContent = JSON.stringify(args);
});
}
</script>
これは、async/await で以下のように書き直せます。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<button onclick="clicked()">Send To Main</button>
<p id="recieve"></p>
</body>
</html>
<script>
async function clicked() {
const args = await Electron.send();
document.getElementById("recieve").textContent = JSON.stringify(args);
console.log(`[Renderer] ${args} received.`);
}
</script>
メインプロセス起点でやり取りする (非同期通信)
非同期通信で、メインプロセスからレンダラープロセスにデータを送り、レンダラープロセスでそれを受信して、メインプロセスに結果を返すサンプルについて紹介します。
1. プリロードスクリプトを作成する
ipcRenderer.on()
を使用して、チャンネルsendAsync
でデータを受信した場合のコールバック関数を登録する関数recieved()
を作成します。ipcRenderer.send()
を使用して、チャンネルsendAsync
で"pong"
というデータをレンダラープロセスからメインプロセスに対して送信する関数send()
を作成します。contextBridge.exposeInMainWorld()
を使用して、これらの関数をレンダラープロセスからElectron
プロパティを通してアクセスできるようにします。
preload.ts
import { contextBridge, ipcRenderer, IpcRendererEvent } from "electron";
contextBridge.exposeInMainWorld("Electron", {
// Renderer -> Main
send: (...args: any[]) => ipcRenderer.send("sendAsync", ...args),
// Main -> Renderer
recieved: (listener: (...args: any[]) => void) => {
ipcRenderer.on("sendAsync", (event: IpcRendererEvent, ...args: any[]) =>
listener(...args)
);
},
});
2. メインプロセスでデータを送信する処理を作成する
win.webContents.send()
でチャンネルsendAsync
で"ping"
というデータをメインプロセスからレンダラープロセスに対して送信します。送信は、ページの表示が完了してから送るため、"did-finish-load"
イベント後に実行します。ipcMain.on()
でチャンネルsendAsync
のデータを受信した場合のコールバック関数を登録します。
main.ts
import { app, BrowserWindow, ipcMain, IpcMainEvent } from "electron";
import * as path from "path";
const createWindow = () => {
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
});
win.webContents.openDevTools();
win.loadFile("index.html");
win.webContents.on("did-finish-load", () => {
// Main -> Renderer
console.log(`[Main] send "ping".`);
win.webContents.send("sendAsync", "ping");
});
};
ipcMain.on("sendAsync", (event: IpcMainEvent, ...args: any[]) => {
console.log(`[Main] ${args} received.`);
});
app.whenReady().then(createWindow);
3. レンダラープロセスでデータを受信した場合の処理を作成する
データを受け取った場合に実行する関数を定義し、プリロードスクリプトで定義した Electron.recieved()
で登録します。
データを受信したら、応答をメインプロセスに返します。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<p id="recieve"></p>
</body>
</html>
<script>
Electron.recieved((...args) => {
console.log(`[Renderer] ${args} received.`);
document.getElementById("recieve").textContent = JSON.stringify(args);
// Renderer -> Main
Electron.send("pong");
});
</script>
コメント