109 votes

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

J'ai (par le passé) écrit des applications multiplates-formes (Windows / Unix) qui, démarrées à partir de la ligne de commande, traitaient de la même manière une combinaison de touches Ctrl-C (c.-à-d. 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 lui demander de se terminer proprement (ce qui lui donne l’occasion de ranger des ressources, etc.)?

70voto

Nemo1024 Points 144

J'ai fait quelques recherches autour de ce thème, qui s'est avéré être plus large que ce que je pensais. KindDragon la réponse a été l'un des points de pivotement.

J'ai écrit un long billet de blog sur le sujet et créé un programme de démonstration, qui illustre deux méthodes de fermeture d'une application en ligne de commande dans une belle façon. Ce poste également des listes de liens externes que j'ai utilisé dans mes recherches.

En bref, ces méthodes ne sont les suivantes:

  • Lancer un programme avec une fenêtre visible de l'utiliser .Net, de les masquer avec pinvoke, 6 secondes, l'affichage avec pinvoke, d'arrêter .Net.
  • Démarrer un programme sans une fenêtre à l'aide .Net, courir pour 6 secondes, arrêter par la fixation de la console et de la délivrance de ConsoleCtrlEvent

Modifier: Pour modifier la solution de KindDragon pour ceux qui sont intéressés dans le code ici et maintenant. Si vous envisagez de démarrer d'autres programmes après l'arrêt de la première, vous devez le réactiver les touches Ctrl-C de la manipulation, sinon, le processus suivant sera hériter du parent état désactivé et ne permettra pas de répondre à 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); 
  }
}

Aussi, un plan d'urgence de la solution si AttachConsole() ou l'envoyé de signal de panne.

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

32voto

arolson101 Points 580

Le plus proche que je suis arrivé à une solution est SendSignal. L'auteur a le code source et un fichier exécutable. J'ai vérifié qu'il fonctionne sous windows 64 bits (comme un programme 32 bits, tuant un autre programme 32 bits), mais j'ai pas trouvé comment intégrer le code dans un programme windows (32 bits ou 64 bits).

ALE:

Après beaucoup de creuser autour 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 a le même prototype que ThreadProc, de sorte qu'il peut être utilisé avec CreateRemoteThread directement, sans avoir à injecter du code. Cependant, ce n'est pas un symbole exporté! C'est à des adresses différentes (et même a des noms différents) sur les différentes versions de Windows. Que faire?

Voici la solution que j'ai enfin trouvé. - Je installer une console ctrl gestionnaire pour mon application, puis générer un ctrl-pause signal pour mon application. Quand mon gestionnaire est appelé à la, je reviens sur le haut de la pile pour trouver les paramètres passés à kernel32!BaseThreadStart. Je prends le premier paramètre, qui est le début souhaitée à l'adresse du fil, qui est l'adresse de kernel32!CtrlRoutine. Alors je reviens de mon gestionnaire, indiquant que j'ai manipulé le signal et mon application ne devrait pas être terminé. De retour dans le thread principal, j'attends jusqu'à ce que l'adresse de kernel32!CtrlRoutine a été récupéré. Une fois que j'ai eu, j'ai créer une distance thread dans le processus cible avec la découverte adresse de départ. Cela provoque la touche ctrl gestionnaires dans le processus cible à être évaluées comme si ctrl-break a été pressé!

La bonne chose est que seul le processus cible est touchée, et tout (même fenêtré processus) peuvent être ciblés. Un inconvénient est que ma petite app ne peut pas être utilisé dans un fichier de commandes, car il va le tuer lorsqu'il envoie la combinaison de touches ctrl-pause événement afin de découvrir l'adresse de kernel32!CtrlRoutine.

20voto

Shakta Points 196

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

Mon problème est que j'aimerais que ma demande soit d'une application graphique, mais le processus exécuté doit être exécuté en arrière-plan sans aucune console interactive de la fenêtre ci-joint. Je pense que cette solution devrait également fonctionner lorsque le processus parent est un processus de console. Vous pouvez avoir à supprimer le "CREATE_NO_WINDOW" drapeau.

J'ai réussi à résoudre ce à l'aide de GenerateConsoleCtrlEvent(). La partie la plus délicate est juste que la documentation n'est pas vraiment clair sur exactement comment il peut être utilisé et les pièges.

Ma solution est basée sur ce qui est décrit ici. Mais cela n'a pas vraiment l'expliquer tous les détails et avec une erreur, voici donc les détails sur la façon d'obtenir ce travail.

Créer une nouvelle application d'aide "Helper.exe". Cette application va s'asseoir entre votre application (parent) et le processus de l'enfant que vous voulez être en mesure de fermer. Il permettra aussi de créer de réels processus enfant. Vous devez avoir cet "homme du milieu" ou GenerateConsoleCtrlEvent() échouera.

Utiliser une sorte de mécanisme IPC de communiquer de la part du parent à l'aide de processus que l'aide doit fermer le processus de l'enfant. Lorsque l'assistant d'obtenir cet événement qu'il appelle "GenerateConsoleCtrlEvent(CTRL_BREAK, 0)", qui se ferme de lui-même et le processus de l'enfant. J'ai utilisé un objet d'événement de ce moi-même qui le parent se termine quand il veut annuler le processus de l'enfant.

Pour créer votre Helper.exe créer avec CREATE_NO_WINDOW et CREATE_NEW_PROCESS_GROUP. Et lors de la création de l'enfant à créer avec pas de drapeaux (0) signifie qu'elle va tirer la console de son parent. Sinon, cela va l'amener à ignorer l'événement.

Il est très important que chaque étape se fait de cette manière. J'ai essayé toutes sortes de combinaisons, mais cette combinaison est le seul qui fonctionne. Vous ne pouvez pas envoyer un CTRL_C événement. Il sera de retour le succès, mais sera ignoré par le processus. CTRL_BREAK est le seul qui fonctionne. N'a pas vraiment d'importance, parce qu'ils seront tous deux appel ExitProcess() à la fin.

Vous aussi vous ne pouvez pas appeler GenerateConsoleCtrlEvent() avec un processus de groupd id de l'enfant id de processus directement permettant l'aide du processus de continuer à vivre. Cela ne fonctionne pas ainsi.

J'ai passé une journée entière à essayer d'obtenir ce travail. Cette solution fonctionne pour moi, mais si quelqu'un a quelque chose à ajouter s'il vous plaît. Je suis allé sur le net de trouver beaucoup de gens avec des problèmes similaires, mais pas de solution définitive au problème. Comment GenerateConsoleCtrlEvent() fonctionne est un peu bizarre donc si quelqu'un en sait plus de détails sur le s'il vous plaît partager.

9voto

KindDragon Points 1656

D'une manière ou d'une autre, GenerateConsoleCtrlEvent() renverra une erreur si vous l'appelez pour un autre processus, mais vous pouvez vous connecter à une autre application de la 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
}
 

8voto

Reed Copsey Points 315315

Edit:

Pour une GUI Application, la façon "normale" pour répondre à ces questions de développement Windows serait d'envoyer un message WM_CLOSE pour le processus de la fenêtre principale.

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

Si la demande ne respecte pas, que, vous pouvez l'appeler TerminateProcess.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X