目的
デスクトップアプリを作る上で、Node.jsからC#コードの結果を取得することが目的です。
色々調べると、Edge.jsというライブラリが見つかり、使ってみました。
もちろんインラインコードなど書きたくなかったので、DLLを読み込む方式にしたのですが、私のやり方が悪いのかVisual Studioでのコードデバッグができず、途方に暮れていました。
また、Edge.jsの場合、C#コンパイラーがすでにnpmパッケージにBundleされているようで、.NET4.5に依存しているようでした。
Node.js -> C#の呼び出し部分のオーバーヘッドが少ないようですが、私の目的としては、C#で作ったデータをもらって、Node.js(実際は、ElectronやCarlo)で扱う程度のものだったので、C#で作った関数を、秒間何千何万と呼び出したいわけではなかったのです。
そこでIPC通信を使うことにしました。
実装
まず、Clientサイド(Node.js)ですが、node-ipcを使わせて頂きました。
node-ipcのIPCソケットには、いくつかタイプがあります。
- Unix Socket or Windows Socket
- TCP Socket
- TLS Socket
- UDP Sockets
結論からいうと、1.を採用しました。
なぜかというと、ネットワーク上の別のPCのプロセスと通信したいわけでもなく、localhost上のポートを使ってまで動かしたくはなかった為です。
また、C#から送られてくるデータは欠損が許されない為、UDPは使えないだろうと。
その中で、最も高速と思われる1.を採用した次第です。
node-ipcが提供しているExampleがあります。
その中から、基本的なクライアントコードを参考にさせていただきました。
const ipc = require("../../../node-ipc");
ipc.config.id = "hello";
ipc.config.retry = 1000;
ipc.connectTo("world", function() {
ipc.of.world.on("connect", function() {
ipc.log("## connected to world ##", ipc.config.delay);
ipc.of.world.emit("app.message", "ping");
});
ipc.of.world.on("disconnect", function() {
ipc.log("disconnected from world");
});
ipc.of.world.on("app.message", function(data) {
ipc.log("got a message from world : ", data);
});
console.log(ipc.of.world.destroy);
});
サーバーに対して、"ping"を送信しています。
この部分を見ると書いてありますが、windowsの場合は、NamedPipeのpipe名に変換されます。
上記コードの場合、connectToの第2引数に文字列で指定した場合のPath
パラメータが省略されています。
そのため、Unix Domain Socketのパスだと、/tmp/app.world
になり、
名前付きパイプの場合、\\.\pipe\tmp-app.world
に変換されます。
C#でNamedPipeServerStream
を生成する際は、tmp-app.world
となります。
それを踏まえ、サーバーサイドのコードを書きます。
C#側のコードはこんな感じにしてみました。
※ ちなみに今回は、サーバーもクライアントも同一PCである為、名前付きパイプに伴うimpersonationのようなセキュリティ対策は考慮していません。
using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
namespace nodeIpc
{
public class Entry
{
static void Main(string[] args)
{
// サーバーの作成
using (var server = new NamedPipeServerStream("tmp-app.world", PipeDirection.InOut))
{
Console.WriteLine("waiting for client connection...");
// クライアント接続待ち
server.WaitForConnection();
Console.WriteLine("connected!");
// 受信
char c;
string message = string.Empty;
while ((c = (char)server.ReadByte()) != '\f')
{
message += c;
}
// typeとdataが設定されたJSON文字列が送信されてくる
Console.WriteLine(message);
var writer = new StreamWriter(server);
// 返信
var data = "pong";
// node-ipcが解釈できる形のJSON文字列 + \fを返す。
writer.Write("{ \"type\": \"app.message\", \"data\": \"" + data + "\" }" + "\f");
writer.Flush();
// クライアント側が送信メッセージを読み終わるまで待つ
server.WaitForPipeDrain();
}
}
}
}
まずは、サーバーを作成し、接続時に送られてくる"ping"
という文字列を取得します。
node-ipcのDefaultのメッセージ終端である\f
が来るまでループし、メッセージを取得します。
メッセージは、JSONになっているので、適宜Json.NETを用いて、パースしましょう。
対して、"pong"
メッセージを送り返す際は、下記の構造のデータをJSON文字列にシリアライズし送信する必要があります。
typeは、node-ipcにおける、メッセージIDに当たります。
dataは、もちろんオブジェクトでもよく、JSONであれば何でもいいです。
{
"type": "app.message",
"data": "pong"
}
稚拙なMyサンプルゆえに、サーバーが1回のメッセージ送受信しかしてくれない為、接続後リトライエラーが多発しますが、通信は可能です。
是非お試しを。