web-dev-qa-db-ja.com

C#のプロセスにctrl + cを送信するにはどうすればよいですか?

コマンドライン実行可能ファイルのラッパークラスを書いています。このexeは、コマンドプロンプトシェルでctrl + cを押すまでstdinからの入力を受け入れます。この場合、stdoutへの入力に基づいて出力が出力されます。 Ctrl + Cキーをc#コードで押してシミュレートし、killコマンドを.Netプロセスオブジェクトに送信します。 Process.kill()を呼び出してみましたが、プロセスのStandardOutput StreamReaderには何も表示されないようです。私が正しく行っていないことはありますか?これが私が使用しようとしているコードです:

ProcessStartInfo info = new ProcessStartInfo(exe, args);
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
Process p = Process.Start(info);

p.StandardInput.AutoFlush = true;
p.StandardInput.WriteLine(scriptcode);

p.Kill(); 

string error = p.StandardError.ReadToEnd();
if (!String.IsNullOrEmpty(error)) 
{
     throw new Exception(error);
}
string output = p.StandardOutput.ReadToEnd();

ただし、exeを手動で実行するとstdoutからデータが返されても、出力は常に空です。編集:これはc#2.0です

26
Kevlar

私は実際に答えを見つけました。両方の回答をありがとうございましたが、私がしなければならなかったことはこれだけでした:

p.StandardInput.Close()

これにより、作成したプログラムがstdinからの読み取りを終了し、必要なものを出力します。

28
Kevlar

Ctrl + C信号の送信にGenerateConsoleCtrlEventを使用することは正しい答えであるという事実にもかかわらず、それをさまざまな.NETアプリケーションタイプで機能させるには、十分な説明が必要です。

.NETアプリケーションが独自のコンソール(WinForms/WPF/Windowsサービス/ASP.NET)を使用しない場合の基本的なフローは次のとおりです。

  1. メインの.NETプロセスを、Ctrl + Cを実行するプロセスのコンソールにアタッチします
  2. SetConsoleCtrlHandlerでのCtrl + Cイベントが原因でメイン.NETプロセスが停止しないようにする
  3. GenerateConsoleCtrlEventでcurrent consoleのコンソールイベントを生成します(processGroupIdはゼロである必要があります!p.SessionIdを送信するコードでの応答は機能せず、正しくありません)
  4. コンソールから切断し、メインプロセスによるCtrl + C処理を復元する

次のコードスニペットは、その方法を示しています。

Process p;
if (AttachConsole((uint)p.Id)) {
    SetConsoleCtrlHandler(null, true);
    try { 
        if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT,0))
            return false;
        p.WaitForExit();
    } finally {
        FreeConsole();
        SetConsoleCtrlHandler(null, false);
    }
    return true;
}

ここで、SetConsoleCtrlHandler、FreeConsole、AttachConsole、およびGenerateConsoleCtrlEventは、ネイティブのWinAPIメソッドです。

internal const int CTRL_C_EVENT = 0;
[DllImport("kernel32.dll")]
internal static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, uint dwProcessGroupId);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
internal static extern bool FreeConsole();
[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);
// Delegate type to be used as the Handler Routine for SCCH
delegate Boolean ConsoleCtrlDelegate(uint CtrlType);

.NETコンソールアプリケーションからCtrl + Cを送信する必要がある場合、状況はさらに複雑になります。この場合、AttachConsoleがfalseを返すため、アプローチは機能しません(メインコンソールアプリには既にコンソールがあります)。 AttachConsoleを呼び出す前にFreeConsoleを呼び出すことは可能ですが、その結果、元の.NETアプリコンソールが失われ、ほとんどの場合は受け入れられません。

この場合の私の解決策(これは実際に機能し、.NETメインプロセスコンソールに副作用はありません):

  1. コマンドライン引数からプロセスIDを受け入れ、AttachConsole呼び出しの前にFreeConsoleで独自のコンソールを緩め、上記のコードでターゲットプロセスにCtrl + Cを送信する小さなサポート.NETコンソールプログラムを作成します。
  2. メインの.NETコンソールプロセスは、Ctrl + Cを別のコンソールプロセスに送信する必要があるときに、このユーティリティを新しいプロセスで呼び出すだけです。
26

@alonl:ユーザーがコマンドラインプログラムをラップしようとしています。コマンドラインプログラムには、特別に作成されない限りメッセージポンプはありません。その場合でも、Ctrl + Cは、Windows環境のアプリケーション(デフォルトではコピー)と同じセマンティクスを持ちません。コマンドライン環境(Break)。

これを一緒に投げました。 CtrlCClient.exeは単にConsole.ReadLine()を呼び出して待機します。


        static void Main(string[] args)
        {
            ProcessStartInfo psi = new ProcessStartInfo("CtrlCClient.exe");
            psi.RedirectStandardInput = true;
            psi.RedirectStandardOutput = true;
            psi.RedirectStandardError = true;
            psi.UseShellExecute = false;
            Process proc = Process.Start(psi);
            Console.WriteLine("{0} is active: {1}", proc.Id, !proc.HasExited);
            proc.StandardInput.WriteLine("\x3");
            Console.WriteLine(proc.StandardOutput.ReadToEnd());
            Console.WriteLine("{0} is active: {1}", proc.Id, !proc.HasExited);
            Console.ReadLine();
        }

私の出力はあなたが望むことをするようです:

 4080がアクティブ:True 
 
 4080がアクティブ:False 

お役に立てば幸いです。

(明確にするために、\ x3は16進文字3の16進エスケープシーケンスで、ctrl + cです。これは単なるマジックナンバーではありません。;))

21
Rob

わかりました、これが解決策です。

Ctrl-Cシグナルを送信する方法は、GenerateConsoleCtrlEventを使用することです。ただし、この呼び出しはprocessGroupdIDパラメーターを受け取り、Ctrl-Cシグナルをグループ内のすべてのプロセスに送信します。あなた(親)とは異なるプロセスグループにある.netに子プロセスを生成する方法がないという事実がなければ、これは問題ありません。したがって、GenerateConsoleCtrlEventを送信すると、両方の子そしてあなた(親)がそれを手に入れよう。したがって、親でもctrl-cイベントをキャプチャして、無視しないようにしているかどうかを判断する必要があります。

私の場合、親がCtrl-Cイベントも処理できるようにしたいので、ユーザーがコンソールで送信したCtrl-Cイベントと、親プロセスが子に送信したイベントを区別する必要があります。これを行うには、ctrl-cを子に送信するときにブールフラグをハックで設定/設定解除し、親のctrl-cイベントハンドラでこのフラグをチェックします(つまり、ctrl-cを子に送信する場合は無視します)。 )

したがって、コードは次のようになります。

//import in the declaration for GenerateConsoleCtrlEvent
[DllImport("kernel32.dll", SetLastError=true)]  
static extern bool GenerateConsoleCtrlEvent(ConsoleCtrlEvent sigevent, int dwProcessGroupId);
public enum ConsoleCtrlEvent  
{  
    CTRL_C = 0,  
    CTRL_BREAK = 1,  
    CTRL_CLOSE = 2,  
    CTRL_LOGOFF = 5,  
    CTRL_SHUTDOWN = 6  
}

//set up the parents CtrlC event handler, so we can ignore the event while sending to the child
public static volatile bool SENDING_CTRL_C_TO_CHILD = false;
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
    e.Cancel = SENDING_CTRL_C_TO_CHILD;
}

//the main method..
static int Main(string[] args)
{
    //hook up the event handler in the parent
    Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);

    //spawn some child process
    System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
    psi.Arguments = "childProcess.exe";
    Process p = new Process();
    p.StartInfo = psi;
    p.Start();

    //sned the ctrl-c to the process group (the parent will get it too!)
    SENDING_CTRL_C_TO_CHILD = true;
    GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, p.SessionId);        
    p.WaitForExit();
    SENDING_CTRL_C_TO_CHILD = false;

    //note that the ctrl-c event will get called on the parent on background thread
    //so you need to be sure the parent has handled and checked SENDING_CTRL_C_TO_CHILD
    already before setting it to false. 1000 ways to do this, obviously.



    //get out....
    return 0;
}
8
James Schopp