.NETでは、プロセスに関する情報の取得や操作はProcessクラスによって行うことができます。 Processクラスでは、自プロセスや他プロセスを参照し、プロセスの状態・情報を取得することができます。 (§.プロセス情報)
子プロセスを生成する(他のアプリケーションを起動する)場合にもProcessクラスを使います。 (§.子プロセスの起動) 子プロセスを起動する際に指定するコマンドライン引数などのオプションは、ProcessStartInfoで指定することができます。 (§.子プロセスの起動オプション)
ここではProcessクラス、およびプロセスの起動と終了に関する事項としてエントリーポイント、コマンドライン引数、終了ステータス等についてを解説します。
プロセスの標準入出力については自プロセスの標準入出力、子プロセスを起動して標準入出力への読み書きを行う場合については子プロセスの標準入出力、プロセスの環境変数については環境変数にて個別に解説しています。
自プロセス
ここでは主にコンソールアプリケーションやウィンドウを持つアプリケーションなど、単一のプロセスとして動作するアプリケーションに関して述べます。 スタンドアロンではないサーバーサイドアプリケーション等にはあてはまらない事柄もあるので注意してください。
プロセスのエントリポイント(Mainメソッド)
.NETでは、プロセスのエントリポイントはMain
メソッドになります。 このメソッドは任意の名前のクラスに持たせることができます。 プロセス名とクラス名を異なるものにすることもできます。
using System;
class Sample {
// エントリポイント (Mainメソッド)
static void Main()
{
Console.WriteLine("Hello, world!");
}
}
using System;
using System.Threading.Tasks;
class Sample {
// 非同期のエントリポイント (Mainメソッド)
static async Task Main()
{
await Console.Out.WriteLineAsync("Hello, world!");
}
}
Imports System
Class Sample
' エントリポイント (Mainメソッド)
Shared Sub Main()
Console.WriteLine("Hello, world!")
End Sub
End Class
エントリポイント(Mainメソッド)はstatic
(VBではShared
)なメソッドにする必要があります。 一方、エントリポイントを持つ型(クラスまたは構造体)自体はstatic
である必要はありません。
また、誤って呼び出されてしまう可能性を排除するため、エントリポイントはpublic
にするべきではないとされています。 ただし、エントリポイントをpublic
にしてもエラー等にはならず、エントリポイントとしての動作に変わりはありません。
Main
という名前のメソッド以外をエントリポイントにすることはできません。 コンパイラはMain
という名前のメソッドを探し、それをエントリポイントとして使用します。
プロセスの引数・コマンドライン引数を受け取りたい場合は、Main
メソッドの引数として受け取ります。 コマンドライン引数については§.コマンドライン引数で解説します。
また、Main
メソッドの戻り値をint
/Integer
にすることで、プロセスの終了ステータスを返すようにすることもできます。 終了ステータスについては§.終了ステータス(終了コード)で解説します。
Mainメソッドを非同期(async)メソッドとする場合、戻り値の型をTaskにします。 終了ステータスを返す場合は、Task<int>にします。 非同期のMainメソッドはC#7.1以降で使用できます。
複数のMainメソッド (エントリポイントの指定)
Main
という名前のメソッドを持つ型が複数ある場合はコンパイルエラーとなります。 この場合、コンパイラオプション/main、またはプロジェクトファイルのプロパティ<StartupObject>
によって、どの型のMainメソッドをエントリポイントとするか指定することができます。 /main
, <StartupObject>
では、Mainメソッドを持つ型を名前空間.型名
の形式で指定します。
using System;
namespace NS {
class EntryPoint {
static void Main() // このMainメソッドをエントリポイントとしたい
{
Console.WriteLine("Hello, entry point!");
}
}
}
class Sample {
static void Main() // このMainメソッドはエントリポイントとしては使用しない
{
Console.WriteLine("Hello, sample!");
}
}
Imports System
Namespace NS
Class EntryPoint
Shared Sub Main() ' このMainメソッドをエントリポイントとしたい
Console.WriteLine("Hello, entry point!")
End Sub
End Class
End Namespace
Class Sample
Shared Sub Main() ’ このMainメソッドはエントリポイントとしては使用しない
Console.WriteLine("Hello, sample!")
End Sub
End Class
test.cs(5,17): error CS0017: プログラムで複数のエントリ ポイントが定義されています。エントリ ポイントを含む型を指定するには、/main でコンパイルしてください。 [sample.csproj]
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5</TargetFramework>
<StartupObject>NS.EntryPoint</StartupObject> <!-- エントリポイントとしたいMainメソッドを持つ型を指定する -->
</PropertyGroup>
</Project>
$dotnet run
Hello, entry point!
>csc /nologo test.cs && test.exe
test.cs(5,17): error CS0017: プログラムで複数のエントリ ポイントが定義されています。エントリ ポイントを含む型を指定する には、/main でコンパイルしてください。
>csc /nologo /main:NS.EntryPoint test.cs && test.exe
Hello, entry point!
$mcs test.cs
test.cs(5,17): error CS0017: Program `test.exe' has more than one entry point defined: `NS.EntryPoint.Main()'
test.cs(13,15): error CS0017: Program `test.exe' has more than one entry point defined: `Sample.Main()'
Compilation failed: 2 error(s), 0 warnings
$mcs /main:NS.EntryPoint test.cs && mono test.exe
Hello, entry point!
IDE上でもエントリポイントをコンパイルオプションとして変更することができます。 またIDE上では、MainメソッドではなくフォームをWindowsアプリケーションのエントリポイントとして指定する場合もあります。
トップレベルステートメント
C# 9.0以降では、型宣言・Mainメソッド宣言を省略してステートメントを直接記述することができます。 トップレベルステートメントを使ったコードでは、ファイル全体がMainメソッドであるかのように扱われます。
using System;
// 型宣言・メソッド宣言を省略して直接ステートメントを記述できる
string text = "Hello, world!";
Console.WriteLine(text);
#if false // 上記のコードはコンパイル時に以下のように展開される
using System;
namespace RootNamespace {
class Program {
static void Main()
{
string text = "Hello, world";
Console.WriteLine(text);
}
}
}
#endif
Hello, world!
トップレベルステートメントでは、コマンドライン引数をメソッドの引数として取得することができないので、代わりにEnvironment.GetCommandlineArgsメソッドを使って取得します。
構造体でのMainメソッド
通常エントリポイントはクラス(VBではモジュールの場合もある)に持たせることが多く、このようにすることは稀ですが、構造体にエントリポイントをもたせることもできます。
using System;
// Mainメソッドを持つ型として構造体を用いる
struct Sample {
static void Main()
{
Console.WriteLine("Hello, world!");
}
}
using System;
using System.Threading.Tasks;
// Mainメソッドを持つ型として構造体を用いる
struct Sample {
static async Task Main()
{
await Console.Out.WriteLineAsync("Hello, world!");
}
}
Imports System
' Mainメソッドを持つ型として構造体を用いる
Structure Sample
Shared Sub Main()
Console.WriteLine("Hello, world!")
End Sub
End Structure
コマンドライン引数
プロセスに与えられたコマンドライン引数、起動時に渡される引数は、Mainメソッドの引数として受け取ることができます。 引数の名前は任意に指定できますが、型はString型の配列である必要があります。 Mainメソッドに渡される引数には、プロセスを起動した時のコマンドラインは含まれません。
using System;
class Sample {
// エントリポイントで引数を取る
// (プロセスに渡された引数はargsを通して参照できる)
static void Main(string[] args)
{
foreach (var arg in args) {
Console.WriteLine(arg);
}
}
}
Imports System
Class Sample
' エントリポイントで引数を取る
' (プロセスに渡された引数はargsを通して参照できる)
Shared Sub Main(ByVal args() As String)
For Each arg As String In args
Console.WriteLine(arg)
Next
End Sub
End Class
>csc test.cs >test.exe /arg1 /arg2 /foo=bar /arg1 /arg2 /foo=bar
$dotnet build $dotnet run -- /arg1 /arg2 /foo=bar /arg1 /arg2 /foo=bar
$mcs test.cs $mono test.exe /arg1 /arg2 /foo=bar /arg1 /arg2 /foo=bar
プロセスを起動した時のコマンドラインを含めて引数を取得したい場合は、Environment.GetCommandLineArgsメソッドを使います。 詳しくはプロセス・アセンブリの情報 §.コマンドライン引数を参照してください。
コマンドライン引数の解析
コマンドライン引数の解析処理は実装者に任されます。 .NETのクラスライブラリでは、コマンドライン引数の解析を目的としたクラスやメソッド等は用意されていません。 そのため、独自に解析処理を記述するか、サードパーティーのライブラリ等を使用する必要があります。
こういったライブラリの一例として、Mono.Optionsがあります。 Mono.Optionsでは、UnixおよびWindowsでよく用いられる形式のコマンドライン引数の解析をサポートしていて、シンプルなコードで引数の解析を実装することができます。
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);
}
}
$mono test.exe /arg1 /arg3 /foo:bar arg1が設定されています arg3が設定されています fooの値に'bar'が指定されています $mono test.exe --arg2 --foo=hoge arg2が設定されています fooの値に'hoge'が指定されています
終了ステータス(終了コード)
.NETでは例外のスローによって異常の発生とその原因を通知することが多いため、終了ステータスを用いることはあまりありませんが、終了ステータスを返すことはできます。
プロセスの終了ステータスを親プロセスに通知するには、Main
メソッドの戻り値をint
/Integer
、非同期の場合はTask<int>
にし、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; // 異常終了
}
}
}
using System;
using System.IO;
using System.Threading.Tasks;
class Sample {
static async Task<int> Main()
{
// ファイル'sample.txt'の有無を調べる
if (File.Exists("sample.txt")) {
// ファイルがある場合は何らかの処理を行うと仮定する
// await DoSomethingAsync();
return 0; // 正常終了
}
else {
await Console.Error.WriteLineAsync("ファイルがありません。");
return 1; // 異常終了
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Function Main() As Integer
' ファイル'sample.txt'の有無を調べる
If File.Exists("sample.txt") Then
' ファイルがある場合は何らかの処理を行うと仮定する
Return 0 ' 正常終了
Else
Console.Error.WriteLine("ファイルがありません。")
Return 1 ' 異常終了
End If
End Function
End Class
Environment.Exitメソッドを使うことでも終了ステータスの設定を行うことができます。 このメソッドは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!");
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' ファイル'sample.txt'の有無を調べる
If File.Exists("sample.txt") Then
' ファイルがある場合は何らかの処理を行うと仮定する
Environment.Exit(0) ' 正常終了
Else
Console.Error.WriteLine("ファイルがありません。")
Environment.Exit(1) ' 異常終了
End If
' Environment.Exitが呼び出された時点でプロセスが
' 終了するため、この部分は実行されない
Console.WriteLine("Hello, world!")
End Sub
End Class
終了ステータスの設定のみを行いたい場合は、Environment.ExitCodeプロパティを設定します。 Environment.Exitメソッドとは異なり、このプロパティに終了ステータスを設定してもプロセスの実行は継続します。
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!");
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' ファイル'sample.txt'の有無を調べる
If File.Exists("sample.txt") Then
' ファイルがある場合は何らかの処理を行うと仮定する
Environment.ExitCode = 0 ' 正常終了
Else
Console.Error.WriteLine("ファイルがありません。")
Environment.ExitCode = 1 ' 異常終了
End If
' Environment.ExitCodeを設定した後でもプロセスの実行は継続する
Console.WriteLine("Hello, world!")
End Sub
End Class
例外発生時の終了ステータス
スローされた例外がハンドリングされないことによりプロセスが終了する場合、終了ステータスとして0
以外の値が自動的に設定されます。 例外による終了ステータスはMainメソッドの型がvoid
の場合でも設定されます。
using System;
using System.IO;
class Sample {
static void Main()
{
// ファイル'sample.txt'の有無を調べる
if (File.Exists("sample.txt")) {
// ファイルがある場合は何らかの処理を行うと仮定する
}
else {
throw new FileNotFoundException("ファイルがありません。", "sample.txt");
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' ファイル'sample.txt'の有無を調べる
If File.Exists("sample.txt") Then
' ファイルがある場合は何らかの処理を行うと仮定する
Else
Throw New FileNotFoundException("ファイルがありません。", "sample.txt")
End If
End Sub
End Class
>dotnet run Unhandled exception. System.IO.FileNotFoundException: ファイルがありません。 File name: 'sample.txt' at Sample.Main() >echo %ERRORLEVEL% -532462766
>csc /nologo test.cs && test.exe ハンドルされていない例外: System.IO.FileNotFoundException: ファイルがありません。 場所 Sample.Main() >echo %ERRORLEVEL% -532462766
$dotnet run Unhandled exception. System.IO.FileNotFoundException: ファイルがありません。 File name: 'sample.txt' at Sample.Main() $echo $? 134
$mcs test.cs && mono test.exe Unhandled Exception: System.IO.FileNotFoundException: ファイルがありません。 File name: 'sample.txt' at Sample.Main () <0x412c2d50 + 0x00067> in <filename unknown>:0 [ERROR] FATAL UNHANDLED EXCEPTION: System.IO.FileNotFoundException: ファイルがありません。 File name: 'sample.txt' at Sample.Main () <0x412c2d50 + 0x00067> in <filename unknown>:0 $echo $? 1
標準ストリーム (標準入出力)
標準出力への文字列の書き込みにはConsole.WriteLineメソッド、標準入力からの文字列の書き込みにはConsole.ReadLineメソッドなどを使います。 Consoleクラスとプロセスの標準ストリームの扱い方に関しては、自プロセスの標準入出力を参照してください。
プロセス情報
自プロセスに関する情報を取得するには、System.Diagnostics.Processクラスのメンバを参照します。 このクラスでは他プロセスを扱うこともできますが、現在のプロセス(自プロセス)を扱うにはProcess.GetCurrentProcessメソッドによってプロセスのインスタンスを取得します。
Processクラスからは、プロセス名・プロセスIDをはじめとして、以下のような情報を取得することができます。
プロパティ | 取得できる情報 | 備考 | |
---|---|---|---|
基本情報 | ProcessName | プロセス名 | |
Id | プロセスID | .NET 5以降では、Environment.ProcessIdプロパティからも自プロセスIDを取得できる | |
SafeHandle
Handle |
プロセスのネイティブハンドル | SafeHandleは、.NET Framework 4.6以降で使用可能 | |
BasePriority
PriorityClass |
プロセスの優先順位 | ||
StartTime | プロセスが起動した時刻 | ||
プロセッサ使用時間 | UserProcessorTime | ユーザーモードでのプロセッサ使用時間 | |
PrivilegedProcessorTime | 特権モードでのプロセッサ使用時間 | ||
TotalProcessorTime | ユーザーモード・特権モード合計のプロセッサ使用時間 | ||
メモリ | PagedMemorySize64 | プロセスに割り当てられたメモリ量 | |
WorkingSet64 | |||
VirtualMemorySize64ほか | |||
PeakPagedMemorySize64 | 同ピーク値 | ||
PeakWorkingSet64 | |||
PeakVirtualMemorySize64ほか | |||
プロパティ | 取得できる情報 | 備考 |
この他、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(Environment.ProcessId); // プロセスID(.NET 5以降)
Console.WriteLine(p.PriorityClass); // プロセスの優先順位
Console.WriteLine(p.SafeHandle); // プロセスのネイティブハンドル (.NET Framework 4.5以前ではHandleプロパティのみ参照可能)
Console.WriteLine(p.StartTime); // プロセスの起動日時
Console.WriteLine(p.UserProcessorTime); // ユーザーモードでのプロセッサ使用時間
Console.WriteLine(p.PrivilegedProcessorTime); // 特権モードでのプロセッサ使用時間
Console.WriteLine(p.TotalProcessorTime); // 合計のプロセッサ使用時間
}
}
Imports System
Imports System.Diagnostics
Class Sample
Shared Sub Main()
' 現在のプロセス(プロセス自身)を取得する
Dim p As Process = Process.GetCurrentProcess()
Console.WriteLine(p.ProcessName) ' プロセス名
Console.WriteLine(p.Id) ' プロセスID
Console.WriteLine(Environment.ProcessId) ' プロセスID(.NET 5以降)
Console.WriteLine(p.PriorityClass) ' プロセスの優先順位
Console.WriteLine(p.SafeHandle) ' プロセスのネイティブハンドル (.NET Framework 4.5以前ではHandleプロパティのみ参照可能)
Console.WriteLine(p.StartTime) ' プロセスの起動日時
Console.WriteLine(p.UserProcessorTime) ' ユーザーモードでのプロセッサ使用時間
Console.WriteLine(p.PrivilegedProcessorTime) ' 特権モードでのプロセッサ使用時間
Console.WriteLine(p.TotalProcessorTime) ' 合計のプロセッサ使用時間
End Sub
End Class
メモリ使用量など、プロセスの詳細な状態を計測するにはパフォーマンスカウンタを用いることもできます。 詳しくはパフォーマンス情報の計測を参照してください。
マシンなどプロセスの実行環境・プラットフォームに関する情報を取得する方法についてはランタイム・システム・プラットフォームの情報、環境変数・自プロセスのパスなどに関する情報についてはプロセス・アセンブリの情報を参照してください。
子プロセス
子プロセスの起動
Process.Startメソッドに実行可能ファイルのパスを指定すると、それを子プロセスとして実行することができます。 .NET Frameworkでは、パスが通っていればコマンドを起動したり、拡張子に関連付けられた実行可能ファイルでファイルを開くこともできます。
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"); // ⚠.NET Core/.NET 5以降では、ProcessStartInfoを使用する必要がある
// ファイル"test.txt"を開く (拡張子.txtに関連付けられた実行可能ファイルで開く)
Process.Start("test.txt"); // ⚠.NET Core/.NET 5以降では、ProcessStartInfoを使用する必要がある
}
}
Imports System
Imports System.Diagnostics
Class Sample
Shared Sub Main()
' 子プロセスchild.exeを起動する
Process.Start("child.exe")
' 引数を与えて子プロセスchild.exeを起動する
Process.Start("child.exe", "/arg1 /arg2 /foo=bar")
' コマンド"dir"を起動する
Process.Start("dir") ' ⚠.NET Core/.NET 5以降では、ProcessStartInfoを使用する必要がある
' ファイル"test.txt"を開く (拡張子.txtに関連付けられた実行可能ファイルで開く)
Process.Start("test.txt") ' ⚠.NET Core/.NET 5以降では、ProcessStartInfoを使用する必要がある
End Sub
End Class
ProcessStartInfoを使用することで、子プロセスを起動する際のオプションを詳細に指定することができます。
子プロセスの終了の待機・終了ステータスの取得
Process.Startメソッドでプロセスを起動した場合、起動した時点で処理が戻ります。 起動したプロセスの終了を待機する場合は、Process.WaitForExitメソッドまたはProcess.WaitForExitAsyncメソッドを使います。
Process.Startメソッドは起動したプロセスをProcessインスタンスとして返すので、このインスタンスに対してWaitForExitメソッドを呼び出すことにより、起動したプロセスの終了を待機することができます。 また、ExitCodeプロパティを参照すれば終了ステータスを取得することもできます。
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);
}
}
}
using System;
using System.Diagnostics;
using System.Threading.Tasks;
class Sample {
static async Task Main()
{
// 子プロセスchild.exeを起動する
using (var p = Process.Start("child.exe")) {
// 起動した子プロセスの終了を待機する
await p.WaitForExitAsync();
Console.WriteLine("プロセスが終了しました。 終了ステータス: {0}", p.ExitCode);
}
}
}
Imports System
Imports System.Diagnostics
Class Sample
Shared Sub Main()
' 子プロセスchild.exeを起動する
Using p As Process = Process.Start("child.exe")
' 起動した子プロセスの終了を待機する
p.WaitForExit()
Console.WriteLine("プロセスが終了しました。 終了ステータス: {0}", p.ExitCode)
End Using
End Sub
End Class
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("プロセスは処理中です");
// 待機を継続する
}
}
}
}
}
Imports System
Imports System.Diagnostics
Class Sample
Shared Sub Main()
' 子プロセスchild.exeを起動する
Using p As Process = Process.Start("child.exe")
Do
' 起動した子プロセスの終了を待機する
' (1秒=1000ミリ秒でタイムアウトする)
If p.WaitForExit(1000) Then
Console.WriteLine("プロセスが終了しました")
' 待機を終了する
Exit Do
Else
' 待機がタイムアウトした
Console.WriteLine("プロセスは処理中です")
' 待機を継続する
End If
Loop
End Using
End Sub
End Class
起動したプロセスを即座に終了させたい場合は、Process.Killメソッドを使うことができます。
子プロセスの起動オプション
ProcessStartInfoクラスを用いると、引数以外にも子プロセス起動時のオプションや動作を細かく指定することができます。 環境変数やカレントディレクトリを変更した状態で起動したい場合には、ProcessStartInfoクラスを使って子プロセスを起動する必要があります。
例として、コマンドなどを子プロセスとして起動したい場合・シェルを使用して起動したい場合には、UseShellExecuteをtrue
にします。 逆に、実行可能ファイルを直接指定して子プロセスとして起動したい場合や、標準入出力を扱いたい場合、環境変数を設定して起動する場合は、false
にする必要があります。 UseShellExecuteはランタイムによってデフォルト値が異なり、.NET Frameworkではtrue
、.NET Core/.NET 5ではfalse
となっています。
また、Windows上でコマンドラインプロセス(コンソールアプリケーション)を起動する場合にDOSプロンプトウィンドウを表示したくない場合には、CreateNoWindowにtrue
を指定して起動します。 CreateNoWindowはデフォルトではfalse
となっています。
using System;
using System.Diagnostics;
class Sample {
static void Main()
{
// 子プロセスchild.exeの起動パラメータを作成する
var psi = new ProcessStartInfo("child.exe");
// 子プロセスに渡すコマンドライン引数を設定する
psi.Arguments = "/arg1 /arg2 /foo=bar";
// .NET Standard 2.1/.NET Core 2.1以降では、ArgumentListプロパティを使用することもできる
// (ArgumentListプロパティでは、空白を含むパスなど、必要なエスケープが自動的に行われる)
psi.ArgumentList.Add("/arg1");
psi.ArgumentList.Add("/arg2");
psi.ArgumentList.Add("/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();
}
}
}
Imports System
Imports System.Diagnostics
Class Sample
Shared Sub Main()
' 子プロセスchild.exeの起動パラメータを作成する
Dim psi As New ProcessStartInfo("child.exe")
' 子プロセスに渡すコマンドライン引数を設定する
psi.Arguments = "/arg1 /arg2 /foo=bar"
' .NET Standard 2.1/.NET Core 2.1以降では、ArgumentListプロパティを使用することもできる
' (ArgumentListプロパティでは、空白を含むパスなど、必要なエスケープが自動的に行われる)
psi.ArgumentList.Add("/arg1")
psi.ArgumentList.Add("/arg2")
psi.ArgumentList.Add("/foo=bar")
' プロセス起動時の作業ディレクトリ(カレントディレクトリ)を設定する
psi.WorkingDirectory = ".\temp\"
' 環境変数を設定する
psi.EnvironmentVariables("PATH_DATA") = ".\data\"
' プロセス起動時にウィンドウを生成しない
' (Windows上でコマンドラインプロセスを起動する場合に
' DOSプロンプトを表示せずに起動する)
psi.CreateNoWindow = True
' シェルを使用せずに子プロセスを起動する
' (環境変数を設定したり、標準ストリームをリダイレクトする場合など、
' 一部オプションを使用する際にはFalseとする必要がある)
psi.UseShellExecute = False
' 上記のパラメータで子プロセスを起動する
Using child As Process = Process.Start(psi)
' 子プロセスの終了を待機する
child.WaitForExit()
End Using
End Sub
End Class
子プロセスの起動と環境変数の設定については環境変数 §.子プロセスの環境変数でも解説しています。
子プロセスのコマンドライン引数
子プロセスを起動する際のコマンドライン引数は、ProcessStartInfoクラスのArgumentsプロパティで指定することができます。 ただし、このプロパティは引数すべてを単一の文字列として指定する必要があり、また空白
を含む文字列とダブルクオーテーション"
のエスケープが必要な場合はエスケープ済みの文字列を指定する必要があります。
.NET Standard 2.1/.NET Core 2.1以降では、ArgumentListプロパティを使用することもできます。 このプロパティは引数を文字列のコレクションとして指定することができ、また空白
を含む文字列とダブルクオーテーション"
のエスケープも自動的に行われます。
コマンドライン引数はArgumentsまたはArgumentListのどちらか一方のみが設定でき、両方設定した状態でProcess.Startメソッドを呼び出すとInvalidOperationExceptionをスローします。 また、Argumentsに設定した値はArgumentListには反映されず、逆もまた同様です。
/esc=" /dir1=C:\Program Files\dotnet /dir2=C:\Program Files\dotnet /dir3 = C:\Program Files\dotnet
/esc=" /dir1=C:\Program Files\dotnet /dir2="C:\Program Files\dotnet" "/dir3 = C:\Program Files\dotnet"
using System;
using System.Diagnostics;
class Sample {
static void Main(string[] args)
{
// 子プロセスとして起動された場合は、コマンドライン引数を一行ごとに表示して終了する
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CHILD"))) {
foreach (var arg in args) {
Console.WriteLine(arg);
}
return;
}
// 自プロセスと同じ実行可能ファイルに、コマンドライン引数を与えて子プロセスとして起動する
var psi = new ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName) {
#if true
// Argumentsプロパティでコマンドライン引数を指定する
Arguments = @"/esc=\"" /dir1=C:\Program Files\dotnet /dir2=""C:\Program Files\dotnet"" ""/dir3 = C:\Program Files\dotnet""",
#else
// ArgumentListプロパティでコマンドライン引数を指定する
ArgumentList = {@"/esc=""", @"/dir1=C:\Program Files\dotnet", @"/dir2=""C:\Program Files\dotnet""", @"""/dir3 = C:\Program Files\dotnet"""},
#endif
EnvironmentVariables = { {"CHILD", "1"} }, // 子プロセスとして起動したことを示す環境変数を設定しておく (再帰的なプロセス生成を抑止する)
UseShellExecute = false, // ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっているため、明示的に指定しなくても可
};
// 上記のパラメータで子プロセスを起動する
using (var child = Process.Start(psi)) {
// 子プロセスの終了を待機する
child.WaitForExit();
}
}
}
Imports System
Imports System.Diagnostics
Class Sample
Shared Sub Main(ByVal args() As String)
' 子プロセスとして起動された場合は、コマンドライン引数を一行ごとに表示して終了する
If Not String.IsNullOrEmpty(Environment.GetEnvironmentVariable("CHILD")) Then
For Each arg As String In args
Console.WriteLine(arg)
Next
Return
End If
' 自プロセスと同じ実行可能ファイルに、コマンドライン引数を与えて子プロセスとして起動する
Dim psi As New ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName)
#If True
' Argumentsプロパティでコマンドライン引数を指定する
psi.Arguments = "/esc=\"" /dir1=C:\Program Files\dotnet /dir2=""C:\Program Files\dotnet"" ""/dir3 = C:\Program Files\dotnet"""
#Else
' ArgumentListプロパティでコマンドライン引数を指定する
psi.ArgumentList.Add("/esc=""")
psi.ArgumentList.Add("/dir1=C:\Program Files\dotnet")
psi.ArgumentList.Add("/dir2=""C:\Program Files\dotnet""")
psi.ArgumentList.Add("""/dir3 = C:\Program Files\dotnet""")
#End If
psi.EnvironmentVariables("CHILD") = "1" ' 子プロセスとして起動したことを示す環境変数を設定しておく (再帰的なプロセス生成を抑止する)
psi.UseShellExecute = False ' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっているため、明示的に指定しなくても可
' 上記のパラメータで子プロセスを起動する
Using child As Process = Process.Start(psi)
' 子プロセスの終了を待機する
child.WaitForExit()
End Using
End Sub
End Class
上記のコードでは、コマンドライン引数を与えた上で再帰的にプロセスを起動しています。 上記のコードで環境変数CHILD
を指定せずに実行した場合Fork爆弾と同様の動作となるため、コードを改変する場合は注意してください。
子プロセスの標準ストリーム(標準入出力)
子プロセスの標準ストリームをリダイレクトしたい(子プロセスの標準出力を読み取る・標準入力に書き込む)場合は、ProcessStartInfo.RedirectStandardOutputをtrue
にし、同時にProcessStartInfo.UseShellExecuteをfalse
にして子プロセスを起動します。
標準出力に書き込まれた内容を読み込むには、Process.StandardOutput.ReadToEndメソッドなどによって取得します。
using System;
using System.Diagnostics;
class Sample {
static void Main()
{
// 子プロセスchild.exeの起動パラメータを作成する
var psi = new ProcessStartInfo("child.exe");
// シェルを使用せず子プロセスを起動する
// ⚠標準入出力をリダイレクトするために必要
// ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっているため、明示的に指定しなくても可
psi.UseShellExecute = false;
// 子プロセスの標準出力をリダイレクトする
psi.RedirectStandardOutput = true;
// 子プロセスを起動する
using (var child = Process.Start(psi)) {
// リダイレクトした子プロセスの標準出力からテキストを読み込む
var stdout = child.StandardOutput.ReadToEnd();
// 子プロセスの終了を待機する
child.WaitForExit();
// 読み込んだテキストを表示する
Console.WriteLine(stdout);
}
}
}
Imports System
Imports System.Diagnostics
Class Sample
Shared Sub Main()
' 子プロセスchild.exeの起動パラメータを作成する
Dim psi As New ProcessStartInfo("child.exe")
' シェルを使用せず子プロセスを起動する
' ⚠標準入出力をリダイレクトするために必要
' ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっているため、明示的に指定しなくても可
psi.UseShellExecute = False
' 子プロセスの標準出力をリダイレクトする
psi.RedirectStandardOutput = True
' 子プロセスを起動する
Using child As Process = Process.Start(psi)
' リダイレクトした子プロセスの標準出力からテキストを読み込む
Dim stdout As String = child.StandardOutput.ReadToEnd()
' 子プロセスの終了を待機する
child.WaitForExit()
' 読み込んだテキストを表示する
Console.WriteLine(stdout)
End Using
End Sub
End Class
子プロセスの標準ストリームはProcess.StandardInput・Process.StandardOutput・Process.StandardErrorプロパティよりStreamReaderクラス/StreamWriterクラスとして取得することができます。 StreamReader/StreamWriterを使った読み書きについてはStreamReaderクラス・StreamWriterクラスを参照してください。
このほか、子プロセスの標準ストリームの扱い方や、非同期での標準ストリームへの読み書きなど、より具体的な方法は子プロセスの標準入出力で解説しています。
他プロセスの取得
Process.GetProcessesメソッドを使うと、現在起動中の取得可能なプロセスを取得することができます。
また、Process.GetProcessByIdメソッド・Process.GetProcessesByNameメソッドを使うと、プロセスIDまたはプロセス名から該当するプロセスの情報を取得することができます。
プロセス間の協調
排他制御
プロセス間で排他制御を行うには、ミューテックス(System.Threading.Mutexクラス)やセマフォ(System.Threading.Semaphoreクラス)を用いることができます。
これらのクラスの詳細や、これらを使ったプロセス間排他制御の具体例については、以下のページを参照してください。
プロセス間通信
ここで紹介する手法は.NET Frameworkのみで使用できます。 .NET Core/.NET 5以降では使用できません。
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を用いたプロセス間通信を行うこともできます。