Как отправить Ctrl + C процессу в C #? - PullRequest
23 голосов
/ 12 ноября 2008

Я пишу класс-оболочку для исполняемого файла командной строки. Этот exe принимает ввод от stdin до тех пор, пока я не нажму ctrl + c в оболочке командной строки, и в этом случае он печатает выходные данные на основе ввода в stdout. Я хочу смоделировать нажатие ctrl + c в коде c #, посылая команду kill объекту процесса .Net. Я пытался вызвать Process.kill (), но, похоже, это ничего не дает мне в StandardOutput StreamReader процесса. Может ли быть что-нибудь, что я не делаю правильно? Вот код, который я пытаюсь использовать:

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

однако вывод всегда пуст, хотя я получаю данные из stdout, когда запускаю exe вручную. редактировать: это c # 2.0 от 100 до *

Ответы [ 5 ]

27 голосов
/ 12 ноября 2008

Я на самом деле только что понял ответ. Спасибо вам обоим за ваши ответы, но оказывается, что все, что мне нужно было сделать, это:

p.StandardInput.Close()

что приводит к тому, что программа, которую я породил, заканчивает чтение из стандартного ввода и выводит то, что мне нужно.

23 голосов
/ 26 марта 2015

Несмотря на тот факт, что использование GenerateConsoleCtrlEvent для отправки сигнала Ctrl + C является правильным ответом, для его работы в различных типах приложений .NET требуется значительное уточнение.

Если ваше .NET-приложение не использует собственную консоль (WinForms / WPF / Windows Service / ASP.NET), основной поток будет:

  1. Присоедините основной .NET-процесс к консоли процесса, который вы хотите, нажав Ctrl + C
  2. Предотвращение остановки основного процесса .NET из-за события Ctrl + C с SetConsoleCtrlHandler
  3. Создание события консоли для текущей консоли с GenerateConsoleCtrlEvent (processGroupId должен быть равен нулю! Ответ с кодом, который отправляет p.SessionId, не будет работать и будет неправильным)
  4. Отключение от консоли и восстановление обработки Ctrl + C основным процессом

Следующий фрагмент кода иллюстрирует, как это сделать:

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

где SetConsoleCtrlHandler, FreeConsole, AttachConsole и GenerateConsoleCtrlEvent являются собственными методами WinAPI:

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

Все становится сложнее, если вам нужно отправить Ctrl + C из консольного приложения .NET. Подход не будет работать, потому что AttachConsole возвращает false в этом случае (главное приложение консоли уже имеет консоль). Можно вызвать FreeConsole до вызова AttachConsole, но в результате оригинальная консоль приложения .NET будет потеряна, что в большинстве случаев неприемлемо.

Мое решение для этого случая (которое действительно работает и не имеет побочных эффектов для консоли основного процесса .NET):

  1. Создать небольшую вспомогательную консольную программу .NET, которая принимает идентификатор процесса из аргументов командной строки, теряет свою собственную консоль с FreeConsole перед вызовом AttachConsole и отправляет Ctrl + C целевому процессу с кодом, упомянутым выше
  2. Основной консольный процесс .NET просто вызывает эту утилиту в новом процессе, когда ему нужно отправить Ctrl + C другому консольному процессу
22 голосов
/ 12 ноября 2008

@ alonl: пользователь пытается обернуть программу командной строки. Программы командной строки не имеют насосов сообщений, если они специально не созданы, и даже если бы это было так, Ctrl + C не имеет такой же семантики в приложении среды Windows (копия, по умолчанию), как в среда командной строки (Break).

Я бросил это вместе. CtrlCClient.exe просто вызывает Console.ReadLine () и ждет:


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

Кажется, мой вывод делает то, что вы хотите:

4080 is active: True

4080 is active: False

Надеюсь, это поможет!

(Для пояснения: \ x3 - это шестнадцатеричная escape-последовательность для шестнадцатеричного символа 3, которая представляет собой ctrl + c. Это не просто магическое число;

7 голосов
/ 06 сентября 2011

Хорошо, вот решение.

Способ отправки сигнала Ctrl-C - с помощью GenerateConsoleCtrlEvent. ОДНАКО этот вызов принимает параметр processGroupdID и отправляет сигнал Ctrl-C всем процессам в группе. Это было бы хорошо, если бы не тот факт, что нет никакого способа порождать дочерний процесс в .net, который находится в другой группе процессов, чем вы (родитель) в. Так, когда вы отправляете GenerateConsoleCtrlEvent, оба дочерних И ВЫ (РОДИТЕЛЬ) ПОЛУЧАЕТЕ ЭТО. Итак, вам нужно перехватить событие ctrl-c и в родительском объекте, а затем определить, нужно ли его игнорировать.

В моем случае я хочу, чтобы родитель также мог обрабатывать события Ctrl-C, поэтому мне нужно различать события Ctrl-C, отправленные пользователем на консоли, и события, отправленные родительским процессом дочернему элементу. , Я делаю это просто путем хакерской установки / сброса логического флага при отправке ctrl-c дочернему элементу, а затем проверяю этот флаг в обработчике событий ctrl-c родительского элемента (т. Е. Если отправляем ctrl-c дочернему элементу, затем игнорируем. )

Итак, код будет выглядеть примерно так:

//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;
}
0 голосов
/ 12 ноября 2008

Попробуйте на самом деле отправить комбинацию клавиш Ctrl + C вместо непосредственного завершения процесса:

 [DllImport("user32.dll")]
        public static extern int SendMessage(
              int hWnd,      // handle to destination window
              uint Msg,       // message
              long wParam,  // first message parameter
              long lParam   // second message parameter
              );

Посмотрите на MSDN, там вы найдете то, что вам нужно, чтобы отправить комбинацию клавиш Ctrl + Key ... Я знаю, что сообщение, необходимое для отправки Alt + Key, - WM_SYSTEMKEYDOWN и WM_SYSTEMKEYUP, не могу рассказать о Ctrl ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...