読者です 読者をやめる 読者になる 読者になる

WindowsでC#とJavaでプロセス間通信する

背景

Windowsはなぜかローカルホストでのソケット通信を許可してくれない。そのため、OSCでプロセス間通信をしていたアプリをWindowsに持ってくると全滅する。Windows使わないという手はあるのだが、今回はKinect SDKを利用したいので、OSC以外の方法でプロセス間通信を実装した。

システム概要

Kinect SDKC#で利用し、その情報をプロセス間通信を利用してProcessing(つまりJava)に投げてProcessing側でメインの処理をする。
プロセス間通信は色々な方法があるが、今回利用したプロセス間通信の手法は『名前付きパイプ(またの名前をFIFO)』である。
送信する情報は"顔の位置と向き"だ。数値を文字列にして渡している。

サーバー側(C#

Kinect SDKを利用して情報を送信するサーバー側はC#である。コードは著作権的ゴニョゴニョにより一部のみしか載せられないので、詳しくは下記のサイトなどを参照だ。なので動作は確認していない。
方法: ネットワークのプロセス間通信で名前付きパイプを使用する
以下のコードのStart()は起動時に一度呼ばれるメソッド。Stop()は止まった時、OnFaceFrameArrived()は顔認識が出来た時に毎回呼び出されるメソッドである。OnFaceFrameArrived()で顔の位置と向きを送信している。クライアント側を止めると例外が投げられるので、パイプの送信するメソッドたちは本来tryを利用して例外をキャッチする必要がある。

NamedPipeServerStream pipeServer;

public void Start()
{
    pipeServer = new NamedPipeServerStream("testpipe", PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
    Console.WriteLine("[PIPE SERVER] thread created");
    pipeServer.BeginWaitForConnection(ConnectCallback, null);
}

public void ConnectCallback(IAsyncResult result)
{
    Console.WriteLine("[PIPE SERVER] Client Connected");
    pipeServer.EndWaitForConnection(result);
    if (pipeServer.IsConnected)
    {
        byte[] message = Encoding.ASCII.GetBytes("START\n");
        pipeServer.BeginWrite(message, 0, message.Length, WriteCallback, null);
    }
}

public void Stop()
{
    this.pipeServer.Close();
}

private void OnFaceFrameArrived(object sender, HighDefinitionFaceFrameArrivedEventArgs e)
{
    using (HighDefinitionFaceFrame faceFrame = e.FrameReference.AcquireFrame())
    {
        if (faceFrame != null && faceFrame.IsFaceTracked)
        {
            faceFrame.GetAndRefreshFaceAlignmentResult(this.faceAlignment);
            if (pipeServer.IsConnected)
            {
                string s = this.faceAlignment.FaceOrientation.Y.ToString() + "," + this.faceAlignment.HeadPivotPoint.X.ToString() + "," + this.faceAlignment.HeadPivotPoint.Z.ToString() + "\n";
                byte[] message = Encoding.ASCII.GetBytes(s);
                pipeServer.BeginWrite(message, 0, message.Length, WriteCallback, null);
            }
        }
    }
}

private void WriteCallback(IAsyncResult ar)
{
    pipeServer.EndWrite(ar);
}

クライアント側(Processing)

サーバー側と同じくプロセス間通信以外のコードを削除したので動作は確認していない。Javaで普通にファイルを読み込む時に利用する"BufferdReader"を利用している。特殊なのは"FileReader"に渡すパスがパイプを指すものになっている。
Processingの仕様でdraw()は毎フレーム呼ばれるので、draw()でポーリングをしているわけだが、この辺りObserverパターンでコールバック関数を呼び出す感じのヤツの方が美しいよな〜と思いながら実装していたので、そういうヤツを実装した人は是非教えてほしい。今回は時間の関係でポーリングになった。

import java.io.FileReader;
import java.io.BufferedReader;

BufferedReader pipeBuffer;

void setup() {
  try {
    pipeBuffer = new BufferedReader(new FileReader("\\\\.\\pipe\\testpipe"));
  }
  catch (Exception e) {
    e.printStackTrace();
  }
}

void draw() {
  if (pipeBuffer != null) {
    try {
      while (pipeBuffer.ready ()) {
        String line = pipeBuffer.readLine();
        parseKinectValue(line);
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}


void exit() {
  if (pipeBuffer != null) {
    try {
      pipeBuffer.close();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
  super.exit();
}

void parseKinectValue(String str) {
  String[] strs = str.split(",");
  if (strs.length == 3) {
    println(str);
    float faceAngle = Float.parseFloat(strs[0]);
    float x = Float.parseFloat(strs[1]);
    float z = Float.parseFloat(strs[2]);
  }
}