コマンドライン実行可能ファイルのラッパークラスを書いています。この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です
私は実際に答えを見つけました。両方の回答をありがとうございましたが、私がしなければならなかったことはこれだけでした:
p.StandardInput.Close()
これにより、作成したプログラムがstdinからの読み取りを終了し、必要なものを出力します。
Ctrl + C信号の送信にGenerateConsoleCtrlEventを使用することは正しい答えであるという事実にもかかわらず、それをさまざまな.NETアプリケーションタイプで機能させるには、十分な説明が必要です。
.NETアプリケーションが独自のコンソール(WinForms/WPF/Windowsサービス/ASP.NET)を使用しない場合の基本的なフローは次のとおりです。
次のコードスニペットは、その方法を示しています。
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メインプロセスコンソールに副作用はありません):
@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です。これは単なるマジックナンバーではありません。;))
わかりました、これが解決策です。
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;
}