名前付きパイプを利用してプログラム間でデータを受け渡す - C#

名前付きパイプを利用してプログラム間でデータを受け渡すコードを紹介します。

概要

名前付きパイプと呼ばれる機能を利用してプログラム間でデータの受け渡しをすることができます。この記事では2つのプログラムを作成して名前付きパイプでデータの受け渡しをするプログラムを紹介します。
補足:他の方法
この記事で紹介する「名前付きパイプ」を利用する以外に、共有メモリを利用する方法やDDE通信を利用する方法があります。共有メモリ(メモリ マップト ファイル)を利用する方法はこちらの記事を参照してください。また、DDE通信を利用する方法はこちらの記事を参照してください。

プログラム例

Windows Formアプリケーションを2つ作成します。

UI : サーバー

下図のUIを作成します。ボタンと複数行のテキストボックスを配置します。

コード : サーバー

下記のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.IO.Pipes;

namespace SimpleNamedPipeServer
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      NamedPipeServerStream npss = new NamedPipeServerStream("TestPipe", PipeDirection.InOut);
      npss.WaitForConnection();

      if (npss.IsConnected == true) {
        int data = npss.ReadByte();
        textBox1.Text += data.ToString("x") + "\r\n";
      }
    }
  }
}

解説

ボタンをクリックすると下記の処理を実行します。
NamedPipeServerStream オブジェクトを作成します。コンストラクタの第一引数にパイプの名称、第二引数に入出力モードの値を指定します。(第二引数のないコンストラクタもあります)
作成した、NamedPipeServerStream オブジェクトのWaitForConnection() メソッドを呼び出し、クライアントの接続を待ちます。なお、WaitForConnection メソッドはブロッキングされるメソッドのため、メソッドを実行したタイミングで処理が止まり、UIはフリーズ状態になります。
  NamedPipeServerStream npss = new NamedPipeServerStream("TestPipe", PipeDirection.InOut);
  npss.WaitForConnection();

クライアントからの接続があると、WaitForConnection メソッドの以降の行が実行されます。
NamedPipeServerStream オブジェクトの IsConnected プロパティの値を確認し、クライアントが接続されていることを確認します。クライアントからのデータの受信は、NamedPipeServerStream オブジェクトのReadByte()メソッドを呼び出します。このメソッドによりクライアントから1バイトデータを読み出します。
取得したデータの値をテキストボックスに16進数表記で表示します。
  if (npss.IsConnected == true) {
    int data = npss.ReadByte();
    textBox1.Text += data.ToString("x") + "\r\n";
  }

UI : クライアント

下図のUIを作成します。フォームにテキストボックスとボタンを配置します。

コード : クライアント

下記のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.IO.Pipes;

namespace SimpleNamedPipeClient
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      byte data = Convert.ToByte(textBox1.Text,16);

      NamedPipeClientStream npcs = new NamedPipeClientStream(".", "TestPipe", PipeDirection.InOut);
      npcs.Connect();

      npcs.WriteByte(data);
      npcs.WaitForPipeDrain();
    }
  }
}

解説

ボタンをクリックすると下記の処理を実行します。

テキストボックスに入力された16進数表記の文字列を数値に変換します。(A3 → 163)
  byte data = Convert.ToByte(textBox1.Text,16);

NamedPipeClientStream オブジェクトを作成します。コンストラクタの第一引数にサーバー名を指定します。今回はネットワーク経由での名前付きパイプではないため、サーバー名は"."を与えます。第二引数にはパイプ名を与えます。こちらはクライアントで指定したパイプ名と同じ名称にします。第三引数は入出力モードの値を指定します。
作成した NamedPipeClientStream オブジェクトのConnectメソッドを呼び出しサーバーに接続します。Connectメソッドはブロッキングメソッドのため接続が完了するまでこの行で処理は停止します。
  NamedPipeClientStream npcs = new NamedPipeClientStream(".", "TestPipe", PipeDirection.InOut);
  npcs.Connect();

NamedPipeClientStream オブジェクトのWriteByte() メソッドを呼び出しサーバーにデータを送信します。送信後WaitForPipeDrain()メソッドを呼び出し、パイプのもう一方の終端であるサーバー側が、送信されたすべてのバイトを読み取るまで待機します。
  npcs.WriteByte(data);
  npcs.WaitForPipeDrain();

実行結果

サーバー側のプログラムを実行します。下図のウィンドウが表示されます。


[button1]をクリックします。クリックすると接続待ちで処理がブロックされ、UIがフリーズします。


クライアント側のプログラムを実行します。下図のウィンドウが表示されます。


クライアント側のテキストボックスに、16進表記で値を入力します。今回の例では"4C"を入力します。


クライアント側のフォームの[button1]をクリックします。サーバー側のプログラムのテキストボックスにクライアントのプログラムで入力した数値が表示されます。クライアントで入力した値がサーバー側のプログラムに渡されていることが確認できます。

プログラム例:文字列を転送する例

先のプログラムでは16進数の値を転送しましたが、文字列を通信できるコードを紹介します。

UI:サーバー

下図のUIを作成します。ボタン1つと複数行のテキストボックスを配置します。(下図はボタンが2つ配置されていますが、button2のみを使用します。)

コード:サーバー

下記のコードを記述します。button2のクリックイベントを実装します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.IO.Pipes;

namespace SimpleNamedPipeServer
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private void button2_Click(object sender, EventArgs e)
    {
      NamedPipeServerStream npss = new NamedPipeServerStream("TestPipe", PipeDirection.InOut);
      npss.WaitForConnection();

      if (npss.IsConnected == true) {
        byte[] buffer = new byte[256];
        int readsize = npss.Read(buffer,0,256);

        string text = Encoding.UTF8.GetString(buffer);
        textBox1.Text += text + "\r\n";
      }
    }
  }
}

解説

基本的なコードは先に紹介した名前付きパイプのサーバー側のコードと同様です。
受信するデータはバイトデータのため、バイトデータを読み取りバイト配列buffer配列に格納します。 取得したバイト配列をEncoding.UTF8.GetString()メソッドで文字列に変換して、文字列をテキストボックスに表示します。
  byte[] buffer = new byte[256];
  int readsize = npss.Read(buffer,0,256);

  string text = Encoding.UTF8.GetString(buffer);
  textBox1.Text += text + "\r\n";

UI:クライアント

下図のUIを作成します。テキストボックスを1つとボタンを1つ配置します。(下図はボタンが2つ配置されていますが、button2のみを使用します。)

コード:クライアント

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.IO.Pipes;

namespace SimpleNamedPipeClient
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private void button2_Click(object sender, EventArgs e)
    {
      byte[] data = new byte[256];
      Encoding.UTF8.GetBytes(textBox1.Text, 0, textBox1.Text.Length, data, 0);

      NamedPipeClientStream npcs = new NamedPipeClientStream(".", "TestPipe", PipeDirection.InOut);
      npcs.Connect();

      npcs.Write(data, 0, 256);
      npcs.WaitForPipeDrain();
    }
  }
}

解説

基本的なコードは先に紹介した名前付きパイプのクライアント側のコードと同様です。

テキストボックスに入力された文字列をUTF8のバイト配列に変換します。変換したバイト配列を名前付きパイプで送信することで文字列のデータを転送します。
      byte[] data = new byte[256];
      Encoding.UTF8.GetBytes(textBox1.Text, 0, textBox1.Text.Length, data, 0);

実行結果

クライアント側のプログラムと、サーバー側のプログラムを実行します。
サーバー側のプログラムを実行すると下図のウィンドウが表示されます。 [button1]をクリックします。クリックすると接続待ちで処理がブロックされ、UIがフリーズします。


クライアント側のプログラムを実行すると下図のウィンドウが表示されます。


クライアント側のウィンドウのテキストボックスに送信する文字列を入力します。入力後[button2]をクリックします。


ボタンをクリックするとサーバー側のウィンドウのテキストボックスに、クライアントのテキストボックスに入力した文字列が表示されます。 クライアントからサーバーに文字列のデータを渡すことができました。

非同期接続のプログラム例

先のプログラムで動作を確認できましたが、ボタンクリックでサーバーのUIがフリーズします。非同期接続を利用するとUIのフリーズを防ぐ動作にできます。非同期接続でのコードを紹介します。

UI:サーバー

下図のUIを作成します。ボタンと複数行のテキストボックスを配置します。

コード:サーバー

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.IO.Pipes;

namespace SimpleNamedPipeAsyncServer
{
  public partial class FormMain : Form
  {
    private NamedPipeServerStream npss;
    private delegate void MyEventHandler(string msg);

    public FormMain()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      npss = new NamedPipeServerStream("TestPipe", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
      npss.BeginWaitForConnection(OnConnect, null);
    }

    private void OnConnect(IAsyncResult result)
    {
      if (npss != null) {
        npss.EndWaitForConnection(result);

        int data = npss.ReadByte();
        this.Invoke(new MyEventHandler(OutputMessage), new string[] { data.ToString("x") + "\r\n" });
      }
    }

    private void OutputMessage(string msg)
    {
      textBox1.Text += msg;
    }
  }
}

解説

ボタンクリックで下記のコードを実行します。NamedPipeServerStream オブジェクトを作成します。非同期で利用する場合は、PipeOptions にAsynchronousを指定します。
作成したNamedPipeServerStream オブジェクトの BeginWaitForConnectionメソッドを呼び出し接続待ちします。BeginWaitForConnectionメソッドは非同期メソッドで、接続されるとメソッドの第一引数のメソッドが呼び出されます。
private void button1_Click(object sender, EventArgs e)
{
  npss = new NamedPipeServerStream("TestPipe", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
  npss.BeginWaitForConnection(OnConnect, null);
}

クライアントからの接続を受け付けると下記のコードが実行されます。EndWaitForConnection()メソッドを呼び出し、接続の完了処理をします。接続ができたのちReadByte()メソッドを呼び出しクライアントから送信された値を取得します。非同期接続での処理のため、この処理はメインスレッドでないスレッドで実行されます。テキストボックスへのメッセージの表示はメインスレッドで実行する必要があるため、Invokeメソッドを利用して、メインスレッドで処理を実行します。Invokeメソッドの詳細はこちらの記事を参照してください。
  private void OnConnect(IAsyncResult result)
  {
    if (npss != null) {
      npss.EndWaitForConnection(result);

      int data = npss.ReadByte();
      this.Invoke(new MyEventHandler(OutputMessage), new string[] { data.ToString("x") + "\r\n" });
    }
  }

Invokeメソッドで呼び出されるメソッドが下記のメソッドになります。テキストボックスにクライアントから受け取ったデータを表示します。
  private void OutputMessage(string msg)
  {
    textBox1.Text += msg;
  }

UI:クライアント

下図のUIを作成します。テキストボックスを2つとボタンを1つ配置します。

コード:クライアント

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.IO.Pipes;

namespace SimpleNamedPipeAsyncClient
{
  public partial class FormMain : Form
  {
    private delegate void MyEventHandler();

    public FormMain()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      byte data = Convert.ToByte(textBox1.Text, 16);

      NamedPipeClientStream npcs = new NamedPipeClientStream(".", "TestPipe", PipeDirection.InOut);
      npcs.Connect();

      npcs.BeginWrite(new byte[] { data }, 0, 1, OnComplete, null);
    }

    private void OnComplete(IAsyncResult result)
    {
      this.Invoke(new MyEventHandler(OutputMessage));
    }

    private void OutputMessage()
    {
      textBox2.Text += "Send Complete";
    }
  }
}

解説

ボタンのクリックで下記のコードを実行します。クライアント側はNamedPipeClientStream オブジェクトを作成し、Connectメソッドで接続します。接続後 BeginWriteメソッドを利用してデータをサーバーに送信します。BeginWriteは非同期メソッドのため、送信が完了すると、第四引数に与えた完了メソッドが呼び出されます。
  private void button1_Click(object sender, EventArgs e)
  {
    byte data = Convert.ToByte(textBox1.Text, 16);

    NamedPipeClientStream npcs = new NamedPipeClientStream(".", "TestPipe", PipeDirection.InOut);
    npcs.Connect();

    npcs.BeginWrite(new byte[] { data }, 0, 1, OnComplete, null);
  }

完了メソッドのコードは下記になります。テキストボックスへの完了メッセージ表示はメインスレッドで実行する必要があるため、Invokeメソッドを利用してメインスレッドで処理を実行します。
  private void OnComplete(IAsyncResult result)
  {
    this.Invoke(new MyEventHandler(OutputMessage));
  }

  private void OutputMessage()
  {
    textBox2.Text += "Send Complete";
  }

実行結果

サーバーのプログラムを起動します。下図のウィンドウが表示されます。[button1]をクリックします。 サーバーが接続待ちになります。非同期でメソッドの呼び出しのためUIはフリーズ状態にはなりません。


クライアントのプログラムを起動します。下図のウィンドウが表示されます。


クライアントプログラムのウィンドウの上部のテキストボックスに16進表記で数値を入力します。今回は"A6"を入力します。入力ができたらテキストボックス下部の[button1]をクリックします。


ボタンをクリックすると下図の画面の状態になります。サーバー側のウィンドウのテキストボックスにクライアントで入力した値の"A6"が表示されます。また、クライアント側の下部のテキストボックスにメッセージの送信ができた旨が表示されます。

Task (await,async) を利用した非同期接続のプログラム例

先の非同期のプログラムでUIのフリーズは防げますが、サブスレッドの処理が必要になるなど、コードが複雑になってしまいます。このセクションでは await async キーワードを利用してTaskを利用した非同期処理のコードを紹介します。awaitキーワードを利用することで、シングルスレッドの記述に近い動作で非同期処理を実装できます。
await, async の詳細についてはこちらの記事を参照してください。

UI:サーバー

下図のUIを作成します。ボタンと複数行のテキストボックスを配置します。

コード:サーバー

下記のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Pipes;

namespace SimpleNamedPipeAsyncTaskServer
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
      NamedPipeServerStream npss = new NamedPipeServerStream("TestPipe", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
      textBox1.Text += "待機中です。\r\n\r\n";
      await npss.WaitForConnectionAsync();

      byte[] buffer = new byte[1];
      int size = await npss.ReadAsync(buffer, 0, 1);

      textBox1.Text += buffer[0].ToString("x");
    }
  }
}

解説

NamedPipeServerStream クラスのオブジェクトを作成するまではこれまでのコードと同じです。クライアントからの接続待ちをする処理が WaitForConnectionAsync() メソッドの呼び出しになります。await キーワードを追加して呼び出すことで、サブスレッドで接続待ちをしつつ、メインスレッドはこの呼び出し行で待機して待つ動作になります。
クライアントからの接続があると事業以降が実行されます。ReadAsync メソッドの呼び出しで再度 await キーワードが指定され、読み取り処理を非同期で処理して、読み取りが完了するまでメインスレッドはこの行で待ちます。

読み取りの完了後、テキストボックスに名前付きパイプで読み出した値を表示します。

UI:クライアント

下図のUIを作成します。テキストボックスを2つとボタンを1つ配置します。

コード:クライアント

下記のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Pipes;

namespace SimpleNamedPipeAsyncTaskClient
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
      byte data = Convert.ToByte(textBox1.Text, 16);

      NamedPipeClientStream npcs = new NamedPipeClientStream(".", "TestPipe", PipeDirection.InOut);
      await npcs.ConnectAsync();

      await npcs.WriteAsync(new byte[] { data }, 0, 1);

      textBox2.Text = "送信しました。";
    }
  }
}

解説

NamedPipeClientStream オブジェクトの作成はこれまでと同様です。サーバーへの接続は awaitキーワードを付加して、ConnectAsync メソッドを呼び出します。接続が完了すると次行以降を実行します。awaitを利用した非同期書き込みでは WriteAsyncメソッドを呼び出します。送信完了後、次の行の処理が実行され、下部のテキストボックスに送信が完了した旨のメッセージを表示します。

実行結果

サーバーのプログラムを実行します。下図のウィンドウが表示されます。


[button1]をクリックします。テキストボックスに「待機中です。」のメッセージが表示され、クライアントからの接続待ちになります。非同期処理のためUIはフリーズ状態にはなりません。


クライアント側のプログラムを起動します。下図のウィンドウが表示されます。


上部のテキストボックスに16進数表記でサーバーに送信する値を入力します。今回は"6B"を入力します。入力後[button1]をクリックします。


クライアントに入力した値が、サーバーに送信されます。クライアントプログラムの下部のテキストボックスには送信をした旨のメッセージが表示されます。また、サーバー側のテキストボックスにはクライアントから送信された値である"6b"が表示されます。


著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2018-12-21
iPentec all rights reserverd.