2008-11-12 6 views
21

Piszę klasy wrapper dla pliku wykonywalnego wiersza poleceń. Ten exe akceptuje dane wejściowe ze standardowego wejścia, dopóki nie uderzę ctrl + c w powłoce wiersza poleceń, w takim przypadku wydrukuje dane wyjściowe na podstawie danych wejściowych na standardowe wyjście. Chcę symulować, że ctrl + c wciśnij w kod C#, wysyłając polecenie kill do obiektu procesu .Net. Próbowałem wywoływać Process.kill(), ale nie wydaje mi się, żebym dał coś w standardzie StreamOneput ProcessOutput. Czy może być coś, czego nie robię dobrze? Oto kod próbuję użyć:Jak wysłać ctrl + c do procesu w języku 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(); 

jednak wyjście jest zawsze pusty, chociaż mam dane z powrotem z stdout kiedy uruchomić exe ręcznie. edit: to jest C# 2.0 btw

Odpowiedz

26

mam właściwie tylko zorientowali się odpowiedź . Dziękuję zarówno za odpowiedzi, ale okazuje się, że wszystko, co musiałem zrobić, to w ten sposób:

p.StandardInput.Close() 

który powoduje, że program mam zrodził dokończyć czytanie ze standardowego wejścia i wyjścia, co muszę.

+9

Pamiętaj, że działa tylko wtedy, gdy proces próbuje odczytać ze standardowego wejścia. Zamknięcie stdin nic nie robi, dopóki program nie spróbuje odczytać z niego czegoś. – Doug

+0

dlaczego pokazuje ten wyjątek "StandardIn nie został przekierowany." ? Używam programu ffmpeg do przechwytywania ekranu. – Ahmad

-6

Spróbuj faktycznie wysyłając kombinację klawiszy Ctrl + C, zamiast bezpośrednio zakończenia procesu:

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

Spójrz go na MSDN, należy znaleźć to, czego potrzebujesz tam w celu wysłania kombinacji klawiszy Ctrl + Key ... Wiem, że wiadomość potrzebna do wysłania Alt + Key to WM_SYSTEMKEYDOWN i WM_SYSTEMKEYUP, nie mogę powiedzieć o Ctrl ...

+0

Co jeśli proces jest uruchamiany ukryty bez okna? – FindOutIslamNow

19

@alonl: Użytkownik jest próba owinięcia programu wiersza poleceń. Programy wiersza polecenia nie mają pomp komunikatów, chyba że zostały specjalnie utworzone, a nawet jeśli tak było, Ctrl + C nie ma tej samej semantyki w aplikacji środowiska Windows (domyślnie kopiowanie), tak jak robi to w środowisko linii poleceń (Przerwa).

Zrzuciłem to razem. CtrlCClient.exe prostu wywołuje Console.ReadLine() i czeka:


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

moje wyjście wydaje się robić to, co chcesz:

 
4080 is active: True 

4080 is active: False 

nadzieję, że pomoże!

(Dla wyjaśnienia:.. \ X3 jest sekwencja hex ucieczka dla postaci hex 3, która jest ctrl + c To nie tylko liczba magiczna;))

+4

Używanie programu testowego, który przypisuje delegację Console.CancelKeyPress i wykonuje konsolę Console.ReadLine(); proponowane rozwiązanie StandardInput.WriteLine ("\ x3"); wykonuje połączenie ReadLine, ale nie wywołuje (dla mnie) delegata CancelKeyPress. Błąd/niepoprawna demonstracja poprawności, ponieważ jakiekolwiek wejście, nie tylko ctrl + c, wyzwala wyjście procesu? (Naciśnięcie ctrl + c na klawiaturze uruchamia dla mnie delegata) –

+0

@ David Burg: Prawdopodobnie błąd w kodzie frameworkowym? Post został napisany w trzech wersjach i> 4 lata temu. – Rob

+0

Aby wyjaśnić (przepraszam za gravedig), to nie działa, jeśli nie czytasz ze standardowego wejścia, ponieważ w rzeczywistości nie wysyła sygnału –

6

OK, oto rozwiązanie.

Sposób przesyłania sygnału Ctrl-C za pomocą GenerateConsoleCtrlEvent. JEDNAK, to wywołanie pobiera parametr processGroupdID i wysyła sygnał Ctrl-C do wszystkich procesów w grupie. Byłoby dobrze, gdyby nie fakt, że nie ma możliwości, aby potomek spawnował proces w .net, który znajduje się w innej grupie procesowej niż ty (rodzic). Więc kiedy wysyłasz GenerateConsoleCtrlEvent, oba te elementy A TY (RODZICA) DOSTAJ GO. Tak więc, musisz także uchwycić zdarzenie ctrl-c w rodzica, a następnie określić, czy nie chcesz tego zignorować.

W moim przypadku chcę, aby rodzic również mógł obsługiwać zdarzenia Ctrl-C, więc muszę rozróżnić między zdarzeniami Ctrl-C wysyłanymi przez użytkownika na konsoli, a tymi wysyłanymi przez proces macierzysty do dziecko. Robię to, po prostu hakująco ustawiając/rozbrajając flagę binarną podczas wysyłania ctrl-c do potomka, a następnie sprawdzając tę ​​flagę w macierzystym module obsługi zdarzenia ctrl-c (tj. Jeśli wysyła ctrl-c do podrzędnego, a następnie ignoruje.)

Więc, kod będzie wyglądał mniej więcej tak:

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

Kilka poprawek do importu na reguły StyleCop i FxCop. Zbyt długi komentarz, więc spróbuje inline ... –

+5

'p.SessionId' nie jest poprawnym parametrem dla' GenerateConsoleCtrlEvent'. –

+2

Ten kod nie działa. GenerateConsoleCtrlEvent wymaga identyfikatora grupy procesów, a nie identyfikatora sesji terminalowej. – user626528

16

Pomimo faktu, że za pomocą GenerateConsoleCtrlEvent do wysyłania sygnału Ctrl + C jest prawidłowa odpowiedź to potrzebuje znaczącego wyjaśnień, aby działać w różny. Typy aplikacji NET.

Jeśli aplikacja .NET nie używa własną konsolę (WinForms/WPF/Windows życzenie/ASP.NET) podstawowy przepływ jest:

  1. Zamontuj główny proces .NET do konsoli procesu chcesz Ctrl + C
  2. Prevent główny proces .NET od zatrzymania z powodu zdarzenia Ctrl + C z SetConsoleCtrlHandler
  3. wygenerować zdarzenie na konsoli bieżącym konsoli z GenerateConsoleCtrlEvent (processGroupId powinna wynosić zero! Odpowiedź z kodem, który wysyła p.SessionId nie będzie działa i jest nieprawidłowy)
  4. Odłącz od konsoli i przywrócić Ctrl + C obsługi przez głównego procesu

Poniższy fragment kodu pokazuje, jak to zrobić:

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

gdzie SetConsoleCtrlHandler, FreeConsole, AttachConsole i GenerateConsoleCtrlEvent są natywne metody 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); 

Rzeczy stają się bardziej złożone, jeśli trzeba wysłać Ctrl + C z aplikacji konsoli .NET. Podejście nie zadziała, ponieważ AttachConsole zwraca w tym przypadku wartość false (główna aplikacja konsoli ma już konsolę). Możliwe jest wywołanie FreeConsole przed wywołaniem AttachConsole, ale w rezultacie oryginalna konsola aplikacji .NET zostanie utracona, co w większości przypadków jest niedopuszczalne.

Moje rozwiązanie dla tego przypadku (to naprawdę działa i nie ma skutków ubocznych dla .NET konsoli głównego procesu):

  1. tworzyć małe program konsoli .NET wspieranie który akceptuje identyfikator procesu z argumentów wiersza poleceń, traci własna konsola z FreeConsole przed AttachConsole rozmowy i wysyła Ctrl + C, aby proces z kodem wspomniano powyżej
  2. procesu konsoli .NET Główny cel tylko wywołuje to narzędzie w nowym procesie, gdy jest konieczne do wysłania Ctrl + C do innego procesu konsoli
+0

Jest to w rzeczywistości jedyne rozwiązanie, które działało dla mnie podczas korzystania z System.Diagnostics.Process. Dzięki! –

+0

Ten fragment kodu zamyka główną aplikację (WinForm), jeśli jest wywoływana dwa razy. Przywiązałem go do naciśnięcia klawisza w mojej aplikacji i zamyka się, nawet gdy klawisz jest wciśnięty kilka sekund od siebie, a cały fragment znajduje się wewnątrz instrukcji 'lock'. – Alexey