2008-11-12 33 views
21

Tôi đang viết một lớp trình bao bọc cho một dòng lệnh có thể thực thi được. Exe này chấp nhận đầu vào từ stdin cho đến khi tôi nhấn ctrl + c trong shell lệnh, trong trường hợp này nó in ra dựa trên đầu vào cho stdout. Tôi muốn mô phỏng rằng ctrl + c nhấn trong mã C#, gửi lệnh kill đến một đối tượng tiến trình .Net. Tôi đã thử gọi Process.kill(), nhưng điều đó dường như không cho tôi bất cứ thứ gì trong ProcessReader của StandardOutput của quy trình. Có thể có bất cứ điều gì tôi không làm đúng không? Đây là mã tôi đang cố gắng sử dụng:Làm cách nào để gửi ctrl + c tới một tiến trình trong 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(); 

Tuy nhiên, đầu ra luôn trống khi tôi lấy lại dữ liệu từ chế độ stdout khi tôi chạy thủ công. chỉnh sửa: đây là C# 2.0 btw

Trả lời

26

Tôi đã thực sự chỉ tìm ra câu trả lời . Cảm ơn bạn cả cho câu trả lời của bạn, nhưng nó chỉ ra rằng tất cả tôi phải làm được điều này:

p.StandardInput.Close() 

mà làm cho chương trình tôi đã sinh ra để đọc xong từ stdin và đầu ra những gì tôi cần.

+9

Lưu ý rằng nó chỉ hoạt động nếu quá trình đang cố đọc từ đầu vào tiêu chuẩn. Đóng stdin không làm gì cho đến khi chương trình cố gắng đọc cái gì đó từ nó. – Doug

+0

lý do tại sao nó hiển thị ngoại lệ này "StandardIn chưa được chuyển hướng." ? Tôi đang sử dụng ffmpeg để chụp màn hình – Ahmad

-6

Hãy thử thực sự gửi tổ hợp phím Ctrl + C, thay vì trực tiếp chấm dứt quá trình:

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

Nhìn nó lên trên MSDN, bạn nên tìm những gì bạn cần có để gửi tổ hợp Ctrl + Key ... Tôi biết rằng thư bạn cần để gửi Alt + Key là WM_SYSTEMKEYDOWN và WM_SYSTEMKEYUP, không thể cho bạn biết về Ctrl ...

+0

Điều gì nếu quá trình được bắt đầu ẩn mà không có một cửa sổ? – FindOutIslamNow

19

@alonl: Người dùng là cố gắng bọc một chương trình dòng lệnh. Các chương trình dòng lệnh không có các máy chủ tin nhắn trừ khi chúng được tạo ra đặc biệt, và ngay cả khi trường hợp đó xảy ra, Ctrl + C không có cùng ngữ nghĩa trong một ứng dụng môi trường Windows (bản sao, theo mặc định) như trong một môi trường dòng lệnh (Break).

Tôi đã ném điều này lại với nhau. CtrlCClient.exe chỉ đơn giản gọi Console.ReadLine() và chờ đợi:


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

đầu ra của tôi dường như để làm những gì bạn muốn:

 
4080 is active: True 

4080 is active: False 

Hy vọng rằng sẽ giúp!

(Để làm rõ:.. \ X3 là chuỗi hex thoát cho nhân vật hex 3, đó là ctrl + c Nó không phải chỉ là một con số kỳ diệu;))

+4

Sử dụng một chương trình thử nghiệm mà cả hai chỉ định giao diện Console.CancelKeyPress và làm một Console.ReadLine(); giải pháp được đề xuất của StandardInput.WriteLine ("\ x3"); hoàn thành cuộc gọi ReadLine nhưng không (đối với tôi) kích hoạt đại biểu CancelKeyPress. Lỗi/trình diễn chính xác không chính xác vì bất kỳ đầu vào nào, không chỉ ctrl + c, sẽ kích hoạt quá trình thoát? (Nhấn ctrl + c trên bàn phím sẽ kích hoạt đại biểu cho tôi) –

+0

@David Burg: Có thể là một bugfix trong mã khung? Bài đăng được viết ba phiên bản và cách đây 4 năm. – Rob

+0

Để làm rõ (Xin lỗi cho gravedig), điều này không hoạt động nếu bạn không đọc từ stdin, vì nó không thực sự gửi tín hiệu –

6

Ok, đây là giải pháp.

Cách gửi tín hiệu Ctrl-C bằng GenerateConsoleCtrlEvent. BAO GIỜ, cuộc gọi này có tham số processGroupdID và gửi tín hiệu Ctrl-C tới tất cả các tiến trình trong nhóm. Điều này sẽ tốt nếu không có thực tế là không có cách nào để sinh ra quá trình con trong .net nằm trong một nhóm tiến trình khác với bạn (cha mẹ) đang ở. Vì vậy, khi bạn gửi GenerateConsoleCtrlEvent, cả hai con VÀ BẠN (PHỤ HUYNH) NHẬN CNTT. Vì vậy, bạn cần phải nắm bắt các sự kiện ctrl-c trong phụ huynh quá, và sau đó xác định nếu bạn ned bỏ qua nó không.

Trong trường hợp của mình, tôi muốn phụ huynh có thể xử lý các sự kiện Ctrl-C, vì vậy tôi cần phân biệt giữa các sự kiện Ctrl-C do người dùng gửi trên bảng điều khiển và các sự kiện được gửi bởi quá trình gốc tới đứa trẻ. Tôi làm điều này bằng cách chỉ hackishly setting/unsetting một cờ boolean trong khi gửi ctrl-c cho đứa trẻ, và sau đó kiểm tra cờ này trong trình xử lý sự kiện ctrl-c của cha mẹ (ví dụ: nếu gửi ctrl-c cho con, thì bỏ qua.)

Vì vậy, các mã sẽ giống như thế này:

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

Hai cải tiến đối với việc nhập khẩu theo quy tắc StyleCop và FxCop. Quá dài để nhận xét như vậy sẽ cố gắng nội tuyến ... –

+5

'p.SessionId' không phải là tham số chính xác cho' GenerateConsoleCtrlEvent'. –

+2

Mã này không thể hoạt động. GenerateConsoleCtrlEvent yêu cầu id nhóm tiến trình, không phải id phiên đầu cuối. – user626528

16

Mặc dù thực tế rằng việc sử dụng GenerateConsoleCtrlEvent để gửi tín hiệu Ctrl + C là một câu trả lời đúng nó cần làm rõ ý nghĩa để làm cho nó làm việc trong khác nhau. Các loại ứng dụng NET.

Nếu ứng dụng NET của bạn không sử dụng giao diện điều khiển của riêng mình (WinForms/WPF/Windows Service/ASP.NET) dòng chảy cơ bản là:

  1. Gắn quá trình .NET chính để an ủi của quá trình bạn muốn Ctrl + C
  2. Ngăn chặn quá trình .NET chính từ dừng lại vì sự kiện Ctrl + C với SetConsoleCtrlHandler
  3. Tạo giao diện điều khiển sự kiện cho hiện console với GenerateConsoleCtrlEvent (processGroupId nên zero! trả lời với mã mà gửi p.SessionId sẽ không công việc và không chính xác)
  4. Ngắt kết nối từ giao diện điều khiển và khôi phục lại tổ hợp phím Ctrl + C xử lý theo quy trình chính

Đoạn mã sau minh họa làm thế nào để làm điều đó:

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

nơi SetConsoleCtrlHandler, FreeConsole, AttachConsole và GenerateConsoleCtrlEvent là phương pháp WinAPI mẹ đẻ:

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

Mọi thứ trở nên phức tạp hơn nếu bạn cần gửi Ctrl + C từ ứng dụng bảng điều khiển .NET. Phương pháp tiếp cận sẽ không hoạt động vì AttachConsole trả về false trong trường hợp này (ứng dụng bảng điều khiển chính đã có bảng điều khiển). Có thể gọi FreeConsole trước cuộc gọi AttachConsole nhưng kết quả là giao diện điều khiển ứng dụng .NET gốc sẽ bị mất và không thể chấp nhận được trong hầu hết các trường hợp.

Giải pháp của tôi đối với trường hợp này (mà thực sự làm việc và không có tác dụng phụ cho NET quá trình chính console):

  1. Tạo nhỏ chương trình điều khiển hỗ trợ .NET chấp nhận quá trình ID từ đối số dòng lệnh, thua nó giao diện điều khiển riêng với FreeConsole trước AttachConsole cuộc gọi và gửi Ctrl + C để quá trình với mã nêu trên
  2. chính NET quá trình giao diện điều khiển nhắm mục tiêu chỉ gọi tiện ích này trong tiến trình mới khi nó cần phải gửi Ctrl + C để một quá trình giao diện điều khiển
+0

Đây là giải pháp duy nhất làm việc cho tôi khi sử dụng System.Diagnostics.Process. Cảm ơn! –

+0

Đoạn mã này đóng ứng dụng chính (WinForms) nếu nó được gọi hai lần. Tôi gắn nó với một phím bấm trong ứng dụng của tôi, và nó đóng nó ngay cả khi phím được nhấn vài giây ngoài và toàn bộ đoạn mã bên trong một tuyên bố 'khóa'. – Alexey

Các vấn đề liên quan