Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
583 views
in Technique[技术] by (71.8m points)

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

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

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 stdout when I run the exe manually.

Edit: This is C# 2.0 by the way.

question from:https://stackoverflow.com/questions/283128/how-do-i-send-ctrlc-to-a-process-in-c

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

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

  1. Attach the main .NET process to the console of the process that you want to signal with Ctrl+C.
  2. Prevent the main .NET process from stopping because of Ctrl+C event by disabling handling of the signal with SetConsoleCtrlHandler().
  3. Generate the console event for the current console with GenerateConsoleCtrlEvent() (processGroupId should be zero! The answer with code that sends p.SessionId will not work and is incorrect).
  4. Wait for the signaled process to respond (e.g. by waiting for it to exit)
  5. Restore Ctrl+C handling by main process and disconnect from console.

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 {
        SetConsoleCtrlHandler(null, false);
        FreeConsole();
    }
    return true;
}

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

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

Note that waiting for the targeted process to respond, typically by waiting for the process to exit, is critical. Otherwise, the Ctrl+C signal will remain in the current process's input queue and when handling is restored by the second call to SetConsoleCtrlHandler(), that signal will terminate the current process, rather than the targeted one.

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

Here is my solution for this case; it works and has no side effects for the .NET main process console:

  1. Create small supporting .NET console program that accepts process ID from command line arguments, loses its own console with FreeConsole() before the AttachConsole() call and sends Ctrl+C to the target process with code mentioned above.
  2. The main .NET console process just invokes this utility in a new process when it needs to send Ctrl+C to another console process.

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...