Как отправить ctrl + c процессу в С#?

Я пишу класс оболочки для исполняемого файла командной строки. Этот exe принимает ввод от stdin, пока я не нажму ctrl + c в командной строке командной оболочки, и в этом случае он выводит результат на основе ввода в stdout. Я хочу, чтобы имитировать, что ctrl + 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 вручную. edit: это С# 2.0 btw

Ответ 1

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

p.StandardInput.Close()

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

Ответ 2

@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. Это не просто магическое число.;))

Ответ 3

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

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

  • Прикрепите основной процесс .NET к консоли процесса, который вы хотите Ctrl + C
  • Предотвращение остановки основного процесса .NET из-за события Ctrl + C с помощью SetConsoleCtrlHandler
  • Генерировать консольное событие для текущей консоли с помощью GenerateConsoleCtrlEvent (processGroupId должно быть нулевым! Ответ с кодом, который отправляет p.SessionId, не будет работать и некорректно)
  • Отключиться от консоли и восстановить обработку 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):

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

Ответ 4

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

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

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

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

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

Ответ 5

Попробуйте отправить комбинацию клавиш 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...