Edited at

Electron (Node.js)で別プロセスとinteractiveなやりとりをする

More than 3 years have passed since last update.

Electronを使っていて、Rendere, Mainとはさらに別のプロセスとデータのやり取りをすることがあったのでそのメモを兼ねて。

シナリオとしては、GUIからの入力をMain -> ChildとpipeしてそれをGUIに戻すという感じ。

シーケンス図はこんな感じ。

sec.png

要は、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


child-server-js


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();
}
}



main.js

const child = new ChildServer();

child.run();

electron.ipcMain.on("ipc:command", (ev, arg) => {
child.write(arg).then((res) => {
ev.sender.send("ipc:response", res);
});
});



renderer.js

$("#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に結果を返すようなものだと、キューイングする仕組みが必要かもしれない。