¿Puedo enviar un ctrl-C (SIGINT) a una aplicación en Windows?

Tengo (en el pasado) aplicaciones multiplataforma (Windows / Unix) escritas que, cuando comencé desde la línea de comando, manejaban una combinación CtrlC escrita por el usuario de la misma manera (es decir, terminar la aplicación limpiamente).

¿Es posible en Windows enviar un CtrlC / SIGINT / equivalente a un proceso desde otro proceso (no relacionado) para solicitar que termine limpiamente (dándole la oportunidad de ordenar recursos, etc.)?

Lo más cercano que he llegado a una solución es la aplicación SendSignal de terceros. El autor enumera el código fuente y un ejecutable. He verificado que funciona en Windows de 64 bits (ejecutándose como un progtwig de 32 bits, matando a otro progtwig de 32 bits), pero no he descubierto cómo incorporar el código en un progtwig de Windows (ya sea de 32 bits) o 64 bits).

Cómo funciona:

Después de excavar mucho en el depurador, descubrí que el punto de entrada que realmente hace el comportamiento asociado con una señal como ctrl-break es kernel32! CtrlRoutine. La función tenía el mismo prototipo que ThreadProc, por lo que se puede utilizar con CreateRemoteThread directamente, sin tener que inyectar código. Sin embargo, ¡eso no es un símbolo exportado! Está en diferentes direcciones (e incluso tiene diferentes nombres) en diferentes versiones de Windows. ¿Qué hacer?

Aquí está la solución que finalmente se me ocurrió. Instalo un controlador ctrl de consola para mi aplicación, luego genero una señal ctrl-break para mi aplicación. Cuando se llama a mi controlador, miro hacia atrás a la parte superior de la stack para averiguar los parámetros pasados ​​a kernel32! BaseThreadStart. Agarro el primer param, que es la dirección de inicio deseada del hilo, que es la dirección de kernel32! CtrlRoutine. Luego regreso de mi controlador, lo que indica que he manejado la señal y que mi aplicación no debe finalizar. De vuelta en el hilo principal, espero hasta que se haya recuperado la dirección de kernel32! CtrlRoutine. Una vez que lo tengo, creo un hilo remoto en el proceso objective con la dirección de inicio descubierta. Esto hace que los manejadores ctrl en el proceso objective sean evaluados como si se hubiera presionado ctrl-break.

Lo bueno es que solo el proceso de destino se ve afectado, y cualquier proceso (incluso un proceso de ventana) puede ser objective. Una desventaja es que mi pequeña aplicación no se puede usar en un archivo por lotes, ya que lo matará cuando envíe el evento ctrl-break para descubrir la dirección de kernel32! CtrlRoutine.

(Preceda con start si lo ejecuta en un archivo por lotes).

Hice algunas investigaciones sobre este tema, que resultó ser más popular de lo que esperaba. La respuesta de KindDragon fue uno de los puntos fundamentales.

Escribí una publicación de blog más larga sobre el tema y creé un progtwig de demostración en funcionamiento, que demuestra el uso de este tipo de sistema para cerrar una aplicación de línea de comando de un par de buenas modas. Esa publicación también enumera enlaces externos que utilicé en mi investigación.

En resumen, esos progtwigs de demostración hacen lo siguiente:

  • Inicie un progtwig con una ventana visible usando .Net, oculte con pinvoke, ejecute durante 6 segundos, muestre con pinvoke, deténgase con .Net.
  • Comience un progtwig sin una ventana usando .Net, corra durante 6 segundos, pare adjuntando consola y emitiendo ConsoleCtrlEvent

Editar: la solución modificada de KindDragon para quienes estén interesados ​​en el código aquí y ahora. Si planea iniciar otros progtwigs después de detener el primero, debe volver a habilitar el manejo de Ctrl-C; de lo contrario, el siguiente proceso heredará el estado desactivado del padre y no responderá a Ctrl-C.

 [DllImport("kernel32.dll", SetLastError = true)] static extern bool AttachConsole(uint dwProcessId); [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] static extern bool FreeConsole(); // Enumerated type for the control messages sent to the handler routine enum CtrlTypes : uint { CTRL_C_EVENT = 0, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT = 5, CTRL_SHUTDOWN_EVENT } [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId); public void StopProgram(Process proc) { //This does not require the console window to be visible. if (AttachConsole((uint)proc.Id)) { // Disable Ctrl-C handling for our program SetConsoleCtrlHandler(null, true); GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0); // Must wait here. If we don't and re-enable Ctrl-C // handling below too fast, we might terminate ourselves. proc.WaitForExit(2000); FreeConsole(); //Re-enable Ctrl-C handling or any subsequently started //programs will inherit the disabled state. SetConsoleCtrlHandler(null, false); } } 

Además, planifique una solución de contingencia si AttachConsole() o la señal enviada falla, por ejemplo, durmiendo luego esto:

 if (!proc.HasExited) { try { proc.Kill(); } catch (InvalidOperationException e){} } 

Creo que llegué un poco tarde a esta pregunta, pero escribiré algo de todos modos para cualquiera que tenga el mismo problema. Esta es la misma respuesta que le di a esta pregunta.

Mi problema es que me gustaría que mi aplicación sea una aplicación GUI, pero los procesos ejecutados deben ejecutarse en segundo plano sin ninguna ventana de consola interactiva adjunta. Creo que esta solución también debería funcionar cuando el proceso principal es un proceso de consola. Sin embargo, es posible que deba eliminar el indicador “CREATE_NO_WINDOW”.

Pude resolver esto usando GenerateConsoleCtrlEvent () con una aplicación de envoltura. La parte difícil es simplemente que la documentación no es realmente clara sobre cómo se puede utilizar y las dificultades con ella.

Mi solución se basa en lo que aquí se describe. Pero eso tampoco explicaba todos los detalles y con un error, así que aquí están los detalles sobre cómo hacerlo funcionar.

Cree una nueva aplicación auxiliar “Helper.exe”. Esta aplicación se ubicará entre su aplicación (principal) y el proceso secundario que desea que pueda cerrar. También creará el proceso hijo real. Debe tener este proceso de “intermediario” o GenerateConsoleCtrlEvent () fallará.

Utilice algún tipo de mecanismo de IPC para comunicarse desde el proceso principal al de ayudante para que el ayudante cierre el proceso secundario. Cuando el ayudante obtiene este evento llama a “GenerateConsoleCtrlEvent (CTRL_BREAK, 0)” que se cierra solo y el proceso secundario. Usé yo mismo un objeto de evento que el padre completa cuando quiere cancelar el proceso hijo.

Para crear su Helper.exe créelo con CREATE_NO_WINDOW y CREATE_NEW_PROCESS_GROUP. Y al crear el proceso hijo, créelo sin flags (0), lo que significa que derivará la consola de su principal. Si no lo haces, ignorará el evento.

Es muy importante que cada paso se haga así. He estado probando diferentes tipos de combinaciones, pero esta combinación es la única que funciona. No puedes enviar un evento CTRL_C. Devolverá el éxito pero será ignorado por el proceso. CTRL_BREAK es el único que funciona. Realmente no importa ya que ambos llamarán a ExitProcess () al final.

Tampoco puede llamar directamente a GenerateConsoleCtrlEvent () con un id. De grupo de proceso de la Id. De proceso hijo, lo que permite que el proceso auxiliar continúe vivo. Esto también fallará

Pasé todo un día intentando que esto funcionara. Esta solución funciona para mí, pero si alguien tiene algo más para agregar, hazlo. Fui por toda la red encontrando muchas personas con problemas similares pero sin una solución definitiva al problema. Cómo funciona GenerateConsoleCtrlEvent () también es un poco raro, así que si alguien conoce más detalles, por favor comparte.

Editar:

Para una aplicación GUI, la forma “normal” de manejar esto en el desarrollo de Windows sería enviar un mensaje WM_CLOSE a la ventana principal del proceso.

Para una aplicación de consola, necesita usar SetConsoleCtrlHandler para agregar un CTRL_C_EVENT .

Si la aplicación no cumple con eso, puede llamar a TerminateProcess .

De alguna manera, GenerateConsoleCtrlEvent() devuelve un error si lo llama para otro proceso, pero puede adjuntarlo a otra aplicación de consola y enviar eventos a todos los procesos secundarios.

 void SendControlC(int pid) { AttachConsole(pid); // attach to process console SetConsoleCtrlHandler(NULL, TRUE); // disable Control+C handling for our app GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // generate Control+C event } 

Debería estar claro como el cristal porque en este momento no lo es. Hay una versión modificada y comstackda de SendSignal para enviar Ctrl-C (de manera predeterminada solo envía Ctrl + Break). Aquí hay algunos binarios:

(2014-3-7): Creé tanto la versión de 32 bits como la de 64 bits con Ctrl-C, se llama SendSignalCtrlC.exe y puede descargarla en: https://dl.dropboxusercontent.com/u/49065779/ sendsignalctrlc / x86 / SendSignalCtrlC.exe https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe – Juraj Michalak

También he duplicado esos archivos por si acaso:
Versión de 32 bits: https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0
Versión de 64 bits: https://www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0

Descargo de responsabilidad: no construí esos archivos. No se realizó ninguna modificación en los archivos originales comstackdos. La única plataforma probada es la de Windows 7 de 64 bits. Se recomienda adaptar la fuente disponible en http://www.latenighthacking.com/projects/2003/sendSignal/ y comstackrla usted mismo.

Aquí está el código que uso en mi aplicación C ++.

Puntos positivos :

  • Funciona desde la aplicación de la consola
  • Funciona desde el servicio de Windows
  • No se requiere demora
  • No cierra la aplicación actual

Puntos negativos :

  • La consola principal se pierde y se crea una nueva (ver FreeConsole )
  • El cambio de consola da resultados extraños …

 // Inspired from http://stackoverflow.com/a/15281070/1529139 // and http://stackoverflow.com/q/40059902/1529139 bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent) { bool success = false; DWORD thisConsoleId = GetCurrentProcessId(); // Leave current console if it exists // (otherwise AttachConsole will return ERROR_ACCESS_DENIED) bool consoleDetached = (FreeConsole() != FALSE); if (AttachConsole(dwProcessId) != FALSE) { // Add a fake Ctrl-C handler for avoid instant kill is this console // WARNING: do not revert it or current program will be also killed SetConsoleCtrlHandler(nullptr, true); success = (GenerateConsoleCtrlEvent(dwCtrlEvent, 0) != FALSE); FreeConsole(); } if (consoleDetached) { // Create a new console if previous was deleted by OS if (AttachConsole(thisConsoleId) == FALSE) { int errorCode = GetLastError(); if (errorCode == 31) // 31=ERROR_GEN_FAILURE { AllocConsole(); } } } return success; } 

Ejemplo de uso:

 DWORD dwProcessId = ...; if (signalCtrl(dwProcessId, CTRL_C_EVENT)) { cout < < "Signal sent" << endl; } 
  void SendSIGINT( HANDLE hProcess ) { DWORD pid = GetProcessId(hProcess); FreeConsole(); if (AttachConsole(pid)) { // Disable Ctrl-C handling for our program SetConsoleCtrlHandler(NULL, true); GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // SIGINT //Re-enable Ctrl-C handling or any subsequently started //programs will inherit the disabled state. SetConsoleCtrlHandler(NULL, false); WaitForSingleObject(hProcess, 10000); } } 

De acuerdo con la identificación del proceso, podemos enviar la señal al proceso para terminar con fuerza o con gracia o cualquier otra señal.

Listar todo el proceso:

 C:\>tasklist 

Para matar el proceso:

 C:\>Taskkill /IM firefox.exe /F or C:\>Taskkill /PID 26356 /F 

Detalles:

http://tweaks.com/windows/39559/kill-processes-from-command-prompt/

En Java, usando JNA con la biblioteca Kernel32.dll, similar a una solución de C ++. Ejecuta el método principal CtrlCSender como un Proceso que simplemente obtiene la consola del proceso para enviar el evento Ctrl + C y genera el evento. Como se ejecuta por separado sin una consola, el evento Ctrl + C no necesita ser deshabilitado y habilitado de nuevo.

CtrlCSender.java – Basado en las respuestas de Nemo1024 y KindDragon .

Dado un ID de proceso conocido, esta aplicación sin consolas conectará la consola del proceso específico y generará un evento CTRL + C en él.

 import com.sun.jna.platform.win32.Kernel32; public class CtrlCSender { public static void main(String args[]) { int processId = Integer.parseInt(args[0]); Kernel32.INSTANCE.AttachConsole(processId); Kernel32.INSTANCE.GenerateConsoleCtrlEvent(Kernel32.CTRL_C_EVENT, 0); } } 

Aplicación principal : ejecuta CtrlCSender como un proceso de consola independiente

 ProcessBuilder pb = new ProcessBuilder(); pb.command("javaw", "-cp", System.getProperty("java.class.path", "."), CtrlCSender.class.getName(), processId); pb.redirectErrorStream(); pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); pb.redirectError(ProcessBuilder.Redirect.INHERIT); Process ctrlCProcess = pb.start(); ctrlCProcess.waitFor(); 

Un amigo mío sugirió una forma completamente diferente de resolver el problema y funcionó para mí. Use un vbscript como a continuación. Se inicia y la aplicación, deja que se ejecute durante 7 segundos y se cierra con Ctrl + C.

‘Ejemplo de VBScript

 Set WshShell = WScript.CreateObject("WScript.Shell") WshShell.Run "notepad.exe" WshShell.AppActivate "notepad" WScript.Sleep 7000 WshShell.SendKeys "^C" 

Encontré todo esto demasiado complicado y usé SendKeys para enviar una pulsación CTRLC a la ventana de la línea de comandos (es decir, la ventana cmd.exe) como solución alternativa.