Processクラスを使うことで子プロセスを起動することができます。 Processクラスには起動した子プロセスの標準ストリームをリダイレクトするためのプロパティやメソッドが用意されています。

ProcessStartInfoクラスを使って子プロセスを起動する場合、UseShellExecuteプロパティfalseを指定し、RedirectStandardOutputなどのプロパティをtrueにすることで子プロセスの標準ストリームをリダイレクトさせることができます。

§1 標準入力への書き込み

子プロセスの標準入力へ書き込みを行う場合は、ProcessStartInfo.RedirectStandardInputtrueにして子プロセスを起動します。 その後、Process.StandardInputプロパティに設定されるStreamWriterに対して書き込みを行うことで起動した子プロセスの標準入力へ書き込むことができます。

なお、子プロセスの標準入力をリダイレクトするためにProcessStartInfo.UseShellExecutefalseにする必要があります。

次の例では、自プロセスparent.exeから子プロセスchild.exeを起動し、子プロセスの標準入力にテキストを書き込んでいます。 子プロセスでは、標準入力からテキストを読み込み、行番号を付けて標準出力に書き込んでいます。

parent.cs; リダイレクトした子プロセスの標準入力に書き込む
// csc parent.cs /out:parent.exe
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeの起動パラメータを作成する
    ProcessStartInfo psi = new ProcessStartInfo("child.exe");

    psi.UseShellExecute = false; // シェルを使用せず子プロセスを起動する(リダイレクトするために必要)
    psi.RedirectStandardInput = true; // 子プロセスの標準入力をリダイレクトする

    // 子プロセスを起動する
    using (Process child = Process.Start(psi)) {
      // リダイレクトした子プロセスの標準入力にテキストを書き込む
      child.StandardInput.WriteLine("line1");
      child.StandardInput.WriteLine("line2");
      child.StandardInput.WriteLine("line3");

      // 子プロセスの標準入力を閉じて書き込みを終了する
      child.StandardInput.Close();

      // 子プロセスの終了を待機する
      child.WaitForExit();
    }
  }
}
child.cs; 標準入力から読み込んだ内容に行番号を付けて標準出力に書き込む
// csc child.cs /out:child.exe
using System;

class Sample {
  static void Main()
  {
    int lineNumber = 0;

    while (true) {
      string line = Console.ReadLine();

      if (line == null) {
        break;
      }
      else {
        lineNumber++;
        Console.WriteLine("{0}: {1}", lineNumber, line);
      }
    }
  }
}
実行結果
>parent.exe
1: line1
2: line2
3: line3

child.exeでは標準入力から読み込めなくなるまで読み込みを続けるようになっているため、parent.exeの方でProcess.StandardInput.Closeメソッドを呼び出して標準入力を閉じない限りchild.exeは終了しない点に注意してください。

このようにして子プロセスの標準入力に書き込みを行うことができます。 子プロセスの標準入力となるProcess.StandardInputプロパティはStreamWriterであるため、StreamWriterのメソッドを使って書き込みを行うことができます。

プロセスの起動や起動時のオプションについてはプロセス §.子プロセスの起動およびプロセス §.子プロセスの起動オプションを参照してください。



§2 標準出力・標準エラーからの読み込み

子プロセスの標準出力から読み込みを行う場合は、標準入力の場合と同様にProcessStartInfo.RedirectStandardOutpouttrueにして子プロセスを起動します。 その後、Process.StandardOutputプロパティに設定されるStreamReaderを使って読み込みを行うことで起動した子プロセスの標準出力から読み込むことができます。

なお、子プロセスの標準出力をリダイレクトするために、ProcessStartInfo.UseShellExecutefalseにする必要があります。

次の例では、自プロセスparent.exeから子プロセスchild.exeを起動し、子プロセスの標準出力に書き込まれるテキストを読み込んでいます。 子プロセスでは、単に標準出力に文字列を出力しています。

parent.cs; リダイレクトした子プロセスの標準出力からテキストを読み込む
// csc parent.cs /out:parent.exe
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeの起動パラメータを作成する
    ProcessStartInfo psi = new ProcessStartInfo("child.exe");

    psi.UseShellExecute = false; // シェルを使用せず子プロセスを起動する(リダイレクトするために必要)
    psi.RedirectStandardOutput = true; // 子プロセスの標準出力をリダイレクトする

    // 子プロセスを起動する
    using (Process child = Process.Start(psi)) {
      // リダイレクトした子プロセスの標準出力からテキストを読み込む
      string stdout = child.StandardOutput.ReadToEnd();

      // 子プロセスの終了を待機する
      child.WaitForExit();

      // 読み込んだテキストを表示する
      Console.WriteLine("<{0}>", stdout);
    }
  }
}
child.cs; 標準出力に文字列を書き込む
// csc child.cs /out:child.exe
using System;

class Sample {
  static void Main()
  {
    Console.Write("Hello, world!");
  }
}
実行結果
>parent.exe
<Hello, world!>

標準エラーの場合も標準出力と同様で、ProcessStartInfo.RedirectStandardErrortrueにして子プロセスを起動し、Process.StandardErrorプロパティを使って標準エラーから読み込みを行います。

このようにして子プロセスの標準出力・標準エラーから読み込みを行うことができます。 子プロセスの標準出力・標準エラーとなるProcess.StandardOutput, StandardErrorプロパティはStreamReaderであるため、StreamReaderのメソッドを使って読み込みを行うことができます。

§2.1 イベントハンドラを使った非同期読み込み

Process.BeginOutputReadLine, BeginErrorReadLineメソッドを使うと、標準出力・標準エラーに書き込まれる内容を非同期的に読み込むことができます。

同期的に読み込む場合と同様、ProcessStartInfo.RedirectStandardOutpout, RedirectStandardErrortrueにして子プロセスを起動し、読み込みを開始する時点でProcess.BeginOutputReadLine, BeginErrorReadLineメソッドを呼び出します。

書き込まれた内容はProcess.OutputDataReceived, ErrorDataReceivedイベントを使うことで受信できます。 そのため、読み込みを開始する前にこれらのイベントに適切なイベントハンドラを指定しておく必要があります。

受信したデータはイベントハンドラでDataReceivedEventArgs.Dataプロパティを参照することで得られます。 なお、このプロパティで取得できる文字列には改行文字は含まれません。 受信した改行文字毎(=一行毎)にイベントハンドラが呼び出されます。

parent.cs; リダイレクトした子プロセスの標準出力・標準エラーから非同期的に読み込む
// csc parent.cs /out:parent.exe
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスを作成する
    using (Process child = new Process()) {
      // 子プロセスchild.exeの起動パラメータを設定する
      child.StartInfo.FileName = "child.exe";
      child.StartInfo.UseShellExecute = false; // シェルを使用せず子プロセスを起動する(リダイレクトするために必要)
      child.StartInfo.RedirectStandardOutput = true; // 子プロセスの標準出力をリダイレクトする
      child.StartInfo.RedirectStandardError = true; // 子プロセスの標準エラーをリダイレクトする

      // リダイレクトした標準出力・標準エラーの内容を受信するイベントハンドラを設定する
      child.OutputDataReceived += PrintOutputData;
      child.ErrorDataReceived  += PrintErrorData;

      // 子プロセスを起動する
      child.Start();

      // 標準出力・標準エラーの非同期読み込みを開始する
      child.BeginOutputReadLine();
      child.BeginErrorReadLine();

      // 子プロセスの終了を待機する
      while (!child.WaitForExit(250)) {
        // 終了するまでの間、250ミリ秒毎に待機中のメッセージを表示する
        Console.WriteLine("(waiting)");
      }
    }
  }

  private static void PrintOutputData(object sender, DataReceivedEventArgs e)
  {
    // 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
    Process p = (Process)sender;

    if (!string.IsNullOrEmpty(e.Data))
      Console.WriteLine("[{0};stdout] {1}", p.ProcessName, e.Data);
  }

  private static void PrintErrorData(object sender, DataReceivedEventArgs e)
  {
    // 子プロセスの標準エラーから受信した内容を自プロセスの標準エラーに書き込む
    Process p = (Process)sender;

    if (!string.IsNullOrEmpty(e.Data))
      Console.Error.WriteLine("[{0};stderr] {1}", p.ProcessName, e.Data);
  }
}
child.cs; 一定時間おきに標準出力と標準エラーにメッセージを出力する
// csc child.cs /out:child.exe
using System;
using System.Threading;

class Sample {
  static void Main()
  {
    for (int i = 0; i < 10; i++) {
      if (i % 2 == 0)
        Console.Error.WriteLine("Hello, stderr!");
      else
        Console.WriteLine("Hello, stdout!");

      Thread.Sleep(100);
    }
  }
}
実行結果
>parent.exe
[child;stderr] Hello, stderr!
[child;stdout] Hello, stdout!
[child;stderr] Hello, stderr!
(waiting)
[child;stdout] Hello, stdout!
[child;stderr] Hello, stderr!
[child;stdout] Hello, stdout!
(waiting)
[child;stderr] Hello, stderr!
[child;stdout] Hello, stdout!
(waiting)
[child;stderr] Hello, stderr!
[child;stdout] Hello, stdout!

CancelOutputRead, CancelErrorReadメソッドを使うと、イベントハンドラでの標準出力・標準エラーの受信を中止することができます。 このメソッドを呼び出した後はイベントハンドラが呼び出されることはありませんが、標準出力・標準エラーに書き込まれる内容は破棄されず引き続きバッファリングされます。 そのため、再度BeginOutputReadLine, BeginErrorReadLineメソッドを呼び出すとバッファリングされた内容が受信されます。

§3 バイナリデータの読み書き

Process.StandardInput/StandardOutputプロパティはStreamWriter/StreamReaderであるため、バイナリ形式での読み書きを行えるようにはなっていません。 また、OutputDataReceivedイベントで受信できる内容もテキスト形式となっています。

子プロセスの標準入出力に対してバイナリデータの読み書きを行う場合は、StandardInput.BaseStream/StandardOutput.BaseStreamプロパティを参照して標準入出力のStreamを取得します。 取得したStreamを使って直接読み書きを行うか、取得したStreamからBinaryWriter/BinaryReaderを作成することでバイナリデータの読み書きができるようになります。

次の例では、BinaryWriterを使って子プロセスの標準入力にバイナリデータを書き込んでいます。

parent.cs; リダイレクトした子プロセスの標準入力にバイナリデータを書き込む
// csc parent.cs /out:parent.exe
using System;
using System.Diagnostics;
using System.IO;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeの起動パラメータを作成する
    ProcessStartInfo psi = new ProcessStartInfo("child.exe");

    psi.UseShellExecute = false; // シェルを使用せず子プロセスを起動する
    psi.RedirectStandardInput = true; // 子プロセスの標準入力をリダイレクトする

    // 子プロセスを起動する
    using (Process child = Process.Start(psi)) {
      // リダイレクトした子プロセスの標準入力に書き込むBinaryWriterを作成
      BinaryWriter writer = new BinaryWriter(child.StandardInput.BaseStream);

      // バイナリデータを書き込む
      writer.Write((long)0x0706050403020100);
      writer.Write((int)0x0b0a0908);
      writer.Write((short)0x0d0c);
      writer.Write((int)0x11223344);

      // 子プロセスの標準入力を閉じて書き込みを終了する
      child.StandardInput.Close();

      // 子プロセスの終了を待機する
      child.WaitForExit();
    }
  }
}
child.cs; 標準入力から読み込んだ内容を1行8バイト毎に文字列化して表示する
// csc child.cs /out:child.exe
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream stdin = Console.OpenStandardInput()) {
      byte[] buffer = new byte[8];
      int cursor = 0;

      for (;;) {
        int len = stdin.Read(buffer, 0, buffer.Length);

        if (len == 0)
          break;

        for (int index = 0; index < len; index++) {
          Console.Write("{0:X2} ", buffer[index]);

          cursor++;

          if (cursor == 8) {
            Console.WriteLine();
            cursor = 0;
          }
        }
      }
    }

    Console.WriteLine();
  }
}
実行結果
>parent.exe
00 01 02 03 04 05 06 07 
08 09 0A 0B 0C 0D 44 33 
22 11 

標準出力・標準エラーの場合も同様に、StandardOutput.BaseStream/StandardError.BaseStreamを直接扱うことでバイナリ形式での読み込みを行うことができます。

Streamクラスを使った読み書きの方法についてはストリームの基本とStreamクラス、BinaryWriterクラス/BinaryReaderクラスを使った読み書きの方法についてはBinaryReaderクラス・BinaryWriterクラスを参照してください。