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

问题描述:

我正在为命令行可执行文件编写包装类。这个exe接受来自stdin的输入,直到我在命令提示符shell中按ctrl + c,在这种情况下,它会根据stdout的输入打印输出。我想模拟ctrl + c在c#代码中按下,发送kill命令给.Net过程对象。我曾尝试调用Process.kill(),但似乎并没有在流程的StandardOutput StreamReader中给我任何东西。可能有什么我不正确的做法?下面是我尝试使用代码:如何发送ctrl + c到c#中的进程?

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(); 

但输出始终是空的,即使我得到的数据,从stdout中回来时,我手动运行exe。 编辑:这是C#2.0 BTW

我其实只是想出了答案。谢谢大家既为你的答案,但事实证明,我必须做的是这样的:

p.StandardInput.Close() 

导致我产生了要完成从标准输入和输出读什么我需要的程序。

+9

请注意,只有当进程正在尝试从标准输入读取时,它才起作用。直到程序试图从中读取某些东西,关闭stdin才会执行任何操作。 – Doug 2013-03-12 23:00:50

尝试实际发送组合键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 + Key的消息是WM_SYSTEMKEYDOWN和WM_SYSTEMKEYUP,不能告诉你有关Ctrl ...

+0

如果该过程在没有窗口的情况下开始隐藏? – FindOutIslamNow 2017-11-28 12:21:20

@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 is active: True 

4080 is active: False 

希望帮助!

(澄清:\ X3是十六进制字符3,这是Ctrl + C十六进制转义序列它不仅是一个神奇的数字;))

+4

使用既分配Console.CancelKeyPress委托并执行Console.ReadLine()的测试程序; StandardInput.WriteLine(“\ x3”)的建议解决方案;不会完成ReadLine调用,但不会(对我来说)触发CancelKeyPress委托。错误/错误的正确性演示,因为任何输入,而不仅仅是ctrl + c,会触发一个进程退出? (在键盘上按ctrl + c确实会触发代表) – 2013-01-15 02:40:51

+0

@David Burg:可能是框架代码中的错误修正?这篇文章创作了三个版本,并在4年前出版。 – Rob 2013-01-15 06:17:47

+0

若要澄清(对于gravedig抱歉),如果您没有从stdin读取数据,则这不起作用,因为它实际上并未发送信号 – 2015-06-01 19:26:25

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

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

尽管事实上,使用GenerateConsoleCtrlEvent发送按Ctrl + C信号是需要显著澄清得到它在不同的工作中正确的答案。 NET应用程序类型。

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

  1. 连接主.NET程序要安慰过程
  2. 产生当前控制台GenerateConsoleCtrlEvent(processGroupId控制台的事件按Ctrl + C
  3. 防止主.NET从SetConsoleCtrlHandler因为按Ctrl + C事件停止过程应该是零!与发送p.SessionId代码答案不工作和不正确)
  4. 012从控制台
  5. 断开和恢复按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小的支持.NET控制台程序,失去其自己的控制台FreeConsole AttachConsole呼叫之前,发送Ctrl + C可针对以上
  2. 主要.NET控制台进程中提到码进程只是在调用新工艺此实用工具时,它需要按Ctrl + C发送到另一台主机的过程