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

时间:2008-11-12 05:38:15

标签: c# command-line .net-2.0 process

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

6 个答案:

答案 0 :(得分:27)

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

p.StandardInput.Close()

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

答案 1 :(得分:23)

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

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

  1. 将主要.NET进程附加到要进行Ctrl + C
  2. 的进程的控制台
  3. 由于使用SetConsoleCtrlHandler
  4. 的Ctrl + C事件,阻止主.NET进程停止
  5. 使用GenerateConsoleCtrlEvent为当前控制台生成控制台事件(processGroupId应为零!使用发送p.SessionId的代码回答将无法正常工作)
  6. 断开与控制台的连接,并按主进程
  7. 恢复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控制台进程只在新进程中调用此实用程序

答案 2 :(得分:22)

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

4080 is active: False

希望有所帮助!

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

答案 3 :(得分:7)

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

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

答案 4 :(得分:0)

就我而言,FWIW我想要的是从控制台进程中创建一个子控制台进程(针对此问题为ffmpeg.exe),并在我的进程和子进程中支持干净的CTRL-C处理(ffmpreg退出)通常在按下CTRL-C时,这是我想继续使用的好功能)

我在这里找到的所有解决方案都没有用,所以我只是互操作了Windows'CreateProcess function而无须任何努力,子应用程序和父应用程序会自动接收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);

答案 5 :(得分:-7)

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