2008-11-12 3 views
21

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

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

Однако выход всегда пуст, даже если я получаю данные от стандартного вывода при запуске еха вручную. редактирования: это C# 2,0 BTW

ответ

26

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

p.StandardInput.Close() 

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

+9

Обратите внимание, что он работает только в том случае, если процесс пытается прочитать со стандартного ввода. Закрытие stdin ничего не делает, пока программа не попытается что-то прочитать. – Doug 2013-03-12 23:00:50

-6

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

+0

Что делать, если процесс запускается без окна? – FindOutIslamNow 2017-11-28 12:21:20

19

@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 

Надежда, что помогает!

(Для уточнения:.. \ Х3 последовательность шестигранной побег для шестигранного символа 3, что CTRL + C Это не только магическое число;))

+4

Использование тестовой программы, которая назначает делегат Console.CancelKeyPress и выполняет Console.ReadLine(); предлагаемое решение StandardInput.WriteLine ("\ x3"); завершает вызов ReadLine, но не вызывает (для меня) делегат CancelKeyPress. Ошибка/неправильная корректность, потому что любой вход, а не только ctrl + c, запускает процесс выхода? (Нажатие ctrl + c на клавиатуре вызывает триггер для делегата для меня) – 2013-01-15 02:40:51

+0

@David Burg: Возможно, исправление в коде рамки? Сообщение было автором трех версий и> 4 года назад. – Rob 2013-01-15 06:17:47

+0

Чтобы уточнить (извините за gravedig), это не работает, если вы не читаете из stdin, так как он фактически не посылает сигнал – 2015-06-01 19:26:25

6

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

Способ отправки сигнала 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; 
} 
16

Несмотря на то, что использование 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, которая неприемлема в большинстве случаев.

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

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