こんにちは、シロクマです!
今日は、electronを用いたデスクトップアプリケーションでよく使う「IPC通信」について説明します!
Contents
そもそもIPC通信とは?
IPCとは、コンピュータ上で実行中のプログラムの間でデータをやり取りするための仕組み。あるプログラムから別のプログラムへデータやメッセージを通知したり、データの提供依頼や処理依頼を行ったり、依頼に対する結果を返したりすることができる。
https://e-words.jp/w/IPC.html
はい。ネット上で検索すると以下のように分かりやすくまとまっていました。
要するに、こんな感じ。
シャチさん、「水戸市の2021年1月の降水量データ」ちょうだい!【依頼】
あいよ、ちょっと待ってな。【受付①】
(ごにょごにょ)【処理】
…ほらよ、「水戸市の2021年1月の降水量データ」だ!【返却】
ありがとう~【受付②】
つまり、ペンギンさんから、シャチさんへある【依頼】がされて、その依頼を【受付】たシャチさんが、依頼内容の【処理】を行い、処理結果をペンギンさんへ【返却】して、返却された結果をペンギンさんは【受付】る、という一連の流れをIPC通信という、ということ。
electronにおけるIPC通信の利用
electronでは、ユーザインターフェースと実際の処理を行うプロセスとのデータのやり取りで「IPC通信」を使っています。というのも、electronは、「http」という通信規約(プロトコル)が原則使えないのです!
(electronの触れ込みが、「Web系リソースを使ってデスクトップアプリケーション開発が出来る!」であるため忘れがちですが、、、)
httpが使えない、ということは――
「GET」メソッドでリクエストが来たときは、この画面を返して、「POST」で来たときは、DBにデータ登録して・・・
みたいなことが単純に出来ないのです。
そこで、データのやり取りでhttpに代わって使われるのが「IPC通信」であり、具体的に以下になります。
- ipcMain
- ipcRenderer
- contextBridge(おまけ)
サンプルアプリ「水戸の降水量」概要
では、実際にどんな風にIPC通信を実装すればよいか? ということについて、以下のサンプルプログラムを用いて解説します。
- プログラム名:水戸の降水量表示
- 機能①:年月を入力するとその月の降水量が表示される
- 機能②:年月・降水量を入力して、特定年月の降水量を登録する
完成イメージは以下となります。
ソースは全貌はそのうちあげます。
【機能①】ipcMain(メインプロセス)
さっそく、メインプロセスです。
const { ipcMain } = require('electron');
const fs = require("fs");
const readLine = require("readline");
const { files } = require("../config/mitoRain.config");
const { dataFileName } = files;
// ブロック①---------------------------------------------
const listenSearch = () => {
// ★レンダラープロセスからの通信待ち
ipcMain.on("search-data", (event,term) => {
let result = {msg: undefined}; // 検索結果格納用
// // dataFileNameは、ファイルパスを入れる。
let readStream = fs.createReadStream(dataFileName,"utf8");
let readIF = readLine.createInterface({ input: readStream })
// ブロック②---------------------------------------------
readIF.on("line", data => {
if (data.indexOf(term) != -1 ) { // ユーザ入力の条件に一致する場合
let ArrayData = data.split(",");
result.msg =`年月" ${term} " の降水量は、" ${ArrayData[ 2 ]}mm "です。`;
}
})
// ブロック③---------------------------------------------
readIF.on("close",() => {
if ( !(result.msg) ) { // 該当なしの場合
result.msg = "検索結果なし。条件を確認してください。";
}
// ★レンダラープロセスへ結果返却
event.reply("rep-search",result);
})
})
}
// ブロック④---------------------------------------------
module.exports = {
listenSearch
}
// ブロック⑤---------------------------------------------
各ブロックの概要です。
ブロック①:
本機能で使う各モジュールを呼び出しています。
const { files } = require(“../config/mitoRain.config”);
は、本アプリの設定ファイルです。
ブロック②:
はい。本記事のキモの一つです。「ipcMain.on」を使ってレンダラープロセスから送られてくるデータを受け取ります。第1引数の”search-data”は、チャンネルと呼ばれるもので、後に出てきますがレンダラープロセス側にも同一名のチャンネルが存在ます。
イメージとしては、インターフェース側(レンダラー)が、チャンネル名でメインプロセスへデータを投げて、メインプロセスもチャンネル名で待ち構えていて、待っていたチャンネル名でデータが来たらキャッチして処理を行う、と言う感じかと。
※ペンギンさんと、シャチさんの例でいう【受付①】に相当する部分です。
ブロック③:
検索条件にヒットするレコードがあるかチェックしています。レコードが見つかれば返却用変数へ値を格納します。
※ペンギンさんと、シャチさんの例でいう【処理】に相当する部分です。
ブロック④:
検索条件にヒットしなかった場合は、検索結果なし、を返却用変数へ格納します。
また、ここでレンダラープロセスに対して処理結果の返却を行います。ここで行っていることも処理②と同じで、今度はメインプロセス → インターフェース(レンダラー)でチャンネル名を使いデータを投げます。
※ペンギンさんと、シャチさんの例でいう【返却】に相当する部分です。
ブロック⑤:
おまけですが、本機能は、エントリポイントとなるjsファイルとは別のファイルへ記述してますので、modeule.exportsでエントリポイントから呼べるようにしています。
【機能①】ipcRenderer(レンダラープロセス)
//preload.js
const { ipcRenderer, contextBridge} = require("electron");
contextBridge.exposeInMainWorld(
'electron',
{
search_data:(term) => ipcRenderer.send("search-data",term),
rep_search:(callback) => ipcRenderer.on("rep-search",(event,data) => {
callback(data);
}),
}
)
上記は、IPC通信におけるユーザインターフェース側実装のための準備です。以下で準備したものを使います。
const searchBtn = $(".search-button");
const registBtn = $(".regist-button");
const resultDiv = $(".result-wrapper");
let term;
searchBtn.on("click", (event) => {
term = $(".term-search").val();
//★search_dataを使ってメインプロセスへデータを投げる
window.electron.search_data(term);
//★rep_searchを使ってメインプロセスからの返却を待つ
window.electron.rep_search( (data) => {
resultDiv.text(data.msg);
})
})
処理としては、「window.electron.search_data」がメインプロセスへ検索条件を投げています。検索ボタンを押下したときに本処理は実行されます。
※ペンギンさんと、シャチさんの例でいう【依頼】に相当する部分です。
そして、「window.electron.rep_search」でメインプロセスで検索した返却結果を受け取ります。受け取った後は、画面上へ表示しています。
※ペンギンさんと、シャチさんの例でいう【受付②】に相当する部分です。
まとめ
思っていたより長くなってしまったので、続きは別記事に記載します。
ちなみに、electronを使ったときに最初に躓いたのが、このデータやり取りの部分でした。いろいろ試行錯誤して使い方を学びましたが、やっぱりWebシステムって便利だなって思いました。