Posso inviare un Ctrl-C (SIGINT) a un’applicazione su Windows?

Ho (in passato) scritto applicazioni multipiattaforma (Windows / Unix) che, quando sono state avviate dalla riga di comando, gestivano una combinazione CtrlC digitata dall’utente nello stesso modo (cioè per terminare l’applicazione in modo pulito).

Su Windows è ansible inviare un CtrlC / SIGINT / equivalente a un processo da un altro processo (non correlato) per richiedere che termini correttamente (dandogli l’opportunità di riordinare le risorse, ecc.)?

Il più vicino a cui sono arrivato a una soluzione è l’ app di terze parti SendSignal . L’autore elenca il codice sorgente e un eseguibile. Ho verificato che funzioni con Windows a 64 bit (in esecuzione come un programma a 32 bit, uccidendo un altro programma a 32 bit), ma non ho capito come incorporare il codice in un programma Windows (a 32 bit o 64 bit).

Come funziona:

Dopo aver scavato molto nel debugger ho scoperto che il punto di ingresso che effettivamente fa il comportamento associato a un segnale come ctrl-break è kernel32! CtrlRoutine. La funzione aveva lo stesso prototipo di ThreadProc, quindi può essere utilizzata direttamente con CreateRemoteThread, senza dover inserire codice. Tuttavia, questo non è un simbolo esportato! È su indirizzi diversi (e ha anche nomi diversi) su diverse versioni di Windows. Cosa fare?

Ecco la soluzione che alla fine mi è venuta in mente. Installa un gestore di console ctrl per la mia app, quindi genera un segnale di ctrl-break per la mia app. Quando il mio gestore viene chiamato, guardo in cima alla pila per scoprire i parametri passati a kernel32! BaseThreadStart. Prendo il primo parametro, che è l’indirizzo iniziale desiderato del thread, che è l’indirizzo di kernel32! CtrlRoutine. Quindi torno dal gestore, indicando che ho gestito il segnale e che la mia app non deve essere chiusa. Tornando al thread principale, aspetto che l’indirizzo di kernel32! CtrlRoutine sia stato recuperato. Una volta ottenuto, creo un thread remoto nel processo di destinazione con l’indirizzo iniziale rilevato. Ciò fa in modo che i gestori di ctrl nel processo di destinazione vengano valutati come se fosse stato premuto ctrl-break!

La cosa bella è che solo il processo di destinazione è interessato e qualsiasi processo (anche un processo a windows) può essere preso di mira. Uno svantaggio è che la mia piccola app non può essere usata in un file batch, dato che lo ucciderà quando invierà l’evento ctrl-break per scoprire l’indirizzo di kernel32! CtrlRoutine.

(Precedilo con start se lo si esegue in un file batch).

Ho svolto alcune ricerche su questo argomento, che si è rivelato più popolare di quanto immaginassi. La risposta di KindDragon fu uno dei punti cardine.

Ho scritto un post sul blog più lungo sull’argomento e ho creato un programma dimostrativo funzionante, che dimostra di usare questo tipo di sistema per chiudere un’applicazione della riga di comando in un paio di belle mode. Quel post elenca anche i link esterni che ho usato nella mia ricerca.

In breve, questi programmi dimostrativi fanno quanto segue:

  • Avvia un programma con una finestra visibile usando .Net, nascondi con pinvoke, corri per 6 secondi, mostra con pinvoke, fermati con .Net.
  • Avviare un programma senza una finestra utilizzando .Net, eseguire per 6 secondi, interrompere collegando la console ed emettendo ConsoleCtrlEvent

Modifica: la soluzione modificata di KindDragon per coloro che sono interessati al codice qui e ora. Se si prevede di avviare altri programmi dopo aver arrestato il primo, è necessario ritriggersre la gestione Ctrl-C, altrimenti il ​​processo successivo erediterà lo stato disabilitato del genitore e non risponderà 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); } } 

Inoltre, pianificare una soluzione di emergenza nel caso in cui AttachConsole() o il segnale inviato non AttachConsole() mancare, ad esempio, se si dorme, allora:

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

Credo di essere un po ‘in ritardo su questa domanda, ma scriverò comunque qualcosa per chiunque abbia lo stesso problema. Questa è la stessa risposta che ho dato a questa domanda.

Il mio problema era che mi piacerebbe che la mia applicazione fosse un’applicazione GUI, ma i processi eseguiti dovrebbero essere eseguiti in background senza alcuna finestra della console intertriggers collegata. Penso che questa soluzione dovrebbe funzionare anche quando il processo genitore è un processo di console. Potrebbe essere necessario rimuovere il flag “CREATE_NO_WINDOW”.

Sono riuscito a risolvere questo problema utilizzando GenerateConsoleCtrlEvent () con un’app wrapper. La parte difficile è solo che la documentazione non è chiara su come esattamente può essere utilizzato e le insidie ​​con esso.

La mia soluzione è basata su ciò che è descritto qui . Ma questo non ha davvero spiegato tutti i dettagli e con un errore, quindi ecco i dettagli su come farlo funzionare.

Creare una nuova applicazione di supporto “Helper.exe”. Questa applicazione si posizionerà tra l’applicazione (genitore) e il processo figlio che si desidera poter chiudere. Creerà anche il processo figlio effettivo. È necessario disporre di questo processo “middle man” o GenerateConsoleCtrlEvent () non riuscirà.

Utilizzare una sorta di meccanismo IPC per comunicare dal genitore al processo di supporto che l’helper deve chiudere il processo figlio. Quando l’helper riceve questo evento, chiama “GenerateConsoleCtrlEvent (CTRL_BREAK, 0)” che chiude se stesso e il processo figlio. Ho usato un object evento per questo me stesso che il genitore completa quando vuole cancellare il processo figlio.

Per creare il tuo Helper.exe, crealo con CREATE_NO_WINDOW e CREATE_NEW_PROCESS_GROUP. E quando si crea il processo figlio lo si crea senza flag (0), il che significa che deriverà la console dal suo genitore. In caso contrario, ignorerà l’evento.

È molto importante che ogni passo sia fatto in questo modo. Ho provato diversi tipi di combinazioni, ma questa combinazione è l’unica che funziona. Non puoi inviare un evento CTRL_C. Restituirà successo ma verrà ignorato dal processo. CTRL_BREAK è l’unico che funziona. Non ha molta importanza dal momento che entrambi chiameranno ExitProcess () alla fine.

Non è inoltre ansible chiamare GenerateConsoleCtrlEvent () con un ID di processo groupd dell’ID processo figlio che consente direttamente al processo di supporto di continuare a vivere. Anche questo fallirà.

Ho passato un’intera giornata a cercare di farlo funzionare. Questa soluzione funziona per me, ma se qualcuno ha qualcos’altro da aggiungere, per favore fallo. Sono andato in giro per la rete trovando molte persone con problemi simili ma nessuna soluzione definitiva al problema. Il funzionamento di GenerateConsoleCtrlEvent () è anche un po ‘strano, quindi se qualcuno conosce più dettagli, per favore condividi.

Modificare:

Per un’applicazione GUI, il modo “normale” di gestirlo nello sviluppo di Windows sarebbe quello di inviare un messaggio WM_CLOSE alla finestra principale del processo.

Per un’app console, è necessario utilizzare SetConsoleCtrlHandler per aggiungere un CTRL_C_EVENT .

Se l’applicazione non lo rispetta, puoi chiamare TerminateProcess .

In qualche modo GenerateConsoleCtrlEvent() restituisce un errore se lo si chiama per un altro processo, ma è ansible collegarlo a un’altra applicazione console e inviare un evento a tutti i processi figlio.

 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 } 

Dovrebbe essere reso chiaro perché al momento non lo è. C’è una versione modificata e compilata di SendSignal per inviare Ctrl-C (di default invia solo Ctrl + Break). Ecco alcuni binari:

(2014-3-7): Ho creato sia la versione a 32 bit che a 64 bit con Ctrl-C, si chiama SendSignalCtrlC.exe e puoi scaricarla su: https://dl.dropboxusercontent.com/u/49065779/ sendsignalctrlc / x86 / SendSignalCtrlC.exe https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe – Juraj Michalak

Ho anche rispecchiato questi file nel caso in cui:
Versione a 32 bit: https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0
Versione a 64 bit: https://www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0

Disclaimer: non ho creato questi file. Nessuna modifica è stata apportata ai file originali compilati. L’unica piattaforma testata è Windows 7 a 64 bit. Si consiglia di adattare la fonte disponibile all’indirizzo http://www.latenighthacking.com/projects/2003/sendSignal/ e compilarla da soli.

Ecco il codice che uso nella mia app C ++.

Punti positivi:

  • Funziona dall’app console
  • Funziona dal servizio di Windows
  • Nessun ritardo richiesto
  • Non chiude l’app corrente

Punti negativi:

  • La console principale è persa e ne viene creata una nuova (vedi FreeConsole )
  • La commutazione della console dà strani risultati …

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

Esempio di utilizzo:

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

Sulla base del processo id, possiamo inviare il segnale al processo per terminare con forza o con garbo o con qualsiasi altro segnale.

Elenca tutti i processi:

 C:\>tasklist 

Per uccidere il processo:

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

Dettagli:

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

In Java, utilizzando JNA con la libreria Kernel32.dll, simile a una soluzione C ++. Esegue il metodo principale CtrlCSender come un processo che ottiene la console del processo per inviare l’evento Ctrl + C e genera l’evento. Poiché viene eseguito separatamente senza console, l’evento Ctrl + C non deve essere disabilitato e abilitato di nuovo.

CtrlCSender.java – Basato sulle risposte di Nemo1024 e KindDragon .

Dato un ID di processo noto, questa applicazione di consolanza collegherà la console del processo mirato e genererà un evento CTRL + C su di esso.

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

Applicazione principale : esegue CtrlCSender come processo di consolatorio separato

 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 mio amico ha suggerito un modo completamente diverso di risolvere il problema e ha funzionato per me. Usa un vbscript come sotto. Inizia e applica, lascia che funzioni per 7 secondi e lo chiuda usando ctrl + c .

‘Esempio VBScript

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

Ho trovato tutto questo troppo complicato e usato SendKeys per inviare un tasto CTRLC alla finestra della riga di comando (es. Finestra cmd.exe) come soluzione alternativa.