我正在为命令行可执行文件编写一个包装类.这个exe接受来自stdin的输入,直到我在命令提示符shell中命中ctrl + c,在这种情况下,它根据输入到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()
这导致我产生的程序完成从标准输入读取并输出我需要的东西.
尽管使用GenerateConsoleCtrlEvent发送Ctrl + C信号是一个正确的答案,但它需要明确的澄清才能使它在不同的.NET应用程序类型中工作.
如果您的.NET应用程序不使用自己的控制台(WinForms/WPF/Windows服务/ ASP.NET),则基本流程为:
将主要.NET进程附加到要进行Ctrl + C的进程的控制台
使用SetConsoleCtrlHandler阻止主要.NET进程因Ctrl + C事件而停止
使用GenerateConsoleCtrlEvent为当前控制台生成控制台事件(processGroupId应为零!使用发送p.SessionId的代码回答将无法正常工作)
断开与控制台的连接并恢复主进程的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主进程控制台没有副作用):
创建小的支持.NET控制台程序,从命令行参数接受进程ID,在AttachConsole调用之前用FreeConsole丢失自己的控制台,并使用上面提到的代码将Ctrl + C发送到目标进程
当需要将Ctrl + C发送到另一个控制台进程时,主.NET控制台进程只在新进程中调用此实用程序
@alonl:用户正在尝试包装命令行程序.命令行程序没有消息泵,除非它们是专门创建的,即使是这种情况,Ctrl + C在Windows环境应用程序中没有相同的语义(默认情况下为copy),因为它在命令行环境(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 is active: True 4080 is active: False
希望有所帮助!
(澄清一下:\ x3是十六进制字符3的十六进制转义序列,它是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; }