2008-11-12 32 views
21

我正在爲命令行可執行文件編寫包裝類。這個exe接受來自stdin的輸入,直到我在命令提示符shell中按ctrl + c,在這種情況下,它會根據stdout的輸入打印輸出。我想模擬ctrl + c在c#代碼中按下,發送kill命令給.Net過程對象。我曾嘗試調用Process.kill(),但似乎並沒有在流程的StandardOutput 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(); 

但輸出始終是空的,即使我得到的數據,從stdout中回來時,我手動運行exe。 編輯:這是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 +組合鍵... 我知道你需要發送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 

希望幫助!

(澄清:\ X3是十六進制字符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信號發送到組中的所有進程。如果不是因爲沒有辦法在.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; 
} 
16

儘管事實上,使用GenerateConsoleCtrlEvent發送按Ctrl + C信號是需要顯著澄清得到它在不同的工作中正確的答案。 NET應用程序類型。

如果你的.NET應用程序不使用自己的控制檯(的WinForms/WPF/Windows服務/ ASP.NET)基本流程是:

  1. 連接主.NET程序要安慰過程
  2. 產生當前控制檯GenerateConsoleCtrlEvent(processGroupId控制檯的事件按Ctrl + C
  3. 防止主.NET從SetConsoleCtrlHandler因爲按Ctrl + C事件停止過程應該是零!與發送p.SessionId代碼答案不工作和不正確)
  4. 012從控制檯
  5. 斷開和恢復按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); 

如果您需要從.NET控制檯應用程序發送Ctrl + C,事情會變得更加複雜。方法不會工作,因爲AttachConsole在這種情況下返回false(主控制檯應用程序已經有一個控制檯)。可以在AttachConsole調用之前調用FreeConsole,但結果是原始的.NET應用程序控制臺將丟失,這在大多數情況下是不可接受的。

我對這種情況下的解決方案(中真正起作用的,並且沒有副作用.NET主要工藝控制檯):

  1. 創建命令行參數的接受進程ID小的支持.NET控制檯程序,失去其自己的控制檯FreeConsole AttachConsole呼叫之前,發送Ctrl + C可針對以上
  2. 主要.NET控制檯進程中提到碼進程只是在調用新工藝此實用工具時,它需要按Ctrl + C發送到另一臺主機的過程