[英]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),則基本流程是:
SetConsoleCtrlHandler()
禁用信號處理,防止主 .NET 進程因Ctrl + C事件而停止。GenerateConsoleCtrlEvent()
為當前控制台生成控制台事件( processGroupId
應該為零!發送p.SessionId
代碼的答案將p.SessionId
且不正確)。以下代碼片段說明了如何做到這一點:
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 主進程控制台沒有副作用:
AttachConsole()
調用之前使用FreeConsole()
失去自己的控制台,並使用上述代碼將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.