web-dev-qa-db-fra.com

Comment envoyer ctrl + c à un processus en c #?

J'écris une classe wrapper pour un exécutable en ligne de commande. Cet exe accepte l'entrée de stdin jusqu'à ce que j'appuie sur ctrl + c dans la commande Prompt Shell, auquel cas il imprime la sortie en fonction de l'entrée vers stdout. Je veux simuler cette pression ctrl + c dans le code c #, en envoyant la commande kill à un objet de processus .Net. J'ai essayé d'appeler Process.kill (), mais cela ne semble rien me donner dans le StandardOutput StreamReader du processus. Pourrait-il y avoir quelque chose que je ne fais pas bien? Voici le code que j'essaie d'utiliser:

ProcessStartInfo info = new ProcessStartInfo(exe, args);
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
Process p = Process.Start(info);

p.StandardInput.AutoFlush = true;
p.StandardInput.WriteLine(scriptcode);

p.Kill(); 

string error = p.StandardError.ReadToEnd();
if (!String.IsNullOrEmpty(error)) 
{
     throw new Exception(error);
}
string output = p.StandardOutput.ReadToEnd();

cependant la sortie est toujours vide même si je récupère des données de stdout lorsque j'exécute l'exe manuellement. edit: c'est c # 2.0 btw

26
Kevlar

Je viens juste de trouver la réponse. Merci à vous deux pour vos réponses, mais il s'avère que tout ce que j'avais à faire était la suivante:

p.StandardInput.Close()

ce qui fait que le programme que j'ai créé termine la lecture depuis stdin et génère ce dont j'ai besoin.

28
Kevlar

Malgré le fait que l'utilisation de GenerateConsoleCtrlEvent pour envoyer un signal Ctrl + C est une bonne réponse, il a besoin de précisions importantes pour le faire fonctionner dans différents types d'applications .NET.

Si votre application .NET n'utilise pas sa propre console (WinForms/WPF/Windows Service/ASP.NET), le flux de base est:

  1. Attachez le processus .NET principal à la console du processus que vous souhaitez Ctrl + C
  2. Empêcher l'arrêt du processus .NET principal en raison d'un événement Ctrl + C avec SetConsoleCtrlHandler
  3. Générer un événement de console pour la console actuelle avec GenerateConsoleCtrlEvent (processGroupId doit être nul! Répondre avec un code qui envoie p.SessionId ne fonctionnera pas et est incorrect)
  4. Se déconnecter de la console et restaurer la gestion Ctrl + C par le processus principal

L'extrait de code suivant illustre comment procéder:

Process p;
if (AttachConsole((uint)p.Id)) {
    SetConsoleCtrlHandler(null, true);
    try { 
        if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT,0))
            return false;
        p.WaitForExit();
    } finally {
        FreeConsole();
        SetConsoleCtrlHandler(null, false);
    }
    return true;
}

où SetConsoleCtrlHandler, FreeConsole, AttachConsole et GenerateConsoleCtrlEvent sont des méthodes WinAPI natives:

internal const int CTRL_C_EVENT = 0;
[DllImport("kernel32.dll")]
internal static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, uint dwProcessGroupId);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
internal static extern bool FreeConsole();
[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);
// Delegate type to be used as the Handler Routine for SCCH
delegate Boolean ConsoleCtrlDelegate(uint CtrlType);

Les choses deviennent plus complexes si vous devez envoyer Ctrl + C à partir de l'application console .NET. L'approche ne fonctionnera pas car AttachConsole retourne false dans ce cas (l'application console principale a déjà une console). Il est possible d'appeler FreeConsole avant l'appel AttachConsole, mais la console de l'application .NET d'origine sera perdue, ce qui n'est pas acceptable dans la plupart des cas.

Ma solution pour ce cas (qui fonctionne vraiment et n'a aucun effet secondaire pour la console de processus principal .NET):

  1. Créez un petit programme de console .NET de prise en charge qui accepte l'ID de processus à partir des arguments de ligne de commande, perd sa propre console avec FreeConsole avant l'appel AttachConsole et envoie Ctrl + C au processus cible avec le code mentionné ci-dessus
  2. Le processus principal de la console .NET appelle simplement cet utilitaire dans un nouveau processus lorsqu'il doit envoyer Ctrl + C à un autre processus de console
26

@alonl: L'utilisateur tente d'envelopper un programme en ligne de commande. Les programmes de ligne de commande n'ont pas de pompes de messages sauf s'ils sont spécifiquement créés, et même si c'était le cas, Ctrl + C n'a pas la même sémantique dans une application d'environnement Windows (copie, par défaut) que dans un environnement de ligne de commande (Break).

J'ai jeté ça ensemble. CtrlCClient.exe appelle simplement Console.ReadLine () et attend:


        static void Main(string[] args)
        {
            ProcessStartInfo psi = new ProcessStartInfo("CtrlCClient.exe");
            psi.RedirectStandardInput = true;
            psi.RedirectStandardOutput = true;
            psi.RedirectStandardError = true;
            psi.UseShellExecute = false;
            Process proc = Process.Start(psi);
            Console.WriteLine("{0} is active: {1}", proc.Id, !proc.HasExited);
            proc.StandardInput.WriteLine("\x3");
            Console.WriteLine(proc.StandardOutput.ReadToEnd());
            Console.WriteLine("{0} is active: {1}", proc.Id, !proc.HasExited);
            Console.ReadLine();
        }

Ma sortie semble faire ce que vous voulez:

 4080 est actif: Vrai 
 
 4080 est actif: Faux 

J'espère que cela pourra aider!

(Pour clarifier:\x3 est la séquence d'échappement hexadécimale pour le caractère hexadécimal 3, qui est ctrl + c. Ce n'est pas seulement un nombre magique.;))

21
Rob

Ok, voici une solution.

La façon d'envoyer le signal Ctrl-C est avec GenerateConsoleCtrlEvent. CEPENDANT, cet appel prend un paramètre processGroupdID et envoie le signal Ctrl-C à tous les processus du groupe. Ce serait bien s'il n'y avait aucun moyen de générer un processus enfant dans .net qui se trouve dans un groupe de processus différent de celui que vous (le parent) êtes. Donc, lorsque vous envoyez le GenerateConsoleCtrlEvent, à la fois l'enfant ET VOUS (LE PARENT) L'OBTENEZ. Donc, vous devez également capturer l'événement ctrl-c dans le parent, puis déterminer si vous devez l'ignorer.

Dans mon cas, je veux que le parent puisse également gérer les événements Ctrl-C, donc je dois faire la distinction entre les événements Ctrl-C envoyés par l'utilisateur sur la console et ceux envoyés par le processus parent à l'enfant. Je le fais en définissant/désinstallant simplement un drapeau booléen tout en envoyant le ctrl-c à l'enfant, puis en vérifiant ce drapeau dans le gestionnaire d'événements ctrl-c du parent (c'est-à-dire si vous envoyez ctrl-c à l'enfant, puis ignorez. )

Ainsi, le code ressemblerait à ceci:

//import in the declaration for GenerateConsoleCtrlEvent
[DllImport("kernel32.dll", SetLastError=true)]  
static extern bool GenerateConsoleCtrlEvent(ConsoleCtrlEvent sigevent, int dwProcessGroupId);
public enum ConsoleCtrlEvent  
{  
    CTRL_C = 0,  
    CTRL_BREAK = 1,  
    CTRL_CLOSE = 2,  
    CTRL_LOGOFF = 5,  
    CTRL_SHUTDOWN = 6  
}

//set up the parents CtrlC event handler, so we can ignore the event while sending to the child
public static volatile bool SENDING_CTRL_C_TO_CHILD = false;
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
    e.Cancel = SENDING_CTRL_C_TO_CHILD;
}

//the main method..
static int Main(string[] args)
{
    //hook up the event handler in the parent
    Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);

    //spawn some child process
    System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
    psi.Arguments = "childProcess.exe";
    Process p = new Process();
    p.StartInfo = psi;
    p.Start();

    //sned the ctrl-c to the process group (the parent will get it too!)
    SENDING_CTRL_C_TO_CHILD = true;
    GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, p.SessionId);        
    p.WaitForExit();
    SENDING_CTRL_C_TO_CHILD = false;

    //note that the ctrl-c event will get called on the parent on background thread
    //so you need to be sure the parent has handled and checked SENDING_CTRL_C_TO_CHILD
    already before setting it to false. 1000 ways to do this, obviously.



    //get out....
    return 0;
}
8
James Schopp