2008-11-12 23 views
21

Estoy escribiendo una clase contenedora para un ejecutable de línea de comando. Este exe acepta la entrada de stdin hasta que pulse ctrl + c en el intérprete de comando shell, en cuyo caso imprime salida en función de la entrada a stdout. Quiero simular que ctrl + c presione en C# code, enviando el comando kill a un objeto de proceso .Net. Intenté llamar a Process.kill(), pero eso no parece darme nada en el StreamReader StandardOutput del proceso. ¿Podría haber algo que no esté haciendo bien? Aquí está el código que estoy tratando de usar:¿Cómo envío ctrl + c a un proceso en 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(); 

obstante salida siempre está vacío, aunque puedo recuperar los datos de vuelta de la salida estándar cuando corro el exe manualmente. de edición: se trata de C# 2.0 por cierto

Respuesta

26

De hecho, he acabo de descubrir la respuesta . Gracias a ambos por sus respuestas, pero resulta que todo lo que tenía que hacer era esto:

p.StandardInput.Close() 

que hace que el programa que he engendrado para terminar de leer de la entrada estándar y la salida de lo que necesito.

+9

Tenga en cuenta que solo funciona si el proceso está tratando de leer desde la entrada estándar. Cerrar el stdin no hace nada hasta que el programa intente leer algo de él. – Doug

+0

por qué muestra esta excepción "No se ha redireccionado a StandardIn". ? estoy usando ffmpeg para la captura de pantalla – Ahmad

-6

Intenta enviar realmente la combinación de teclas Ctrl + C, en lugar de directamente por concluido el proceso:

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

buscarlo en el MSDN, debe encontrar lo que necesita allí para enviar la combinación de teclas Ctrl + ... Sé que el mensaje que necesita para enviar Alt + Key es WM_SYSTEMKEYDOWN y WM_SYSTEMKEYUP, no puedo decirle sobre Ctrl ...

+0

¿Qué pasa si el proceso se inicia oculto sin una ventana? – FindOutIslamNow

19

@alonl: El usuario está intentando envolver un programa de línea de comandos. Los programas de línea de comandos no tienen bombas de mensajes a menos que se creen específicamente, e incluso si ese fuera el caso, Ctrl + C no tiene la misma semántica en una aplicación de entorno Windows (copia, de forma predeterminada) como lo hace en un entorno de línea de comandos (Break).

Tiré esto juntos. CtrlCClient.exe simplemente llama Console.ReadLine() y espera:


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

Mi salida parece hacer lo que quiere:

 
4080 is active: True 

4080 is active: False 

Espero que ayude!

(Para aclarar:.. \ X3 es la secuencia hexadecimal de escape para el carácter hexadecimal 3, que es Ctrl + C No es sólo un número mágico;))

+4

Utilizando un programa de prueba que asigna un delegado Console.CancelKeyPress y realiza una Console.ReadLine(); la solución propuesta de StandardInput.WriteLine ("\ x3"); completa la llamada ReadLine pero no (para mí) activa el delegado CancelKeyPress. Error/demostración de corrección incorrecta porque cualquier entrada, no solo ctrl + c, desencadena una salida de proceso? (Presionar ctrl + c en el teclado activa el delegado para mí) –

+0

@David Burg: ¿Posiblemente una corrección de error en el código de la infraestructura? La publicación fue autor de tres versiones y> hace 4 años. – Rob

+0

Para aclarar (Perdón por gravedig), esto no funciona si no está leyendo desde stdin, ya que en realidad no envía la señal –

6

Ok, aquí hay una solución.

La forma de enviar la señal Ctrl-C es con GenerateConsoleCtrlEvent. SIN EMBARGO, esta llamada toma un parámetro processGroupdID y envía la señal Ctrl-C a todos los procesos en el grupo. Esto estaría bien si no fuera por el hecho de que no hay forma de engendrar el proceso hijo en .NET que está en un grupo de procesos diferente del que usted (el padre) tiene. Por lo tanto, cuando envía GenerateConsoleCtrlEvent, tanto el niño Y USTED (EL PADRE) OBTENGA. Entonces, necesita capturar el evento ctrl-c en el padre también, y luego determinar si es necesario ignorarlo.

En mi caso, quiero que el padre también pueda manejar los eventos de Ctrl-C, así que necesito distinguir entre los eventos de Ctrl-C enviados por el usuario en la consola, y los enviados por el proceso padre al niño. Hago esto simplemente ajustando/desarmando un indicador booleano mientras envío el comando ctrl-c al niño, y luego comprobando este indicador en el controlador de eventos ctrl-c del padre (es decir, si envía ctrl-c al hijo, luego lo ignora.)

Por lo tanto, el código sería algo como esto:

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

Mejoras en la pareja a la importación según las reglas de StyleCop y FxCop. Demasiado tiempo para el comentario, por lo que intentará en línea ... –

+5

'p.SessionId' no es un parámetro correcto para' GenerateConsoleCtrlEvent'. –

+2

Este código no puede funcionar. GenerateConsoleCtrlEvent requiere la identificación del grupo de proceso, no la identificación de la sesión del terminal. – user626528

16

A pesar del hecho de que el uso de GenerateConsoleCtrlEvent para el envío de la señal Ctrl + C es una respuesta correcta que necesita una aclaración significativa para conseguir que funcione en diferentes. Tipos de aplicaciones NET.

Si su aplicación .NET no utiliza su propia consola (WinForms/WPF/servicio de Windows/ASP.NET) de flujo básico es:

  1. proceso de conexión a la consola .NET principal del proceso que desea Ctrl + C
  2. Prevenir proceso principal .NET se detenga debido evento Ctrl + C con SetConsoleCtrlHandler
  3. Generar evento consola para actual consola con GenerateConsoleCtrlEvent (processGroupId debe ser cero! Responder con código que envía p.SessionId no lo hará trabajo e incorrecto)
  4. Desconectar de la consola y restaurar Ctrl + C manipulación por el proceso principal

El siguiente fragmento de código muestra cómo hacerlo:

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

donde SetConsoleCtrlHandler, FreeConsole, AttachConsole y GenerateConsoleCtrlEvent son métodos WinAPI nativas:

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

Las cosas se vuelven más complejas si necesita enviar Ctrl + C desde la aplicación de consola .NET. El enfoque no funcionará porque AttachConsole devuelve falso en este caso (la aplicación de la consola principal ya tiene una consola). Es posible llamar a FreeConsole antes de la llamada a AttachConsole, pero como resultado se perderá la consola de la aplicación .NET original, lo cual no es aceptable en la mayoría de los casos.

Mi solución para este caso (que realmente funciona y no tiene efectos secundarios para la consola principal proceso .NET):

  1. crear pequeñas programa de consola .NET de apoyo que acepta el ID del proceso de argumentos de línea de comandos, pierde su propia consola con FreeConsole antes de la llamada y envía AttachConsole Ctrl + C para procesos con código mencionado anteriormente
  2. proceso de consola .NET principal objetivo simplemente invoca esta utilidad en el nuevo proceso cuando se van a enviar Ctrl + C para otro proceso de consola
+0

Esta es, de hecho, la única solución que funcionó para mí al usar System.Diagnostics.Process. ¡Gracias! –

+0

Este fragmento cierra la aplicación principal (WinForms) si se llama dos veces. Lo até a presionar una tecla en mi aplicación, y lo cierra incluso cuando la tecla se presiona con varios segundos de diferencia y el fragmento completo está dentro de una declaración 'lock'. – Alexey

Cuestiones relacionadas