C#でアプリケーションを開発する際、外部のプログラムを起動したり、現在動作しているシステムの状況を把握したりする必要が生じることが多々あります。

このようなシナリオで中心的な役割を果たすのが、System.Diagnostics名前空間に用意されているProcessクラスです。

本記事では、C#を用いたプロセス制御の基本から、高度なリダイレクト処理、パフォーマンス監視に至るまでを詳しく解説します。

開発効率を高め、より高度なシステム連携を実現するための知識を深めていきましょう。

Processクラスの基本概念と導入

Processクラスは、ローカルおよびリモートのプロセスへのアクセスを提供し、プロセスの起動や停止を制御するためのクラスです。

Windows、Linux、macOSなどのマルチプラットフォームで動作する.NETアプリケーションにおいても、OS固有のプロセス操作を抽象化して扱うことができます。

プログラミングにおいて、自前で実装するのが困難な機能を外部ツールに委ねる場合、このクラスの習得は必須と言えるでしょう。

実行中のプロセス情報を取得する

まずは、現在システムで動作しているプロセスの一覧を取得する方法を確認しましょう。

Process.GetProcesses()メソッドを使用することで、実行中のすべてのプロセスを配列として取得できます。

特定の名前を持つプロセスを探す場合は、Process.GetProcessesByName("processName")を使用するのが効率的です。

C#
// 実行中のすべてのプロセス名を出力するサンプル
using System;
using System.Diagnostics;

class Program
{
    static void Main()
    {
        // ローカルコンピュータで実行中のすべてのプロセスを取得
        Process[] processlist = Process.GetProcesses();

        foreach (Process theprocess in processlist)
        {
            // IDと名前を表示
            Console.WriteLine($"Process: {theprocess.ProcessName} ID: {theprocess.Id}");
        }
    }
}
実行結果
Process: idle ID: 0
Process: System ID: 4
Process: devenv ID: 12345
... (省略)

外部プログラムを起動する基本手順

最も頻繁に利用されるのが、Process.Start()メソッドによる外部プログラムの起動です。

最もシンプルな形態では、実行ファイルのパスを引数に渡すだけで起動が完了します。

しかし、実務では引数の指定やウィンドウの状態を制御する必要があるため、より詳細な設定が求められます。

C#
// メモ帳を起動する最も簡単な例
using System.Diagnostics;

Process.Start("notepad.exe");

詳細な制御を可能にするProcessStartInfo

プロセスの起動条件を細かくカスタマイズしたい場合は、ProcessStartInfoクラスを利用します。

このクラスを使用することで、コマンドライン引数の設定、作業ディレクトリの指定、環境変数の追加などが可能になります。

また、GUIを表示させずにバックグラウンドで処理を実行させるといった制御もこのクラスで行います。

標準入出力のパイプ処理とリダイレクト

CLI (コマンドラインインターフェース) ツールと連携する場合、その出力をC#側で受け取りたいことがあります。

RedirectStandardOutputプロパティをtrueに設定することで、外部プロセスのコンソール出力をプログラム内でキャプチャできます。

これにより、外部コマンドの結果をパースして自アプリケーションのロジックに組み込むことが可能になります。

C#
// コマンドプロンプトで「ipconfig」を実行し、その結果を取得する例
using System;
using System.Diagnostics;

class Program
{
    static void Main()
    {
        ProcessStartInfo startInfo = new ProcessStartInfo
        {
            FileName = "ipconfig.exe",
            Arguments = "/all",
            RedirectStandardOutput = true, // 出力をリダイレクト
            UseShellExecute = false,       // シェル機能を使用しない
            CreateNoWindow = true          // ウィンドウを表示しない
        };

        using (Process process = Process.Start(startInfo))
        {
            // 出力ストリームを最後まで読み込む
            string result = process.StandardOutput.ReadToEnd();
            process.WaitForExit();

            Console.WriteLine("ネットワーク設定情報:");
            Console.WriteLine(result);
        }
    }
}
実行結果
ネットワーク設定情報:
Windows IP 構成
   ホスト名 . . . . . . . . . . . . . : Desktop-PC
   プライマリ DNS サフィックス . . . . . : 
   ... (以下、ネットワーク情報の詳細が出力されます)

ウィンドウ表示とシェル実行の制御

UseShellExecuteプロパティの切り替えは、動作環境によって重要な意味を持ちます。

trueに設定すると、OSのシェル (Windowsならエクスプローラー) を介してファイルを起動します。

これにより、実行ファイルだけでなく、.html.pdfといったドキュメントファイルを、関連付けられたプログラムで開くことができます。

一方で、標準入出力のリダイレクトを行う場合は、この値をfalseにする必要がある点に注意してください。

プロセスの状態監視とパフォーマンス計測

プロセスの起動だけでなく、その後の「状態」を監視することも業務システムでは重要です。

Processクラスは、対象のプロセスがどれだけのシステムリソースを消費しているかを知るためのプロパティを豊富に備えています。

CPU使用率やメモリ使用量の取得

特定のプロセスが過負荷になっていないか、メモリリークを起こしていないかをチェックするコードを記述できます。

WorkingSet64プロパティは物理メモリの使用量を、TotalProcessorTimeプロパティはCPU時間の合計を返します。

これらを定期的にサンプリングすることで、独自の監視モニターを作成することも可能です。

主要なパフォーマンスプロパティの一覧

プロパティ名説明
Idプロセスのユニークな識別子。
StartTimeプロセスが起動した日時。
HasExitedプロセスが既に終了しているかどうか。
WorkingSet64プロセスに割り当てられている物理メモリの量 (バイト)。
TotalProcessorTimeプロセスが消費したCPU時間の合計。
PriorityClassプロセスの実行優先順位。

同期・非同期でのプロセス終了待機

外部プロセスを起動した後、その終了を待ってから次の処理へ進みたい場合があります。

この同期的な待機を実現するのがWaitForExit()メソッドです。

引数にタイムアウト時間を指定することで、プログラムが永久にフリーズするのを防ぐ設計が推奨されます。

C#
// タイムアウト付きで終了を待機する例
using System;
using System.Diagnostics;

class Program
{
    static void Main()
    {
        using (Process myProcess = Process.Start("notepad.exe"))
        {
            Console.WriteLine("メモ帳を閉じると処理が続行されます...");

            // 最大10秒間待機
            if (myProcess.WaitForExit(10000))
            {
                Console.WriteLine("プロセスが正常に終了しました。");
            }
            else
            {
                Console.WriteLine("タイムアウトしました。プロセスを強制終了します。");
                myProcess.Kill();
            }
        }
    }
}

非同期ハンドリングとExitedイベント

UIスレッドをブロックしたくないデスクトップアプリなどでは、イベントベースの非同期終了処理が有効です。

EnableRaisingEventstrueに設定し、Exitedイベントにハンドラを登録します。

これにより、プロセスが終了した瞬間に特定のメソッドをコールバックさせることができます。

セキュリティと権限管理のベストプラクティス

プロセスの制御には、常にセキュリティのリスクが伴います。

意図しないコマンドを実行されたり、高い権限を持つプロセスを悪用されたりしないよう、細心の注意が必要です。

管理者権限での実行とユーザー指定

特定の操作に管理者権限が必要な場合、ProcessStartInfo.Verb“runas” を設定することで、UAC (ユーザーアカウント制御) プロンプトを表示させることができます。

また、UserNamePasswordプロパティを使用して、特定のユーザーコンテキストでプロセスを動かすことも可能です。

ただし、パスワードの取り扱いにはSecureStringを使用するなど、メモリ上の安全性を考慮する必要があります。

リソースの適切な解放 (IDisposable)

Processクラスは、アンマネージドリソースを保持する可能性があるため、IDisposableインターフェースを実装しています。

使い終わったプロセスのインスタンスは、必ずDispose()を呼び出すか、usingブロック内で管理するようにしましょう。

これを怠ると、ファイルハンドルが解放されず、システムの安定性に悪影響を及ぼす恐れがあります。

C#
// 推奨されるリソース管理のパターン
using (Process proc = new Process())
{
    proc.StartInfo.FileName = "example.exe";
    proc.Start();
    proc.WaitForExit();
} // ここで自動的にDisposeが呼ばれる

まとめ

C#のSystem.Diagnostics.Processクラスは、外部プログラムの起動から詳細なシステム監視までをカバーする強力なツールです。

ProcessStartInfoによる柔軟な設定や、標準入出力のリダイレクトを活用することで、他のアプリケーションとの高度な連携が可能になります。

一方で、プロセスの終了待機やリソースの解放といった管理を適切に行わなければ、アプリケーションのパフォーマンス低下を招く原因にもなります。

今回解説した基本手順とベストプラクティスを意識し、安全かつ堅牢なプロセス制御を実装してみてください。

最新の.NET環境においても、このプロセス制御の知識はあらゆるシステム開発の現場で重宝されるはずです。