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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/283128/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 21:32:27  来源:igfitidea点击:

How do I send ctrl+c to a process in c#?

c#command-line.net-2.0process

提问by Kevlar

I'm writing a wrapper class for a command line executable. This exe accepts input from stdinuntil I hit Ctrl+Cin the command prompt shell, in which case it prints output to stdoutbased on the input. I want to simulate that Ctrl+Cpress in C# code, sending the kill command to a .NET Processobject. I've tried calling Process.Kill(), but that doesn't seem to give me anything in the process's StandardOutputStreamReader. Might there be anything I'm not doing right? Here's the code I'm trying to use:

我正在为命令行可执行文件编写包装类。这个 exe 接受输入,stdin直到我Ctrl+C在命令提示符 shell 中点击,在这种情况下,它会stdout根据输入打印输出。我想Ctrl+C在 C# 代码中模拟按下,将 kill 命令发送到 .NETProcess对象。我试过打电话Process.Kill(),但这似乎没有给我任何过程中的StandardOutputStreamReader. 可能有什么我做错的吗?这是我尝试使用的代码:

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

The output is always empty, even though I get data back from stdoutwhen I run the exe manually.

输出始终为空,即使我从stdout手动运行 exe 时取回数据。

Edit: This is C# 2.0 by the way.

编辑:顺便说一下,这是 C# 2.0。

采纳答案by Kevlar

I've actually just figured out the answer. Thank you both for your answers, but it turns out that all i had to do was this:

我其实刚刚想出了答案。谢谢你们的回答,但事实证明我所要做的就是:

p.StandardInput.Close()

which causes the program I've spawned to finish reading from stdin and output what i need.

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

回答by Alon L

Try actually sending the Key Combination Ctrl+C, instead of directly terminating the process:

尝试实际发送组合键 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
              );

Look it up on the MSDN, you should find what you need there in order to send the Ctrl+Key combination... I know that the message you need for sending Alt+Key is WM_SYSTEMKEYDOWN and WM_SYSTEMKEYUP, can't tell you about Ctrl...

在MSDN上查找,您应该在那里找到发送Ctrl+Key组合所需的内容...我知道发送Alt+Key所需的消息是WM_SYSTEMKEYDOWN和WM_SYSTEMKEYUP,无法告诉您Ctrl ...

回答by Rob

@alonl: The user is attempting to wrap a command-line program. Command-line programs don't have message pumps unless they are specifically created, and even if that was the case, Ctrl+Cdoesn't have the same semantics in a Windows-environment application (copy, by default) as it does in a command-line environment (Break).

@alonl:用户正在尝试包装命令行程序。命令行程序没有消息泵,除非它们是专门创建的,即使是这种情况,Ctrl+C在 Windows 环境应用程序(默认情况下复制)中也不具有与命令中相同的语义 -线路环境(Break)。

I threw this together. CtrlCClient.exe simply calls Console.ReadLine()and waits:

我把这个扔在一起。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();
}

My output seems to do what you want:

我的输出似乎做你想要的:

4080 is active: True
4080 is active: False
4080 is active: True
4080 is active: False

Hope that helps!

希望有帮助!

(To clarify: \x3is the hex escape sequence for the hex character 3, which is Ctrl+C. It's not just a magic number. ;) )

(澄清:\x3是十六进制字符 3 的十六进制转义序列,即Ctrl+C。它不仅仅是一个幻数。;))

回答by James Schopp

Ok, here is a solution.

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

The way to send the Ctrl-C signal is with GenerateConsoleCtrlEvent. HOWEVER, this call takes a processGroupdID parameter, and sends the Ctrl-C signal to all processes in the group. This would be fine if it weren't for the fact that there is no way spawn child process in .net that is in a different process group than you (the parent) are in. So, when you send the GenerateConsoleCtrlEvent, both the child AND YOU (THE PARENT) GET IT. So, you need to capture the ctrl-c event in the parent too, and then determine if you ned to ignore it not.

发送 Ctrl-C 信号的方法是使用 GenerateConsoleCtrlEvent。但是,此调用采用 processGroupdID 参数,并将 Ctrl-C 信号发送到组中的所有进程。如果不是因为在 .net 中没有办法在与您(父)所在的进程组不同的进程组中生成子进程,这会很好。因此,当您发送 GenerateConsoleCtrlEvent 时,子进程你(父母)明白了。因此,您还需要在父级中捕获 ctrl-c 事件,然后确定是否需要忽略它。

In my case, I want the parent to be able to handle Ctrl-C events also, so I need to distnguish between Ctrl-C events sent by the user on the console, and those sent by the parent process to the child. I do this by just hackishly setting/unsetting a boolean flag while send the ctrl-c to the child, and then checking for this flag in the parent's ctrl-c event handler (ie. if send ctrl-c to child, then ignore.)

就我而言,我希望父进程也能够处理 Ctrl-C 事件,因此我需要区分用户在控制台上发送的 Ctrl-C 事件和父进程发送给子进程的事件。我通过在将 ctrl-c 发送给孩子的同时设置/取消设置布尔标志,然后在父母的 ctrl-c 事件处理程序中检查此标志(即,如果将 ctrl-c 发送给孩子,则忽略。 )

So, the code would look something like this:

所以,代码看起来像这样:

//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;
}

回答by Vitaliy Fedorchenko

Despite of the fact that using GenerateConsoleCtrlEvent for sending Ctrl+C signal is a right answer it needs significant clarification to get it work in different .NET application types.

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

If your .NET application doesn't use its own console (WinForms/WPF/Windows Service/ASP.NET) basic flow is:

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

  1. Attach main .NET process to console of process you want to Ctrl+C
  2. Prevent main .NET process from stopping because of Ctrl+C event with SetConsoleCtrlHandler
  3. Generate console event for currentconsole with GenerateConsoleCtrlEvent (processGroupId should be zero! Answer with code that sends p.SessionId will not work and incorrect)
  4. Disconnect from console and restore Ctrl+C handling by main process
  1. 将主 .NET 进程附加到您想要 Ctrl+C 的进程控制台
  2. 使用 SetConsoleCtrlHandler 防止主 .NET 进程因 Ctrl+C 事件而停止
  3. 使用 GenerateConsoleCtrlEvent 为当前控制台生成控制台事件(processGroupId 应该为零!使用发送 p.SessionId 的代码将不起作用且不正确)
  4. 断开与控制台的连接并通过主进程恢复 Ctrl+C 处理

The following code snippet illustrates how to do that:

以下代码片段说明了如何做到这一点:

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

where SetConsoleCtrlHandler, FreeConsole, AttachConsole and GenerateConsoleCtrlEvent are native WinAPI methods:

其中 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);

Things become more complex if you need to send Ctrl+C from .NET console application. Approach will not work because AttachConsole returns false in this case (main console app already has a console). It is possible to call FreeConsole before AttachConsole call but as result original .NET app console will be lost which is not acceptable in most cases.

如果您需要从 .NET 控制台应用程序发送 Ctrl+C,事情会变得更加复杂。方法将不起作用,因为在这种情况下 AttachConsole 返回 false(主控制台应用程序已经有一个控制台)。在 AttachConsole 调用之前调用 FreeConsole 是可能的,但结果原始 .NET 应用程序控制台将丢失,这在大多数情况下是不可接受的。

My solution for this case (that really works and has no side effects for .NET main process console):

我针对这种情况的解决方案(确实有效并且对 .NET 主流程控制台没有副作用):

  1. Create small supporting .NET console program that accepts process ID from command line arguments, looses its own console with FreeConsole before AttachConsole call and sends Ctrl+C to target process with code mentioned above
  2. Main .NET console process just invokes this utility in new process when it needs to send Ctrl+C to another console process
  1. 创建小的支持 .NET 控制台程序,该程序接受来自命令行参数的进程 ID,在 AttachConsole 调用之前使用 FreeConsole 释放自己的控制台,并使用上述代码将 Ctrl+C 发送到目标进程
  2. 当需要将 Ctrl+C 发送到另一个控制台进程时,主 .NET 控制台进程只是在新进程中调用此实用程序

回答by Simon Mourier

FWIW, in my case, what I wanted is, from a console process, create a child console process (ffmpeg.exe for that matter) and support clean CTRL-C handling in my process and in the child process (ffmpreg exits normally when CTRL-C is pressed which is a nice feature I wanted to keep working)

FWIW,在我的情况下,我想要的是,从控制台进程,创建一个子控制台进程(ffmpeg.exe)并在我的进程和子进程中支持干净的 CTRL-C 处理(ffmpreg 正常退出时 CTRL -C 被按下,这是我想继续工作的一个很好的功能)

None of the solution I found here were working, so I just interop'd Windows' CreateProcess functionand it just works w/o any effort, CTRL-C is automatically received by the child app and by the parent app, input and output streams are shared, etc. I was not able to reproduce that kind of code using the standard .NET Process class:

我在这里找到的解决方案都没有工作,所以我只是互操作 Windows 的CreateProcess 函数,它无需任何努力就可以工作,子应用程序和父应用程序、输入和输出流会自动接收 CTRL-C是共享的,等等。我无法使用标准的 .NET Process 类重现那种代码:

    static void RunFFMpeg(string arguments)
    {
        var startup = new STARTUPINFO();
        startup.cb = Marshal.SizeOf<STARTUPINFO>();
        if (!CreateProcess(null, "ffmpeg.exe " + arguments, IntPtr.Zero, IntPtr.Zero, false, 0, IntPtr.Zero, null, ref startup, out var info))
            throw new Win32Exception(Marshal.GetLastWin32Error());

        CloseHandle(info.hProcess);
        CloseHandle(info.hThread);

        var process = Process.GetProcessById(info.dwProcessId);
        Console.CancelKeyPress += (s, e) =>
        {
            process.WaitForExit();
            Console.WriteLine("Abort.");
            // end of program is here
        };

        process.WaitForExit();
        Console.WriteLine("Exit.");
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct STARTUPINFO
    {
        public int cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public int dwX;
        public int dwY;
        public int dwXSize;
        public int dwYSize;
        public int dwXCountChars;
        public int dwYCountChars;
        public int dwFillAttribute;
        public int dwFlags;
        public short wShowWindow;
        public short cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

    [DllImport("kernel32")]
    private static extern bool CloseHandle(IntPtr hObject);

    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern bool CreateProcess(
       string lpApplicationName,
       string lpCommandLine,
       IntPtr lpProcessAttributes,
       IntPtr lpThreadAttributes,
       bool bInheritHandles,
       int dwCreationFlags,
       IntPtr lpEnvironment,
       string lpCurrentDirectory,
       ref STARTUPINFO lpStartupInfo,
       out PROCESS_INFORMATION lpProcessInformation);