簡體   English   中英

如何將 ctrl+c 發送到 c# 中的進程?

[英]How do I send ctrl+c to a process in c#?

我正在為命令行可執行文件編寫包裝類。 這個 exe 接受來自stdin輸入,直到我在命令提示符 shell 中按下Ctrl+C ,在這種情況下,它會根據輸入將stdout打印到stdout 我想在 C# 代碼中模擬Ctrl+C按下,將 kill 命令發送到 .NET Process對象。 我試過調用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();

輸出始終為空,即使我在手動運行 exe 時從stdout取回數據。

編輯:順便說一下,這是 C# 2.0。

盡管使用GenerateConsoleCtrlEvent()發送Ctrl + C信號是正確的答案,但它需要大量澄清才能使其在不同的 .NET 應用程序類型中工作。

如果您的 .NET 應用程序不使用自己的控制台(Windows 窗體/WPF/Windows 服務/ASP.NET),則基本流程是:

  1. 將主 .NET 進程附加到要使用Ctrl + C發出信號的進程的控制台。
  2. 通過使用SetConsoleCtrlHandler()禁用信號處理,防止主 .NET 進程因Ctrl + C事件而停止。
  3. 使用GenerateConsoleCtrlEvent()當前控制台生成控制台事件( processGroupId應該為零!發送p.SessionId代碼的答案將p.SessionId且不正確)。
  4. 等待發出信號的進程響應(例如,等待它退出)
  5. 通過主進程恢復Ctrl + C處理並斷開與控制台的連接。

以下代碼片段說明了如何做到這一點:

Process p;
if (AttachConsole((uint)p.Id)) {
    SetConsoleCtrlHandler(null, true);
    try { 
        if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT,p.SessionId))
            return false;
        p.WaitForExit();
    } finally {
        SetConsoleCtrlHandler(null, false);
        FreeConsole();
    }
    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信號將保留在當前進程的輸入隊列中,當第二次調用SetConsoleCtrlHandler()恢復處理時,該信號將終止當前進程,而不是目標進程。

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

這是我對這種情況的解決方案; 它可以工作並且對 .NET 主進程控制台沒有副作用:

  1. 創建小的支持 .NET 控制台程序,它接受來自命令行參數的進程 ID,在AttachConsole()調用之前使用FreeConsole()失去自己的控制台,並使用上述代碼將Ctrl + C發送到目標進程。
  2. 當主 .NET 控制台進程需要將Ctrl + C發送到另一個控制台進程時,它只會在新進程中調用此實用程序。

我其實剛剛想出了答案。 謝謝你們的回答,但事實證明我所要做的就是:

p.StandardInput.Close()

這導致我生成的程序完成從標准輸入讀取並輸出我需要的內容。

@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 。它不僅僅是一個幻數。;))

好的,這是一個解決方案。

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

FWIW,在我的情況下,我想要的是,從控制台進程,創建一個子控制台進程(ffmpeg.exe)並在我的進程和子進程中支持干凈的CTRL - C處理(ffmpreg 在CTRL時正常退出- 按下C ,這是我想繼續工作的一個很好的功能)

我在這里找到的解決方案都沒有工作,所以我只是互操作 Windows 的CreateProcess 函數,它不需要任何努力就可以工作, CTRL - C由子應用程序和父應用程序自動接收,輸入和輸出流是共享的,等等。我無法使用標准的 .NET Process 類重現那種代碼:

    static void RunFFMpeg(string arguments)
    {
        var startup = new STARTUPINFO();
        startup.cb = Marshal.SizeOf<STARTUPINFO>();
        if (!CreateProcess(null, "ffmpeg.exe " + arguments, IntPtr.Zero, IntPtr.Zero, false, 0, IntPtr.Zero, null, ref startup, out var info))
            throw new Win32Exception(Marshal.GetLastWin32Error());

        CloseHandle(info.hProcess);
        CloseHandle(info.hThread);

        var process = Process.GetProcessById(info.dwProcessId);
        Console.CancelKeyPress += (s, e) =>
        {
            process.WaitForExit();
            Console.WriteLine("Abort.");
            // end of program is here
        };

        process.WaitForExit();
        Console.WriteLine("Exit.");
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct STARTUPINFO
    {
        public int cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public int dwX;
        public int dwY;
        public int dwXSize;
        public int dwYSize;
        public int dwXCountChars;
        public int dwYCountChars;
        public int dwFillAttribute;
        public int dwFlags;
        public short wShowWindow;
        public short cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

    [DllImport("kernel32")]
    private static extern bool CloseHandle(IntPtr hObject);

    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern bool CreateProcess(
       string lpApplicationName,
       string lpCommandLine,
       IntPtr lpProcessAttributes,
       IntPtr lpThreadAttributes,
       bool bInheritHandles,
       int dwCreationFlags,
       IntPtr lpEnvironment,
       string lpCurrentDirectory,
       ref STARTUPINFO lpStartupInfo,
       out PROCESS_INFORMATION lpProcessInformation);

嘗試實際發送組合鍵 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 ...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM