Domanda

Ho (in passato) scritto applicazioni multipiattaforma (Windows / Unix) che, quando avviate dalla riga di comando, gestivano un Ctrl - C combinazione allo stesso modo (ovvero per terminare l'applicazione in modo pulito).

È possibile su Windows inviare un Ctrl - C / SIGINT / equivalente a un processo da un altro processo (non correlato) per richiedere che si concluda in modo pulito (dandogli un'opportunità per riordinare le risorse ecc.)?

È stato utile?

Soluzione

Il più vicino a cui sono arrivato a una soluzione è SendSignal app di terze parti. L'autore elenca il codice sorgente e un eseguibile. Ho verificato che funziona con Windows a 64 bit (in esecuzione come 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 svolge effettivamente 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 iniettare codice. Tuttavia, questo non è un simbolo esportato! È a indirizzi diversi (e ha anche nomi diversi) su diverse versioni di Windows. Cosa fare?

     

Ecco la soluzione che ho finalmente trovato. Installo un gestore ctrl console per la mia app, quindi genera un segnale di interruzione ctrl per la mia app. Quando viene chiamato il mio gestore, guardo indietro nella parte superiore dello stack 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 ritorno dal mio gestore, indicando che ho gestito il segnale e la mia app non deve essere terminata. Indietro nel thread principale, aspetto fino a quando l'indirizzo di kernel32! CtrlRoutine è stato recuperato. Una volta ottenuto, creo un thread remoto nel processo di destinazione con l'indirizzo iniziale scoperto. Questo fa sì che i gestori ctrl nel processo target 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 con finestre) può essere preso di mira. Un aspetto negativo è che la mia piccola app non può essere utilizzata in un file batch, poiché la ucciderà quando invierà l'evento ctrl-break per scoprire l'indirizzo di kernel32! CtrlRoutine.

(Precede con start se eseguito in un file batch.)

Altri suggerimenti

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

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

In breve, quei programmi demo effettuano le seguenti operazioni:

  • Avvia un programma con una finestra visibile usando .Net, nascondi con pinvoke, esegui per 6 secondi, mostra con pinvoke, termina con .Net.
  • Avvia un programma senza una finestra usando .Net, esegui per 6 secondi, ferma collegando la console ed emettendo ConsoleCtrlEvent

Modifica: la soluzione modificata di KindDragon per coloro che sono interessati al codice qui e ora. Se prevedi di avviare altri programmi dopo aver interrotto il primo, dovresti riattivare 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();

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

Inoltre, pianificare una soluzione di emergenza se AttachConsole () o il segnale inviato dovesse fallire, ad esempio dormendo quindi:

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

Suppongo 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 this domanda.

Il mio problema era che vorrei che la mia applicazione fosse un'applicazione GUI ma i processi eseguiti dovevano essere eseguiti in background senza alcuna finestra della console interattiva collegata. Penso che questa soluzione dovrebbe funzionare anche quando il processo genitore è un processo console. Potrebbe essere necessario rimuovere il " CREATE_NO_WINDOW " bandiera però.

Sono riuscito a risolverlo usando GenerateConsoleCtrlEvent () con un'app wrapper. La parte difficile è solo che la documentazione non è molto chiara su come può essere utilizzata e sulle insidie ??che ne derivano.

La mia soluzione si basa su quanto descritto qui . Ma questo non ha davvero spiegato tutti i dettagli e con un errore, quindi ecco i dettagli su come farlo funzionare.

Crea una nuova applicazione di supporto " Helper.exe " ;. Questa applicazione si collocherà tra l'applicazione (genitore) e il processo figlio che si desidera poter chiudere. Creerà anche il processo figlio reale. Devi avere questo "uomo medio" process o GenerateConsoleCtrlEvent () falliranno.

Utilizzare un qualche tipo 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 oggetto evento per questo che il genitore completa quando vuole annullare 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 crearlo senza flag (0) che significa che deriverà la console dal suo genitore. In caso contrario, si ignorerà l'evento.

È molto importante che ogni passaggio venga eseguito in questo modo. Ho provato tutti i diversi tipi di combinazioni, ma questa combinazione è l'unica che funziona. Non è possibile inviare un evento CTRL_C. Restituirà il successo ma verrà ignorato dal processo. CTRL_BREAK è l'unico che funziona. Non importa, poiché alla fine entrambi chiameranno ExitProcess ().

Inoltre, non è possibile chiamare GenerateConsoleCtrlEvent () con un ID gruppo di processo dell'ID processo figlio che consenta direttamente al processo di supporto di continuare a vivere. Anche questo fallirà.

Ho trascorso un'intera giornata cercando di farlo funzionare. Questa soluzione funziona per me, ma se qualcuno ha qualcos'altro da aggiungere, per favore, fallo. Sono andato su tutta la rete trovando molte persone con problemi simili ma nessuna soluzione definitiva al problema. Anche il modo in cui GenerateConsoleCtrlEvent () funziona è un po 'strano, quindi se qualcuno ne conosce altri dettagli, per favore, condividi.

Modifica:

Per un'app GUI, la "quot" normale " un modo per 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, è possibile chiamare TerminateProcess .

In qualche modo GenerateConsoleCtrlEvent () restituisce un errore se lo si chiama per un altro processo, ma è possibile collegarsi a un'altra applicazione console e inviare eventi 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
}

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

Punti positivi:

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

Punti negativi:

  • La console principale viene persa e ne viene creata una nuova (consultare FreeConsole )
  • Il cambio della console dà risultati strani ...

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

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

  

(2014-3-7): ho creato entrambe le versioni a 32 e 64 bit con Ctrl-C, si chiama SendSignalCtrlC.exe e puoi scaricarlo da: https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86/SendSignalCtrlC.exe https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrl. exe - Juraj Michalak

Ho anche rispecchiato quei file per ogni evenienza:
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

Dichiarazione di non responsabilità: non ho creato quei file. Nessuna modifica è stata apportata al compilato file originali. 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 compilalo tu stesso.

In Java, usando JNA con la libreria Kernel32.dll, simile a una soluzione C ++. Esegue il metodo principale CtrlCSender come un processo che ottiene appena la console del processo per inviare l'evento Ctrl + C e genera l'evento. Poiché viene eseguito separatamente senza console, non è necessario disabilitare e abilitare nuovamente l'evento Ctrl + C.

CtrlCSender.java - Basato su Nemo1024's e risposte di KindDragon .

Dato un ID processo noto, questa applicazione consolita collegherà la console del processo target e genererà un evento CTRL + C su di essa.

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 consoless 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 mi ha suggerito un modo completamente diverso di risolvere il problema e ha funzionato per me. Usa un vbscript come di seguito. Si avvia e l'applicazione, lascialo funzionare per 7 secondi e chiudilo usando ctrl + c .

'Esempio VBScript

Set WshShell = WScript.CreateObject("WScript.Shell")

WshShell.Run "notepad.exe"

WshShell.AppActivate "notepad"

WScript.Sleep 7000

WshShell.SendKeys "^C"

In base all'ID di processo, possiamo inviare il segnale al processo per terminare con forza o grazia o qualsiasi altro segnale.

Elenca tutto il processo:

C:\>tasklist

Per terminare il processo:

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

dettagli:

http://tweaks.com/windows/39559/ kill-processi-da-prompt dei comandi /

Ho trovato tutto ciò troppo complicato e ho usato SendKeys per inviare una sequenza di tasti CTRL - C nella finestra della riga di comando (ovvero finestra cmd.exe) come soluzione alternativa.

Sì. Il progetto windows-kill fa esattamente quello che vuoi:

windows-kill -SIGINT 1234
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top