Electronを使っていて、Rendere, Mainとはさらに別のプロセスとデータのやり取りをすることがあったのでそのメモを兼ねて。
シナリオとしては、GUIからの入力をMain -> ChildとpipeしてそれをGUIに戻すという感じ。
シーケンス図はこんな感じ。
要は、Mainからforkしたchildのstdinに何かを書き込んでstdouをpipeしてもらえばいい。
child_process#spawn
Node.jsには子processをforkする手段が2つある。
ひとつがchild_process.fork
、もうひとつがchild_process.spawn
。
この2つの違いは正直良くわからないのだが、forkはnode.jsスクリプトを子プロセスとしてspawnし、spawnはシェルコマンドをspawnするもののようだ。(詳しい話は公式ドキュメントを読んだほうがいいです)とりあえず今回はnode.js以外のコマンドを実行するのでspawnを使うことにした。
Sample
import {
spawn
} from "child_process";
import EventEmitter from "events";
import {
dirname,
basename
} from "path";
class ChildServer {
constructor() {
this.event = new EventEmitter;
}
run(cmdPath, args = []) {
const cmd = basename(cmdPath);
const cwd = dirname(cmdPath);
this.process = spawn(`./${cmd}`, args, {
cwd: cwd
});
this.process.on("data", (rawData) => {
const data = new Buffer(rawData).toString("utf-8");
this.event.emit("child:response", data);
})
}
write(cmd) {
return new Promise((resolve) => {
this.process.stdin.write(cmd + "\n");
this.event.on("child:response", resolve);
});
}
close() {
this.process.kill();
}
}
const child = new ChildServer();
child.run();
electron.ipcMain.on("ipc:command", (ev, arg) => {
child.write(arg).then((res) => {
ev.sender.send("ipc:response", res);
});
});
$("#button").on("click", () => {
const cmd = $("#input").value();
electron.ipcRenderer.send("ipc:command", cmd);
});
electron.ipcRenderer.on("ipc:response", (res) => {
console.log(res);
});
解説
spawnした子プロセスへの書き込みは、process.stdin.write(cmd+"\n")
で行うのがミソ。(ちなみにwriteln
ではうまく行かなかった謎)
子プロセスからのoutputは、process.on("data")
でしかキャプチャできないので、内部でEventEmitterを使ってPromiseしている。ただ、この実装だとcommandとresponseが常に排他制御されている場合にしか使えない。子プロセスがマルチスレッドで入力を受け付けて非同期でstdoutに結果を返すようなものだと、キューイングする仕組みが必要かもしれない。