Pregunta

He escrito (en el pasado) aplicaciones multiplataforma (Windows / Unix) que, cuando se inició desde la línea de comandos, manejó un Ctrl escrito por el usuario - C combinación de la misma manera (es decir, para finalizar la aplicación de forma limpia).

¿Es posible en Windows enviar un Ctrl - C / SIGINT / equivalente a un proceso de otro proceso (no relacionado) para solicitar que finalice de forma limpia (dándole) una oportunidad para ordenar recursos, etc.)?

¿Fue útil?

Solución

Lo más cercano que he encontrado a una solución es SendSignal aplicación 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 programa de 32 bits, matando a otro programa de 32 bits), pero no he descubierto cómo incrustar el código en un programa de Windows (ya sea de 32 bits). o 64 bits).

Cómo funciona:

  

Después de mucho investigar en el depurador, descubrí que el punto de entrada que realmente realiza 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 usar directamente con CreateRemoteThread, sin tener que inyectar código. Sin embargo, eso no es un símbolo exportado! Se encuentra en diferentes direcciones (e incluso tiene nombres diferentes) en diferentes versiones de Windows. ¿Qué hacer?

     

Aquí está la solución que finalmente se me ocurrió. Instalo un controlador de ctrl de consola para mi aplicación, luego genero una señal de ctrl-break para mi aplicación. Cuando se llama a mi manejador, miro hacia atrás en la parte superior de la pila para averiguar los parámetros pasados ??a kernel32! BaseThreadStart. Agarro el primer parámetro, que es la dirección de inicio deseada del hilo, que es la dirección de kernel32! CtrlRoutine. Luego vuelvo de mi controlador, lo que indica que he manejado la señal y que mi aplicación no se 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 de destino con la dirección de inicio descubierta. ¡Esto hace que los controladores de control en el proceso de destino se evalúen como si se hubiera presionado ctrl-break!

     

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

(Precede con start si lo ejecutas en un archivo por lotes)

Otros consejos

He investigado sobre este tema, que resultó ser más popular de lo que esperaba. La respuesta de KindDragon fue uno de los puntos clave.

Escribí una publicación de blog más larga sobre el tema y creé un programa de demostración funcional que demuestra usando este tipo de sistema para cerrar una aplicación de línea de comandos en un par de modas agradables. Esa publicación también enumera los enlaces externos que usé en mi investigación.

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

  • Inicie un programa con una ventana visible usando .Net, oculte con pinvoke, ejecute durante 6 segundos, muestre con pinvoke, deténgase con .Net.
  • Inicie un programa sin una ventana usando .Net, ejecute durante 6 segundos, deténgase conectando la consola y emitiendo ConsoleCtrlEvent

Editar: la solución modificada de KindDragon para aquellos que estén interesados ??en el código aquí y ahora. Si planea iniciar otros programas después de detener el primero, debe volver a habilitar el manejo de Ctrl-C, de lo contrario, el siguiente proceso heredará el estado de desactivación de los padres 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();

[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);

delegate bool ConsoleCtrlDelegate(CtrlTypes CtrlType);

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

    //Moved this command up on suggestion from Timothy Jannace (see comments below)
    FreeConsole();

    // Must wait here. If we don't and re-enable Ctrl-C
    // handling below too fast, we might terminate ourselves.
    proc.WaitForExit(2000);

    //Re-enable Ctrl-C handling or any subsequently started
    //programs will inherit the disabled state.
    SetConsoleCtrlHandler(null, false); 
  }
}

También, planee una solución de contingencia si AttachConsole () o la señal enviada debe fallar, por ejemplo, durmiendo esto:

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

Supongo que llego un poco tarde a esta pregunta, pero escribiré algo de todos modos para cualquier persona que tenga el mismo problema. Esta es la misma respuesta que di a this pregunta.

Mi problema era que me gustaría que mi aplicación fuera una aplicación GUI, pero los procesos ejecutados deberían ejecutarse en segundo plano sin ninguna ventana de consola interactiva adjunta. Creo que esta solución también debería funcionar cuando el proceso padre es un proceso de consola. Es posible que deba eliminar la " CREATE_NO_WINDOW " bandera aunque.

Logré resolver esto usando GenerateConsoleCtrlEvent () con una aplicación de envoltorio. La parte difícil es que la documentación no es realmente clara sobre cómo se puede usar exactamente y los inconvenientes que conlleva.

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

Crear una nueva aplicación de ayuda " Helper.exe " ;. Esta aplicación se ubicará entre su aplicación (padre) y el proceso hijo que desea poder cerrar. También creará el proceso hijo real. Debes tener este " hombre medio " el proceso o GenerateConsoleCtrlEvent () fallará.

Utilice algún tipo de mecanismo de IPC para comunicarse desde el proceso principal al proceso de ayuda, de modo que el asistente ayude a cerrar el proceso secundario. Cuando el ayudante obtiene este evento, llama a " GenerateConsoleCtrlEvent (CTRL_BREAK, 0) " Lo que se cierra a sí mismo y al proceso infantil. Usé un objeto de evento para este mismo que el padre completa cuando desea cancelar el proceso secundario.

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

Es muy importante que cada paso se realice de esta manera. 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 a GenerateConsoleCtrlEvent () con un id de grupo de proceso del id de proceso secundario, lo que permite que el proceso de ayuda continúe viviendo. Esto también fallará.

Pasé todo un día tratando de hacer que esto funcionara. Esta solución funciona para mí, pero si alguien tiene algo más que agregar, por favor, hágalo. Recorrí toda la red y encontré a muchas personas con problemas similares pero sin una solución definitiva al problema. El funcionamiento de GenerateConsoleCtrlEvent () también es un poco extraño, por lo que si alguien sabe más detalles sobre él, compártelo.

Editar:

Para una aplicación GUI, la " normal " La forma 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, debe usar SetConsoleCtrlHandler para agregar un CTRL_C_EVENT .

Si la aplicación no respeta 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
}

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

Puntos positivos:

  • Funciona desde la aplicación de 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 (consulte 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);
            }
        }

Debe quedar claro porque en este momento no lo es. Hay una versión modificada y compilada de SendSignal para enviar Ctrl-C (de forma predeterminada, solo envía Ctrl + Interrupción). Aquí hay algunos binarios:

  

(2014-3-7): Construí versiones de 32 bits y 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" rel = "nofollow"> https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC. exe - Juraj Michalak

También he reflejado 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 compilé esos archivos. No se realizó ninguna modificación al compilado. Archivos originales. La única plataforma probada es Windows 7 de 64 bits. Se recomienda adaptar la fuente disponible en http : //www.latenighthacking.com/projects/2003/sendSignal/ y compílalo usted mismo.

En Java, usar JNA con la biblioteca Kernel32.dll, similar a una solución C ++. Ejecuta el método principal CtrlCSender como un proceso que solo 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 nuevamente.

CtrlCSender.java - Basado en Nemo1024's y KindDragon's respuestas .

Dado un ID de proceso conocido, esta aplicación consolidada adjuntará la consola del proceso dirigido y generará un evento CTRL + C en ella.

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 consolidación separado

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 me funcionó. Utilice un vbscript como abajo. Se inicia y la aplicación, déjelo funcionar durante 7 segundos y ciérrelo 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"

En función de la identificación del proceso, podemos enviar la señal al proceso para que finalice 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-process-from-command-prompt /

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

Sí. El proyecto windows-kill hace exactamente lo que desea:

windows-kill -SIGINT 1234
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top