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

Viewed 77425

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.

6 Answers

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.

@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+C doesn't have the same semantics in a Windows-environment application (copy, by default) as it does in a command-line environment (Break).

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

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

Hope that helps!

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

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)

None of the solution I found here were working, so I just interop'd Windows' CreateProcess function and 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:

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

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

 [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...

Related