44

コマンドライン実行可能ファイルのラッパー クラスを作成しています。この exe は、コマンド プロンプト シェルでstdinヒットするまでからの入力を受け入れます。ヒットした場合、入力に基づいてCtrl+C出力を出力します。そのプレスを C# コードでシミュレートし、kill コマンドを .NETオブジェクトに送信stdoutしたいと考えています。を呼び出してみましたが、プロセスの. 私のやり方が間違っているのではないでしょうか?使用しようとしているコードは次のとおりです。Ctrl+CProcessProcess.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();

stdoutexeを手動で実行したときにデータが戻ってきても、出力は常に空です。

編集:ちなみにこれはC#2.0です。

4

6 に答える 6

47

+シグナルGenerateConsoleCtrlEvent()の送信に使用することが正しい答えであるという事実にもかかわらず、さまざまな .NET アプリケーション タイプで動作させるには、大幅な明確化が必要です。CtrlC

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

  1. Ctrl+でシグナルを送るプロセスのコンソールに、メインの .NET プロセスをアタッチしますC
  2. Ctrlでシグナルの処理を無効にすることにより、メインの .NET プロセスが+Cイベントによって停止するのを防ぎますSetConsoleCtrlHandler()
  3. 現在のコンソールのコンソールイベントを生成しますGenerateConsoleCtrlEvent()(processGroupIdゼロにする必要があります! 送信するコードの答えp.SessionIdは機能せず、正しくありません)。
  4. シグナルされたプロセスが応答するのを待ちます (例: プロセスが終了するのを待つ)
  5. 復元Ctrl+Cメイン プロセスによる処理とコンソールからの切断。

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

Process p;
if (AttachConsole((uint)p.Id)) {
    SetConsoleCtrlHandler(null, true);
    try { 
        if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT,p.SessionId))
            return false;
        p.WaitForExit();
    } finally {
        SetConsoleCtrlHandler(null, false);
        FreeConsole();
    }
    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);

対象のプロセスが応答するのを待つこと (通常はプロセスが終了するのを待つこと) が重要であることに注意してください。それ以外の場合、Ctrl+Cシグナルは現在のプロセスの入力キューに残り、 への 2 回目の呼び出しによって処理が復元されるとSetConsoleCtrlHandler()、そのシグナルは対象プロセスではなく現在のプロセスを終了します。

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

この場合の私の解決策は次のとおりです。これは機能し、.NET メイン プロセス コンソールに副作用はありません。

  1. コマンド ライン引数からプロセス ID を受け取り、呼び出しFreeConsole()の前に独自のコンソールを失い、上記のコードでターゲット プロセスに+を送信する、小さなサポート .NET コンソール プログラムを作成します。AttachConsole()CtrlC
  2. メインの .NET コンソール プロセスは、別のコンソール プロセスにCtrl+を送信する必要があるときに、新しいプロセスでこのユーティリティを呼び出すだけです。C
于 2015-03-26T08:48:58.603 に答える
37

私は実際に答えを見つけました。両方の回答に感謝しますが、私がしなければならなかったのはこれだけでした。

p.StandardInput.Close()

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

于 2008-11-12T19:27:34.873 に答える
23

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

これをまとめました。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 is active: True
4080 is active: False

それが役立つことを願っています!

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

于 2008-11-12T08:51:14.220 に答える
10

わかりました、ここに解決策があります。

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

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

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

//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;
}
于 2011-09-06T17:21:04.627 に答える
-6

プロセスを直接終了するのではなく、実際にキーの組み合わせ Ctrl+C を送信してみてください。

 [DllImport("user32.dll")]
        public static extern int SendMessage(
              int hWnd,      // handle to destination window
              uint Msg,       // message
              long wParam,  // first message parameter
              long lParam   // second message parameter
              );

MSDN で調べてください。Ctrl+キーの組み合わせを送信するために必要なものが見つかるはずです... Alt+キーを送信するために必要なメッセージは WM_SYSTEMKEYDOWN と WM_SYSTEMKEYUP で、Ctrl についてはわかりません。 ...

于 2008-11-12T05:50:26.940 に答える