如何将ctrl + c发送到c#中的进程?

我正在为命令行可执行文件编写一个包装类。 这个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顺便说一句

我其实只是想出了答案。 谢谢你们的答案,但事实certificate我所要做的就是:

 p.StandardInput.Close() 

这导致我生成的程序完成从标准输入读取并输出我需要的东西。

@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有效:是的

 4080有效:错误

希望有所帮助!

(澄清一下:\ x3是hex字符3的hex转义序列,它是ctrl + c。它不仅仅是一个幻数。;))

尽管使用GenerateConsoleCtrlEvent发送Ctrl + C信号是一个正确的答案,但需要进行重要的澄清才能使其在不同的.NET应用程序类型中工作。

如果您的.NET应用程序不使用自己的控制台(WinForms / WPF / Windows服务/ ASP.NET),则基本流程为:

  1. 将主.NET进程附加到要进行Ctrl + C的进程的控制台
  2. 使用SetConsoleCtrlHandler阻止主要.NET进程因Ctrl + C事件而停止
  3. 使用GenerateConsoleCtrlEvent为当前控制台生成控制台事件(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. 创建小型支持.NET控制台程序,从程序行参数接受进程ID,在AttachConsole调用之前使用FreeConsole丢失自己的控制台,并使用上面提到的代码将Ctrl + C发送到目标进程
  2. 当需要将Ctrl + C发送到另一个控制台进程时,主.NET控制台进程只在新进程中调用此实用程序

好的,这是一个解决方案。

发送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; } 

这是我将ctrl-c发送到进程的解决方案。

而不是使用GenerateConsoleCtrlEvent,我发现如何将CTRL-C发送到进程。 仅供参考,在这种情况下,我不需要找到组进程ID。

 using System; using System.Diagnostics; using System.Text; using System.Threading; using System.Threading.Tasks; public class ConsoleAppManager { private readonly string appName; private readonly Process process = new Process(); private readonly object theLock = new object(); private SynchronizationContext context; private string pendingWriteData; public ConsoleAppManager(string appName) { this.appName = appName; this.process.StartInfo.FileName = this.appName; this.process.StartInfo.RedirectStandardError = true; this.process.StartInfo.StandardErrorEncoding = Encoding.UTF8; this.process.StartInfo.RedirectStandardInput = true; this.process.StartInfo.RedirectStandardOutput = true; this.process.EnableRaisingEvents = true; this.process.StartInfo.CreateNoWindow = true; this.process.StartInfo.UseShellExecute = false; this.process.StartInfo.StandardOutputEncoding = Encoding.UTF8; this.process.Exited += this.ProcessOnExited; } public event EventHandler ErrorTextReceived; public event EventHandler ProcessExited; public event EventHandler StandartTextReceived; public int ExitCode { get { return this.process.ExitCode; } } public bool Running { get; private set; } public void ExecuteAsync(params string[] args) { if (this.Running) { throw new InvalidOperationException( "Process is still Running. Please wait for the process to complete."); } string arguments = string.Join(" ", args); this.process.StartInfo.Arguments = arguments; this.context = SynchronizationContext.Current; this.process.Start(); this.Running = true; new Task(this.ReadOutputAsync).Start(); new Task(this.WriteInputTask).Start(); new Task(this.ReadOutputErrorAsync).Start(); } public void Write(string data) { if (data == null) { return; } lock (this.theLock) { this.pendingWriteData = data; } } public void WriteLine(string data) { this.Write(data + Environment.NewLine); } protected virtual void OnErrorTextReceived(string e) { EventHandler handler = this.ErrorTextReceived; if (handler != null) { if (this.context != null) { this.context.Post(delegate { handler(this, e); }, null); } else { handler(this, e); } } } protected virtual void OnProcessExited() { EventHandler handler = this.ProcessExited; if (handler != null) { handler(this, EventArgs.Empty); } } protected virtual void OnStandartTextReceived(string e) { EventHandler handler = this.StandartTextReceived; if (handler != null) { if (this.context != null) { this.context.Post(delegate { handler(this, e); }, null); } else { handler(this, e); } } } private void ProcessOnExited(object sender, EventArgs eventArgs) { this.OnProcessExited(); } private async void ReadOutputAsync() { var standart = new StringBuilder(); var buff = new char[1024]; int length; while (this.process.HasExited == false) { standart.Clear(); length = await this.process.StandardOutput.ReadAsync(buff, 0, buff.Length); standart.Append(buff.SubArray(0, length)); this.OnStandartTextReceived(standart.ToString()); Thread.Sleep(1); } this.Running = false; } private async void ReadOutputErrorAsync() { var sb = new StringBuilder(); do { sb.Clear(); var buff = new char[1024]; int length = await this.process.StandardError.ReadAsync(buff, 0, buff.Length); sb.Append(buff.SubArray(0, length)); this.OnErrorTextReceived(sb.ToString()); Thread.Sleep(1); } while (this.process.HasExited == false); } private async void WriteInputTask() { while (this.process.HasExited == false) { Thread.Sleep(1); if (this.pendingWriteData != null) { await this.process.StandardInput.WriteLineAsync(this.pendingWriteData); await this.process.StandardInput.FlushAsync(); lock (this.theLock) { this.pendingWriteData = null; } } } } } 

然后,在实际运行该过程并在我的主应用程序中发送CTRL-C:

  DateTime maxStartDateTime = //... some date time; DateTime maxEndDateTime = //... some later date time var duration = maxEndDateTime.Subtract(maxStartDateTime); ConsoleAppManager appManager = new ConsoleAppManager("myapp.exe"); string[] args = new string[] { "args here" }; appManager.ExecuteAsync(args); await Task.Delay(Convert.ToInt32(duration.TotalSeconds * 1000) + 20000); if (appManager.Running) { // If stilll running, send CTRL-C appManager.Write("\x3"); } 

有关详细信息,请参阅重定向控制台应用程序的标准输入和Windows如何获取已在运行的进程的进程组?

尝试实际发送密钥组合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 + Key组合…我知道发送Alt + Key所需的消息是WM_SYSTEMKEYDOWN和WM_SYSTEMKEYUP,无法告诉你关于Ctrl的信息…