Puis-je envoyer un ctrl-C (SIGINT) à une application sous Windows?


91

J'ai (dans le passé) les applications multi-plateformes (Windows / Unix) écrit qui, lorsqu'il est lancé depuis la ligne de commande, un utilisateur-traitées dactylographié Ctrl- Ccombinaison de la même manière ( par exemple pour arrêter l'application proprement).

Est-il possible sous Windows d'envoyer un Ctrl- C/ SIGINT / équivalent à un processus à partir d'un autre processus (non lié) pour demander qu'il se termine proprement (lui donnant la possibilité de ranger les ressources, etc.)?


1
J'étais intéressé par l'envoi de Ctrl-C aux processus de service java juste pour obtenir des threaddumps. Il semble que cela jstackpuisse être utilisé de manière fiable à la place pour cette question spécifique: stackoverflow.com/a/47723393/603516
Vadzim

Réponses:


31

L' application tierce SendSignal est la solution la plus proche que je connaisse . L'auteur répertorie le code source et un exécutable. J'ai vérifié qu'il fonctionne sous Windows 64 bits (fonctionnant comme un programme 32 bits, tuant un autre programme 32 bits), mais je n'ai pas compris comment incorporer le code dans un programme Windows (soit 32 bits ou 64 bits).

Comment ça fonctionne:

Après avoir beaucoup fouillé dans le débogueur, j'ai découvert que le point d'entrée qui fait réellement le comportement associé à un signal comme ctrl-break est kernel32! CtrlRoutine. La fonction avait le même prototype que ThreadProc, elle peut donc être utilisée avec CreateRemoteThread directement, sans avoir à injecter de code. Cependant, ce n'est pas un symbole exporté! C'est à différentes adresses (et a même des noms différents) sur différentes versions de Windows. Que faire?

Voici la solution que j'ai finalement trouvée. J'installe un gestionnaire de contrôle de la console pour mon application, puis je génère un signal de pause de contrôle pour mon application. Lorsque mon gestionnaire est appelé, je regarde en arrière en haut de la pile pour découvrir les paramètres passés à kernel32! BaseThreadStart. Je saisis le premier paramètre, qui est l'adresse de démarrage souhaitée du thread, qui est l'adresse de kernel32! CtrlRoutine. Ensuite, je reviens de mon gestionnaire, indiquant que j'ai géré le signal et que mon application ne doit pas être interrompue. De retour dans le fil principal, j'attends que l'adresse de kernel32! CtrlRoutine ait été récupérée. Une fois que je l'ai, je crée un thread distant dans le processus cible avec l'adresse de démarrage découverte. Cela provoque l'évaluation des gestionnaires ctrl dans le processus cible comme si ctrl-break avait été pressé!

La bonne chose est que seul le processus cible est affecté, et tout processus (même un processus fenêtré) peut être ciblé. Un inconvénient est que ma petite application ne peut pas être utilisée dans un fichier batch, car elle la tuera lorsqu'elle enverra l'événement ctrl-break afin de découvrir l'adresse de kernel32! CtrlRoutine.

(Faites-le précéder de startsi vous l'exécutez dans un fichier batch.)


2
+1 - Le code lié utilise Ctrl-Break plutôt que Ctrl-C mais à première vue (en regardant la documentation de GenerateConsoleCtrlEvent) pourrait être adapté pour Ctrl-C.
Matthew Murdoch

8
Il existe en fait une fonction pour faire cela pour vous, lol, "GenerateConsoleCtrlEvent". Voir: msdn.microsoft.com/en-us/library/ms683155(v=vs.85).aspx Voir aussi ma réponse ici: serverfault.com/a/501045/10023
Triynko

1
Lien Github lié à la réponse ci-dessus: github.com/walware/statet/tree/master
...

3
Il est stupéfiant que Windows soit aussi vieux qu'il est et pourtant Microsoft n'a pas fourni de mécanisme pour que l'application console A écoute un signal de l'application console B afin que l'application console B puisse dire à l'application console A de s'arrêter proprement. Dans tous les cas, MS-bashing mis à part, j'ai essayé SendSignal et obtenir "CreateRemoteThread a échoué avec 0x00000005". D'autres idées sur la façon de coder l'application A pour écouter un signal (tout signal qui convient), et comment alors le signaler?
David I. McIntosh

3
@ DavidI.McIntosh il semble que vous vouliez créer un événement nommé dans les deux processus; vous seriez alors en mesure de signaler l'un de l'autre. Consultez la documentation pour CreateEvent
arolson101

58

J'ai fait des recherches sur ce sujet, qui s'est avéré être plus populaire que prévu. La réponse de KindDragon a été l'un des points cruciaux.

J'ai écrit un article de blog plus long sur le sujet et créé un programme de démonstration fonctionnel, qui démontre l'utilisation de ce type de système pour fermer une application de ligne de commande dans quelques modes. Cet article répertorie également les liens externes que j'ai utilisés dans mes recherches.

En bref, ces programmes de démonstration font ce qui suit:

  • Démarrez un programme avec une fenêtre visible en utilisant .Net, cachez avec pinvoke, exécutez pendant 6 secondes, montrez avec pinvoke, arrêtez avec .Net.
  • Démarrez un programme sans fenêtre en utilisant .Net, exécutez pendant 6 secondes, arrêtez en attachant la console et en exécutant ConsoleCtrlEvent

Edit: La solution modifiée de KindDragon pour ceux qui sont intéressés par le code ici et maintenant. Si vous prévoyez de démarrer d'autres programmes après avoir arrêté le premier, vous devez réactiver la gestion Ctrl-C, sinon le processus suivant héritera de l'état désactivé du parent et ne répondra pas à 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); 
  }
}

En outre, prévoyez une solution d'urgence si AttachConsole()ou le signal envoyé doit échouer, par exemple dormir alors ceci:

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

4
Inspiré par cela, j'ai créé une application cpp "autonome" qui le fait: gist.github.com/rdp/f51fb274d69c5c31b6be au cas où cela serait utile. Et oui, cette méthode peut envoyer ctrl + c ou ctrl + break à "n'importe quel" pid, apparemment.
rogerdpack

Manque une des importations DLL "SetConsoleCtrlHandler", mais cela fonctionne.
Derf Skren

excellent poste. excellent blog. Je suggérerais de terminer la fonction StopProgramWithInvisibleWindowUsingPinvoke dans votre programme d'expérience. J'ai failli abandonner l'idée en pensant que vous n'aviez pas trouvé de solution
Wilmer Saint

Pour éviter le, wait()j'ai supprimé le Re-enable Ctrl-C ( SetConsoleCtrlHandler(null, false);). J'ai fait quelques tests (appels multiples, également sur des processus déjà terminés) et je n'ai trouvé aucun effet secondaire (encore?).
56ka

FreeConsole doit être appelé immédiatement après l'envoi de GenerateConsoleCtrlEvent. Jusqu'à ce qu'elle soit appelée, votre application sera attachée à l'autre console. Si l'autre console est tuée avant la fin de la fermeture, votre application sera également arrêtée!
Timothy Jannace

17

Je suppose que je suis un peu en retard sur cette question, mais j'écrirai quand même quelque chose pour quiconque a le même problème. C'est la même réponse que j'ai donnée à cette question.

Mon problème était que j'aimerais que mon application soit une application graphique, mais les processus exécutés doivent être exécutés en arrière-plan sans aucune fenêtre de console interactive attachée. Je pense que cette solution devrait également fonctionner lorsque le processus parent est un processus console. Vous devrez peut-être supprimer l'indicateur "CREATE_NO_WINDOW".

J'ai réussi à résoudre ce problème en utilisant GenerateConsoleCtrlEvent () avec une application wrapper. La partie la plus délicate est simplement que la documentation n'est pas vraiment claire sur la manière dont elle peut être utilisée et les pièges qui l'accompagnent.

Ma solution est basée sur ce qui est décrit ici . Mais cela n'a pas vraiment expliqué tous les détails non plus et avec une erreur, voici donc les détails sur la façon de le faire fonctionner.

Créez une nouvelle application d'assistance "Helper.exe". Cette application se situera entre votre application (parent) et le processus enfant que vous souhaitez pouvoir fermer. Cela créera également le processus enfant réel. Vous devez avoir ce processus "intermédiaire" ou GenerateConsoleCtrlEvent () échouera.

Utilisez une sorte de mécanisme IPC pour communiquer du parent au processus d'assistance que l'aide doit fermer le processus enfant. Lorsque l'assistant obtient cet événement, il appelle "GenerateConsoleCtrlEvent (CTRL_BREAK, 0)" qui se ferme lui-même et le processus enfant. J'ai utilisé un objet événement pour cela moi-même que le parent complète lorsqu'il souhaite annuler le processus enfant.

Pour créer votre Helper.exe, créez-le avec CREATE_NO_WINDOW et CREATE_NEW_PROCESS_GROUP. Et lors de la création du processus enfant, créez-le sans indicateur (0), ce qui signifie qu'il dérivera la console de son parent. Si vous ne le faites pas, il ignorera l'événement.

Il est très important que chaque étape se fasse ainsi. J'ai essayé toutes sortes de combinaisons, mais cette combinaison est la seule qui fonctionne. Vous ne pouvez pas envoyer d'événement CTRL_C. Il renverra le succès mais sera ignoré par le processus. CTRL_BREAK est le seul qui fonctionne. Cela n'a pas vraiment d'importance puisqu'ils appelleront tous les deux ExitProcess () à la fin.

Vous ne pouvez pas non plus appeler GenerateConsoleCtrlEvent () avec un ID groupd de processus de l'ID de processus enfant permettant directement au processus d'assistance de continuer à vivre. Cela échouera également.

J'ai passé une journée entière à essayer de faire fonctionner cela. Cette solution fonctionne pour moi, mais si quelqu'un a autre chose à ajouter, veuillez le faire. Je suis allé partout sur le net pour trouver beaucoup de gens avec des problèmes similaires, mais sans solution définitive au problème. Le fonctionnement de GenerateConsoleCtrlEvent () est également un peu étrange, donc si quelqu'un connaît plus de détails, veuillez le partager.


6
Merci pour la solution! C'est encore un autre exemple de l'API Windows étant un si terrible désordre.
vitaut

8

Éditer:

Pour une application GUI, la manière «normale» de gérer cela dans le développement Windows serait d'envoyer un message WM_CLOSE à la fenêtre principale du processus.

Pour une application console, vous devez utiliser SetConsoleCtrlHandler pour ajouter un fichierCTRL_C_EVENT .

Si l'application ne respecte pas cela, vous pouvez appeler TerminateProcess .


Je souhaite faire cela dans une application de ligne de commande (pas de fenêtres). TerminateProcess est un mécanisme de suppression forcée (similaire à SIGKILL) donc ne donne à l'application qui se termine aucune opportunité de nettoyer.
Matthew Murdoch

@Matthew Murdoch: mise à jour pour inclure des informations sur la façon de gérer cela dans une application console. Vous pouvez également attraper d'autres événements, y compris l'arrêt de Windows, ou la fermeture de la console, etc., via le même mécanisme. Consultez MSDN pour plus de détails.
Reed Copsey

@Reed Copsey: Mais j'aimerais lancer cela à partir d'un processus séparé. J'ai jeté un coup d'œil à GenerateConsoleCtrlEvent (référencé à partir de SetConsoleCtrlHandler) mais cela semble ne fonctionner que si le processus en cours de terminaison est dans le même `` groupe de processus '' que le processus effectuant la fin ... Des idées?
Matthew Murdoch

1
Matthew: La seule idée que j'aurais serait de trouver le processus, de trouver son parent (la console), d'obtenir le handle de la fenêtre principale du parent (la console) et d'utiliser SendKeys pour lui envoyer un Ctrl + C directement. Cela devrait amener votre processus de console à recevoir un CTRL_C_EVENT. Je n'ai pas essayé, cependant - je ne sais pas si cela fonctionnerait bien.
Reed Copsey

Voici un équivalent c ++ à SendKeys stackoverflow.com/questions/6838363/... voir aussi stackoverflow.com/questions/10407769/... bien qu'apparemment, il n'est pas garanti de fonctionner même dans ce cas ...
rogerdpack

7

En quelque sorte, GenerateConsoleCtrlEvent()renvoie une erreur si vous l'appelez pour un autre processus, mais vous pouvez vous attacher à une autre application de console et envoyer un événement à tous les processus enfants.

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
}

et comment reprendre le contrôle? et puis j'envoie control-c, programme stopar, mais je ne peux rien faire avec ses mains.
Yaroslav L.

Où reprendre le contrôle?
KindDragon

Je pense que vous appelez SetConsoleCtrlHandler (NULL, FALSE) pour supprimer votre gestionnaire, voir les diverses autres réponses.
rogerdpack

6

Voici le code que j'utilise dans mon application C ++.

Points positifs:

  • Fonctionne à partir de l'application console
  • Fonctionne à partir du service Windows
  • Aucun délai requis
  • Ne ferme pas l'application actuelle

Points négatifs:

  • La console principale est perdue et une nouvelle est créée (voir FreeConsole )
  • La commutation de console donne des résultats étranges ...

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

Exemple d'utilisation:

DWORD dwProcessId = ...;
if (signalCtrl(dwProcessId, CTRL_C_EVENT))
{
    cout << "Signal sent" << endl;
}

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

2

Cela devrait être clair car pour le moment, ce n'est pas le cas. Il existe une version modifiée et compilée de SendSignal pour envoyer Ctrl-C (par défaut, il n'envoie que Ctrl + Break). Voici quelques binaires:

(2014-3-7): J'ai construit une version 32 bits et 64 bits avec Ctrl-C, elle s'appelle SendSignalCtrlC.exe et vous pouvez la télécharger sur: https://dl.dropboxusercontent.com/u/49065779/ sendignalctrlc / x86 / SendSignalCtrlC.exe https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe - Juraj Michalak

J'ai également mis en miroir ces fichiers au cas où:
version 32 bits: https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0 version
64 bits: https://www.dropbox.com /s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0

Avertissement: je n'ai pas construit ces fichiers. Aucune modification n'a été apportée aux fichiers originaux compilés. La seule plate-forme testée est la version 64 bits de Windows 7. Il est recommandé d'adapter la source disponible sur http://www.latenighthacking.com/projects/2003/sendSignal/ et de la compiler vous-même.


2

En Java, en utilisant JNA avec la bibliothèque Kernel32.dll, similaire à une solution C ++. Exécute la méthode principale CtrlCSender en tant que processus qui obtient simplement la console du processus à laquelle envoyer l'événement Ctrl + C et génère l'événement. Comme il s'exécute séparément sans console, l'événement Ctrl + C n'a pas besoin d'être désactivé et réactivé.

CtrlCSender.java - Basé sur les réponses de Nemo1024 et KindDragon .

Étant donné un ID de processus connu, cette application console attachera la console du processus ciblé et générera un événement CTRL + C dessus.

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

Application principale - Exécute CtrlCSender en tant que processus de console séparé

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

hmm, si je l'exécute contre un processus PowerShell, le processus reste actif. D'un autre côté, il est intéressant de noter que l'exécution en cours sur la JVM fait effectivement tomber la VM! Savez-vous pourquoi un processus PowerShell ne répondrait pas à cette technique?
Groostav


1

Une solution que j'ai trouvée à partir d'ici est assez simple si vous avez python 3.x disponible dans votre ligne de commande. Tout d'abord, enregistrez un fichier (ctrl_c.py) avec le contenu:

import ctypes
import sys

kernel = ctypes.windll.kernel32

pid = int(sys.argv[1])
kernel.FreeConsole()
kernel.AttachConsole(pid)
kernel.SetConsoleCtrlHandler(None, 1)
kernel.GenerateConsoleCtrlEvent(0, 0)
sys.exit(0)

Puis appelez:

python ctrl_c.py 12345

Si cela ne fonctionne pas, je vous recommande d'essayer le projet windows-kill: https://github.com/alirdn/windows-kill


0

Un de mes amis a suggéré une manière complètement différente de résoudre le problème et cela a fonctionné pour moi. Utilisez un vbscript comme ci-dessous. Il démarre et l'application, laissez-le fonctionner pendant 7 secondes et fermez-le en utilisant ctrl + c .

'Exemple VBScript

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

WshShell.Run "notepad.exe"

WshShell.AppActivate "notepad"

WScript.Sleep 7000

WshShell.SendKeys "^C"

Cela fonctionne si l'application a une fenêtre que vous pouvez AppActive (mettre au premier plan).
rogerdpack

0

Je trouve tout cela trop compliqué et utilisé SendKeys pour envoyer une CTRL- Cséquence de touches à la fenêtre de ligne de commande (fenêtre c. -à- cmd.exe) comme solution de contournement.


0
// Send [CTRL-C] to interrupt a batch file running in a Command Prompt window, even if the Command Prompt window is not visible,
// without bringing the Command Prompt window into focus.
// [CTRL-C] will have an effect on the batch file, but not on the Command Prompt  window itself -- in other words,
// [CTRL-C] will not have the same visible effect on a Command Prompt window that isn't running a batch file at the moment
// as bringing a Command Prompt window that isn't running a batch file into focus and pressing [CTRL-C] on the keyboard.
ulong ulProcessId = 0UL;
// hwC = Find Command Prompt window HWND
GetWindowThreadProcessId (hwC, (LPDWORD) &ulProcessId);
AttachConsole ((DWORD) ulProcessId);
SetConsoleCtrlHandler (NULL, TRUE);
GenerateConsoleCtrlEvent (CTRL_C_EVENT, 0UL);
SetConsoleCtrlHandler (NULL, FALSE);
FreeConsole ();

0

SIGINT peut être envoyé au programme en utilisant windows-kill , par syntaxe windows-kill -SIGINT PID, où PIDpeut être obtenu par la pslist de Microsoft .

En ce qui concerne la capture des SIGINT, si votre programme est en Python, vous pouvez implémenter le traitement / capture SIGINT comme dans cette solution .


-1

Sur la base de l'identifiant du processus, nous pouvons envoyer le signal au processus pour qu'il se termine de manière forcée ou gracieuse ou tout autre signal.

Lister tous les processus:

C:\>tasklist

Pour tuer le processus:

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

Détails:

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


Vraisemblablement cela envoie sigterm?
teknopaul

Taskkill n'envoie pas CTRL-C. Vérifiez les signaux Windows.
Josi le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.