.NET Frameworkでは、System.Diagnostics.Processクラスによって自プロセスや他プロセスを参照し、プロセスの状態・情報を取得することができます。 また、子プロセスを生成する(他のアプリケーションを起動する)場合にもProcessクラスを使います。

ここではProcessクラスおよびプロセスの起動と終了に関する事項についてを解説します。

§1 自プロセス

ここでは主にコンソールアプリケーションやウィンドウを持つアプリケーションなど、単一のプロセスとして動作するアプリケーションに関して述べます。 スタンドアロンではないサーバーサイドアプリケーション等にはあてはまらない事柄もあるので注意してください。

§1.1 プロセスのエントリポイント

.NET Frameworkでは、プロセスのエントリポイントはMainメソッドになります。 このメソッドは任意の名前のクラスに持たせることができます。 プロセス名とクラス名は一致していなくてもかまいません。

プロセスのエントリポイント
using System;

class Sample {
  // エントリポイント (Mainメソッド)
  static void Main()
  {
    Console.WriteLine("Hello, world!");
  }
}
Copyright© 2016 . Released under the WTFPL version 2.

エントリポイント(Mainメソッド)はstatic(VBではShared)なメソッドにする必要があります。 一方エントリポイントを包括する型(クラスまたは構造体)自体はstaticである必要はありません。

また、誤って呼び出してしまう可能性を排除するため、エントリポイントはpublicにするべきではないとされています。 エントリポイントをpublicにしてもエントリポイントとしての動作に変わりはありません。

プロセスの引数を受け取りたい場合は、Mainメソッドの引数として受け取ります。 詳しくは後述の§.コマンドライン引数を参照してください。 また、Mainメソッドの戻り値をint/Integerにすることで、プロセスの終了ステータスを返すようにすることもできます。 詳しくは後述の§.終了ステータス(終了コード)を参照してください。


Mainという名前のメソッド以外をエントリポイントにすることはできません。 コンパイラはMainという名前のメソッドを探し、それをエントリポイントとして使用します。

Mainという名前のメソッドを持つ型が複数ある場合はコンパイルエラーとなります。 この場合、コンパイラオプション/mainによって、どの型のMainメソッドをエントリポイントとするか指定することができます。

複数のMainメソッドが存在するコード
using System;

class EntryPoint {
  static void Main()
  {
    Console.WriteLine("Hello, entry point!");
  }
}

class Sample {
  static void Main()
  {
    Console.WriteLine("Hello, sample!");
  }
}
Copyright© 2016 . Released under the WTFPL version 2.
複数のMainメソッドがある場合
>csc /nologo test.cs && test.exe
test.cs(4,15): error CS0017: プログラム 'test.exe' で、複数のエントリポイントが定義されています: 'EntryPoint.Main()'。エントリポイントを含む型を指定するには、/main でコンパイルしてください。
test.cs(11,15): error CS0017: プログラム 'test.exe' で、複数のエントリポイントが定義されています: 'Sample.Main()'。エントリポイントを含む型を指定するには、/main でコンパイルしてください。
/mainによってエントリポイントとなるMainメソッドを持つ型を指定する
>csc /nologo /main:EntryPoint test.cs && test.exe
Hello, entry point!

IDE上でもエントリポイントをコンパイルオプションとして変更することができます。 またIDE上では、MainメソッドではなくフォームをWindowsアプリケーションのエントリポイントとして指定する場合もあります。


通常エントリポイントはクラス(VBではモジュールの場合もある)に持たせることが多く、このようにすることは稀ですが、構造体にエントリポイントをもたせることもできます。

構造体にエントリポイントを持たせる
using System;

// Mainメソッドを包括する型として構造体を用いる
struct Sample {
  static void Main()
  {
    Console.WriteLine("Hello, world!");
  }
}
Copyright© 2016 . Released under the WTFPL version 2.

§1.2 コマンドライン引数

プロセスに与えられたコマンドライン引数、起動時に渡される引数は、Mainメソッドの引数として受け取ることができます。 引数の名前は任意に指定できますが、型はString型の配列である必要があります。 Mainメソッドに渡される引数には、プロセスを起動した時のコマンドラインは含まれません

引数を取るエントリポイント
using System;

class Sample {
  // エントリポイントで引数を取る
  // (プロセスに渡された引数はargsを通して参照できる)
  static void Main(string[] args)
  {
    foreach (var arg in args) {
      Console.WriteLine(arg);
    }
  }
}
Copyright© 2016 . Released under the WTFPL version 2.
実行結果例
> csc test.cs
> test.exe /arg1 /arg2 /foo=bar
/arg1
/arg2
/foo=bar
実行結果例(Monoの場合)
$ mcs test.cs
$ mono test.exe /arg1 /arg2 /foo=bar
/arg1
/arg2
/foo=bar

プロセスを起動した時のコマンドラインを含めて引数を取得したい場合は、Environment.GetCommandLineArgsメソッドを使います。 詳しくはプロセス・アセンブリの情報 §.コマンドライン引数を参照してください。


コマンドライン引数の解析処理は実装者に任されます。 .NET Frameworkのライブラリでは、コマンドライン引数の解析を目的としたクラスやメソッド等は用意されていません。 そのため、独自に解析処理を記述するか、サードパーティーのライブラリ等を使用する必要があります。

こういったライブラリの一例として、Mono.Optionsがあります。 Mono.Optionsでは、UnixおよびWindowsでよく用いられる形式のコマンドライン引数の解析をサポートしていて、シンプルなコードで引数の解析を実装することができます。

Mono.Optionsを使ってコマンドライン引数の解析を行う例
using System;
using Mono.Options;

class Sample {
  static void Main(string[] args)
  {
    bool arg1 = false, arg2 = false, arg3 = false;
    string foo = null;

    // コマンドライン引数の定義
    var p = new OptionSet() {
      {"arg1", val => arg1 = (val != null)},
      {"arg2", val => arg2 = (val != null)},
      {"arg3", val => arg3 = (val != null)},
      {"foo=", val => foo = val},
    };

    // 定義に従ってコマンドライン引数を解析する
    p.Parse(args);

    // 解析した結果を表示
    if (arg1)
      Console.WriteLine("arg1が設定されています");
    if (arg2)
      Console.WriteLine("arg2が設定されています");
    if (arg3)
      Console.WriteLine("arg3が設定されています");

    Console.WriteLine("fooの値に'{0}'が指定されています", foo);
  }
}
Copyright© 2016 . Released under the WTFPL version 2.
実行結果例
$ mono test.exe /arg1 /arg3 /foo:bar
arg1が設定されています
arg3が設定されています
fooの値に'bar'が指定されています

$ mono test.exe --arg2 --foo=hoge
arg2が設定されています
fooの値に'hoge'が指定されています

§1.3 終了ステータス(終了コード)

.NET Frameworkでは例外のスローによって異常の発生とその原因を通知することが多いため、終了ステータスを用いることはあまりありません。 プロセスの終了ステータスを親プロセスに通知するには、Mainメソッドの戻り値をint/Integerにし、Mainメソッドの戻り値によって終了ステータスを返します。

Mainメソッドで終了ステータスを返す
using System;
using System.IO;

class Sample {
  static int Main()
  {
    // ファイル'sample.txt'の有無を調べる
    if (File.Exists("sample.txt")) {
      // ファイルがある場合は何らかの処理を行うと仮定する
      return 0; // 正常終了
    }
    else {
      Console.Error.WriteLine("ファイルがありません。");
      return 1; // 異常終了
    }
  }
}
Copyright© 2016 . Released under the WTFPL version 2.

Environment.Exitメソッドを使うことでも終了ステータスの設定を行うことができます。 このメソッドはexitシステムコールに似たもので、終了ステータスを設定すると同時にプロセスを即座に終了します。

Environment.Exitメソッドを使って指定した終了ステータスでプロセスを終了する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // ファイル'sample.txt'の有無を調べる
    if (File.Exists("sample.txt")) {
      // ファイルがある場合は何らかの処理を行うと仮定する
      Environment.Exit(0); // 正常終了
    }
    else {
      Console.Error.WriteLine("ファイルがありません。");
      Environment.Exit(1); // 異常終了
    }

    // Environment.Exitが呼び出された時点でプロセスが
    // 終了するため、この部分は実行されない
    Console.WriteLine("Hello, world!");
  }
}
Copyright© 2016 . Released under the WTFPL version 2.

終了ステータスの設定のみを行いたい場合は、Environment.ExitCodeプロパティを設定します。 Environment.Exitメソッドとは異なり、このプロパティに終了ステータスを設定してもプロセスの実行は継続します。

Environment.ExitCodeプロパティを使って終了ステータスを設定する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // ファイル'sample.txt'の有無を調べる
    if (File.Exists("sample.txt")) {
      // ファイルがある場合は何らかの処理を行うと仮定する
      Environment.ExitCode = 0; // 正常終了
    }
    else {
      Console.Error.WriteLine("ファイルがありません。");
      Environment.ExitCode = 1; // 異常終了
    }

    // Environment.ExitCodeを設定した後でもプロセスの実行は継続する
    Console.WriteLine("Hello, world!");
  }
}
Copyright© 2016 . Released under the WTFPL version 2.

スローされた例外がハンドリングされないことによりプロセスが終了する場合も、終了ステータスとして0以外の値が自動的に設定されます。

例外をスローしてプロセスを終了する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // ファイル'sample.txt'の有無を調べる
    if (File.Exists("sample.txt")) {
      // ファイルがある場合は何らかの処理を行うと仮定する
    }
    else {
      throw new FileNotFoundException("ファイルがありません。", "sample.txt");
    }
  }
}
Copyright© 2016 . Released under the WTFPL version 2.
実行結果と設定される終了ステータス
>csc /nologo test.cs && test.exe

ハンドルされていない例外: System.IO.FileNotFoundException: ファイルがありません。
   場所 Sample.Main()

>echo %ERRORLEVEL%
-532462766

§1.4 標準ストリーム

標準出力への文字列の書き込みにはConsole.WriteLineメソッド、標準入力からの文字列の書き込みにはConsole.ReadLineメソッドなどを使います。 Consoleクラスとプロセスの標準ストリームの扱い方に関しては、自プロセスの標準入出力を参照してください。

§1.5 プロセス情報

自プロセスに関する情報を取得するには、System.Diagnostics.Processクラスのメンバを参照します。 この際、まずProcess.GetCurrentProcessメソッドによって現在のプロセス(自プロセス)のProcessインスタンスを取得します。

Processクラスからは、プロセス名・プロセスID、使用中の各種メモリーサイズや、起動開始時刻と起動してからのプロセッサ時間などを取得することができます。 この他取得出来る情報についてはProcessクラスのドキュメントを参照してください。

Processクラスを使って自プロセスの情報を取得する
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 現在のプロセス(プロセス自身)を取得する
    var p = Process.GetCurrentProcess();

    Console.WriteLine(p.ProcessName); // プロセス名
    Console.WriteLine(p.Id); // プロセスID
    Console.WriteLine(p.SafeHandle); // プロセスのネイティブハンドル (.NET Framework 4.6以降)
    Console.WriteLine(p.StartTime); // プロセスの起動日時
    Console.WriteLine(p.TotalProcessorTime); // 合計プロセッサ時間
    Console.WriteLine(p.UserProcessorTime); // ユーザープロセッサ時間
  }
}
Copyright© 2016 . Released under the WTFPL version 2.

マシンなどプロセスの実行環境・プラットフォームに関する情報を取得する方法についてはランタイム・システム・プラットフォームの情報、環境変数・自プロセスのパスなどに関する情報についてはプロセス・アセンブリの情報を参照してください。

プロセスの状態を計測するにはパフォーマンスカウンタを用いることもできます。 詳しくはパフォーマンス情報の計測を参照してください。

§2 子プロセス

§2.1 子プロセスの起動

Process.Startメソッドに実行可能ファイルのパスを指定すると、それを子プロセスとして実行することができます。 パスが通っていれば、コマンドを起動することもできます。

Process.Startメソッドを使って子プロセスを起動する
using System;
using System.Diagnostics;

class Sample {
  static void Main(string[] args)
  {
    // 子プロセスchild.exeを起動する
    Process.Start("child.exe");

    // 引数を与えて子プロセスchild.exeを起動する
    Process.Start("child.exe", "/arg1 /arg2 /foo=bar");

    // コマンド"dir"を起動する
    Process.Start("dir");
  }
}
Copyright© 2016 . Released under the WTFPL version 2.

§2.2 子プロセスの終了の待機・終了ステータスの取得

Process.Startメソッドでプロセスを起動した場合、起動した時点で処理が戻ります。 起動したプロセスの終了を待機する場合は、Process.WaitForExitメソッドを使います。 Process.Startメソッドは起動したプロセスをProcessインスタンスとして返すので、このインスタンスに対してWaitForExitメソッドを呼び出すことにより、起動したプロセスの終了を待機することができます。 また、ExitCodeプロパティを参照すれば終了ステータスを取得することもできます。

Process.WaitForExitメソッドを使って起動した子プロセスの終了を待機する
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeを起動する
    using (var p = Process.Start("child.exe")) {
      // 起動した子プロセスの終了を待機する
      p.WaitForExit();

      Console.WriteLine("プロセスが終了しました。 終了ステータス: {0}", p.ExitCode);
    }
  }
}
Copyright© 2016 . Released under the WTFPL version 2.

WaitForExitメソッドではタイムアウト時間をミリ秒単位で指定することもできます。 タイムアウトまでにプロセスが終了したかどうかは戻り値によって知ることができます。

タイムアウト時間を指定してProcess.WaitForExitメソッドで子プロセスの終了を待機する
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeを起動する
    using (var p = Process.Start("child.exe")) {
      for (;;) {
        // 起動した子プロセスの終了を待機する
        // (1秒=1000ミリ秒でタイムアウトする)
        if (p.WaitForExit(1000)) {
          Console.WriteLine("プロセスが終了しました");
          // 待機を終了する
          break;
        }
        else {
          // 待機がタイムアウトした
          Console.WriteLine("プロセスは処理中です");
          // 待機を継続する
        }
      }
    }
  }
}
Copyright© 2016 . Released under the WTFPL version 2.

起動したプロセスを即座に終了させたい場合は、Process.Killメソッドを使うことができます。

§2.3 子プロセスの起動オプション

ProcessStartInfoクラスを用いると、引数以外にも子プロセス起動時の動作を細かく制御することができます。 環境変数やカレントディレクトリを変更した状態で起動したい場合にはProcessStartInfoクラスを使って子プロセスを起動する必要があります。 特に、Windows上でコマンドラインプロセス(コンソールアプリケーション)を起動する場合にDOSプロンプトウィンドウを表示したくない場合には、ProcessStartInfo.CreateNoWindowtrueを指定して起動します。

ProcessStartInfoクラスで指定したオプションを使って子プロセスを起動する
using System;
using System.Diagnostics;

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

    // 子プロセスに渡すコマンドライン引数を設定する
    psi.Arguments = "/arg1 /arg2 /foo=bar";

    // プロセス起動時の作業ディレクトリ(カレントディレクトリ)を設定する
    psi.WorkingDirectory = @".\temp\";

    // 環境変数を設定する
    psi.EnvironmentVariables["PATH_DATA"] = @".\data\";

    // プロセス起動時にウィンドウを生成しない
    // (Windows上でコマンドラインプロセスを起動する場合に
    // DOSプロンプトウィンドウを表示せずに起動する)
    psi.CreateNoWindow = true;

    // シェルを使用せずに子プロセスを起動する
    // (環境変数を設定したり、標準ストリームをリダイレクトする場合など、
    // 一部オプションを使用する際にはfalseとする必要がある)
    psi.UseShellExecute = false;

    // 上記のパラメータで子プロセスを起動する
    using (var child = Process.Start(psi)) {
      // 子プロセスの終了を待機する
      child.WaitForExit();
    }
  }
}
Copyright© 2016 . Released under the WTFPL version 2.

子プロセスの起動と環境変数の設定については環境変数 §.子プロセスの環境変数でも解説しています。

§2.4 子プロセスの標準ストリームの読み書き

子プロセスの標準ストリームをリダイレクトしたい(子プロセスの標準出力を読み取る・標準入力に書き込む)場合は、ProcessStartInfo.RedirectStandardOutputtrueにし、同時にProcessStartInfo.UseShellExecutefalseにして子プロセスを起動します。

標準出力に書き込まれた内容を読み込むには、Process.StandardOutput.ReadToEndメソッドなどによって取得します。

子プロセスの標準出力をリダイレクトして標準出力に書き込まれた内容を読み込む
using System;
using System.Diagnostics;

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

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

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

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

      // 読み込んだテキストを表示する
      Console.WriteLine(stdout);
    }
  }
}
Copyright© 2016 . Released under the WTFPL version 2.

子プロセスの標準ストリームはProcess.StandardInputProcess.StandardOutputProcess.StandardErrorプロパティよりStreamReaderクラス/StreamWriterクラスとして取得することができます。 StreamReader/StreamWriterを使った読み書きについてはStreamReaderクラス・StreamWriterクラスを参照してください。

このほか、子プロセスの標準ストリームの扱い方や、非同期での標準ストリームへの読み書きなど、より具体的な方法は子プロセスの標準入出力で解説しています。

§3 プロセス間の協調

§3.1 排他制御

プロセス間で排他制御を行うには、ミューテックス(System.Threading.Mutexクラス)やセマフォ(System.Threading.Semaphoreクラス)を用いることができます。

これらのクラスの詳細や、これらを使ったプロセス間排他制御の具体例については、以下のページを参照してください。

§3.2 プロセス間通信

System.Runtime.Remoting名前空間にあるクラスを使うことでプロセス間通信を行うことができます。 ここではその一例として、IPCチャンネルを使ったプロセス間通信を例示します。

まず、サーバー・クライアントの両方で使用される、通信オブジェクトとなるクラスを用意します。 このクラスは、MarshalByRefObjectを継承する必要があります。 MarshalByRefObjectは、リモート処理によってプロセス境界(厳密にはアプリケーションドメイン境界)を超えるオブジェクトに必要な機能を備えた基本クラスです。

リモートオブジェクト(通信オブジェクト)となるクラス
using System;

// リモートオブジェクトとして公開するクラス
public class Message : MarshalByRefObject {
  public string Text {
    get; set;
  }
}

次にサーバー側です。 サーバー側では、IpcServerChannelクラスでチャネルを登録し、RemotingServices.Marshalメソッドで上記のリモートオブジェクトを公開します。

IpcServerChannelクラスを使用する場合、System.Runtime.Remoting.dllをアセンブリ参照に追加する必要があります。

サーバー側プロセスのコード
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;

// サーバー側となるクラス
class Server {
  private readonly Message message = new Message();

  public Server()
  {
    // ポート名"ipc-test"でチャンネルを登録
    var channel = new IpcServerChannel("ipc-test");

    ChannelServices.RegisterChannel(channel, false);

    // 登録したチャンネルでリモートオブジェクト"message"を公開する
    message = new Message();
    message.Text = "Hello, world";

    RemotingServices.Marshal(message, "message", typeof(Message));
  }

  static void Main()
  {
    // サーバーを起動して待機する
    var s = new Server();

    for (;;) {
      Console.ReadLine();
    }
  }
}

最後にクライアント側です。 クライアント側では、サーバー同様にIpcClientChannelクラスでチャネルを登録し、Activator.GetObjectメソッドで公開されているリモートオブジェクトを取得します。

IpcClientChannelクラスを使用する場合、System.Runtime.Remoting.dllをアセンブリ参照に追加する必要があります。

クライアント側プロセスのコード
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;

// クライアント側となるクラス
class Client {
  private readonly Message message;

  public Message Message {
    get { return message; }
  }

  public Client()
  {
    // クライアント側のチャンネルを登録
    var channel = new IpcClientChannel();

    ChannelServices.RegisterChannel(channel, false);

    // ポート名"ipc-test"のリモートオブジェクト"message"を取得する
    message = Activator.GetObject(typeof(Message), "ipc://ipc-test/message") as Message;
  }

  static void Main()
  {
    // クライアントを起動して取得したリモートオブジェクトを参照する
    var c = new Client();

    Console.WriteLine(c.Message.Text);
  }
}

サーバー側の起動後にクライアント側を起動すると、サーバー側で設定された文字列を取得することができます。 ICPのほか、System.Runtime.Remoting名前空間のクラスを使うことでHTTP・TCPを用いたプロセス間通信を行うこともできます。

§4 他プロセスの取得

Process.GetProcessesメソッドを使うと、現在起動中の取得可能なプロセスを取得することができます。

また、Process.GetProcessByIdメソッドProcess.GetProcessesByNameメソッドを使うと、プロセスIDまたはプロセス名から該当するプロセスの情報を取得することができます。