web-dev-qa-db-fra.com

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

J'ai (dans le passé) écrit des applications multiplateformes (Windows/Unix) qui, lorsqu'elles étaient démarrées à partir de la ligne de commande, géraient une saisie utilisateur Ctrl-C combinaison de la même manière (c'est-à-dire pour terminer l'application proprement).

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

81
Matthew Murdoch

La solution la plus proche que j'ai trouvée est l'application SendSignal 3rd party. L'auteur répertorie le code source et un exécutable. J'ai vérifié que cela 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 marche:

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 directement avec CreateRemoteThread, sans avoir à injecter de code. Cependant, ce n'est pas un symbole exporté! Il se trouve à différentes adresses (et porte 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 ctrl de console pour mon application, puis génère un signal de coupure ctrl pour mon application. Lorsque mon gestionnaire est appelé, je regarde en haut de la pile pour découvrir les paramètres passés à kernel32! BaseThreadStart. J'attrape le premier paramètre, qui est l'adresse de début souhaitée du thread, qui est l'adresse de kernel32! CtrlRoutine. Ensuite, je reviens de mon gestionnaire, indiquant que j'ai traité le signal et que mon application ne doit pas être fermée. De retour dans le fil principal, j'attends que l'adresse de kernel32! CtrlRoutine soit 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ébut découverte. Cela entraîne l'évaluation des gestionnaires ctrl dans le processus cible comme si ctrl-break avait été enfoncé!

Ce qui est bien, c'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 le tuera lors de l'envoi de l'événement ctrl-break afin de découvrir l'adresse de kernel32! CtrlRoutine.

(Faites-le précéder de start si vous l'exécutez dans un fichier de commandes.)

27
arolson101

J'ai fait des recherches sur ce sujet, qui se sont avérées plus populaires que je ne l'avais prévu. La réponse de KindDragon a été l'un des points pivots.

J'ai écrit un article de blog plus long sur le sujet et créé un programme de démonstration fonctionnel, qui montre comment utiliser ce type de système pour fermer une application en ligne de commande dans quelques modes Nice. Ce message 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, affichez 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 émettant 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); 
  }
}

Prévoyez également une solution de contingence si AttachConsole() ou le signal envoyé échoue, par exemple en dormant alors ceci:

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

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

Mon problème était que j'aimerais que mon application soit une application GUI mais les processus exécutés devraient ê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 de console. Vous devrez peut-être supprimer le drapeau "CREATE_NO_WINDOW".

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

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, alors voici les détails sur la façon de le faire fonctionner.

Créez une nouvelle application d'assistance "Helper.exe". Cette application sera placée entre votre application (parent) et le processus enfant que vous souhaitez pouvoir fermer. Il 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'aide 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 veut 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 comme ceci. 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 car ils appellent tous les deux ExitProcess () à la fin.

Vous ne pouvez pas non plus appeler GenerateConsoleCtrlEvent () avec un identifiant groupd de processus de l'identifiant 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 quelque chose à ajouter, faites-le. Je suis allé partout sur le net pour trouver beaucoup de gens avec des problèmes similaires, mais aucune solution définitive au problème. Le fonctionnement de GenerateConsoleCtrlEvent () est également un peu bizarre, donc si quelqu'un connaît plus de détails, veuillez partager.

17
Shakta

Modifier:

Pour une application GUI, la façon "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 CTRL_C_EVENT.

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

7
Reed Copsey

D'une manière ou d'une autre 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
}
6
KindDragon

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

Points positifs:

  • Fonctionne depuis 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 )
  • Le changement 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;
}
5
56ka
        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);
            }
        }
4

Il devrait être clair comme du cristal 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, elle n'envoie que Ctrl + Break). Voici quelques binaires:

(2014-3-7): J'ai construit les versions 32 bits et 64 bits avec Ctrl-C, il s'appelle SendSignalCtrlC.exe et vous pouvez le télécharger sur: https://dl.dropboxusercontent.com/ u/49065779/sendignalctrlc/x86/SendSignalCtrlC.exehttps://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=
Version 64 bits: https://www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=

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 Windows 7 64 bits. Il est recommandé d'adapter la source disponible sur http://www.latenighthacking.com/projects/2003/sendSignal/ et de la compiler vous-même.

3
ioikka

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 pour 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 Nemo1024 et KindDragon .

Étant donné un ID de processus connu, cette application sans 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 consoless 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();
3
Patimo

Oui. Le windows-kill le projet fait exactement ce que vous voulez:

windows-kill -SIGINT 1234
2
BullyWiiPlaza

Un de mes amis a suggéré une façon 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"
1
Frederic Simard

J'ai trouvé tout cela trop compliqué et utilisé SendKeys pour envoyer un CTRL-C appuyez sur la fenêtre de ligne de commande (c'est-à-dire la fenêtre cmd.exe) comme solution de contournement.

1
thomiel

Sur la base de l'ID de processus, nous pouvons envoyer le signal au processus pour terminer avec force ou grâce ou tout autre signal.

Liste de 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/

1
Awanish Kumar