我正在为命令行可执行文件编写包装类。这个exe接受来自stdin的输入,直到我在命令提示符shell中命中ctrl + c,在这种情况下,它根据输入到stdout打印输出。我想模拟ctrl + c按c#代码,将kill命令发送到.Net进程对象。我试过调用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 btw
答案 0 :(得分:27)
我其实只是想出了答案。谢谢你们的答案,但事实证明我所要做的就是:
p.StandardInput.Close()
导致我生成的程序完成从stdin读取并输出我需要的内容。
答案 1 :(得分:23)
尽管使用GenerateConsoleCtrlEvent发送Ctrl + C信号是一个正确的答案,但需要明确说明才能使其在不同的.NET应用程序类型中工作。
如果您的.NET应用程序不使用自己的控制台(WinForms / WPF / Windows服务/ ASP.NET),则基本流程为:
以下代码段说明了如何执行此操作:
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主进程控制台没有任何副作用):
答案 2 :(得分:22)
@alonl:用户正在尝试包装命令行程序。命令行程序没有消息泵,除非它们是专门创建的,即使是这种情况,Ctrl + C在Windows环境应用程序中没有相同的语义(默认情况下为copy),因为它在命令行环境(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。它不仅仅是一个幻数。;))
答案 3 :(得分:7)
好的,这是一个解决方案。
发送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;
}
答案 4 :(得分:0)
就我而言,FWIW我想要的是从控制台进程中创建一个子控制台进程(针对此问题为ffmpeg.exe),并在我的进程和子进程中支持干净的CTRL-C处理(ffmpreg退出)通常在按下CTRL-C时,这是我想继续使用的好功能)
我在这里找到的所有解决方案都没有用,所以我只是互操作了Windows'CreateProcess function而无须任何努力,子应用程序和父应用程序会自动接收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);
答案 5 :(得分:-7)
尝试实际发送组合键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 ...