我终于弄清楚了如何在启动 Windows 应用程序时透明地附加到控制台(如果它是前台窗口)。
不要问我为什么必须传递 STD_ERROR_HANDLE 而不是 STD_OUTPUT_HANDLE,但它只是有效,可能是因为标准错误可以共享。
注意:控制台可以在内部显示应用程序消息时接受用户输入,但是在 stderr 从应用程序输出时使用它有点令人困惑。
使用这段代码,如果您使用至少一个参数从控制台窗口启动应用程序,它将附加 Console.Write 到它,如果您使用参数 /debug 启动应用程序,那么它甚至会将 Debug.Write 附加到安慰。
在退出应用程序之前调用 Cleanup() 以释放控制台,并发送 Enter 按键以释放最后一行,以便控制台像启动应用程序之前一样可用。
附言。您不能通过此方法使用输出重定向,即:yourapp.exe > file.txt,因为
你会得到一个空文件。甚至不要尝试 myapp.exe > file.txt 2>&1,因为您将使应用程序崩溃(将错误重定向到输出意味着我们正在尝试附加到非共享缓冲区)。
这是代码:
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
[DllImport("kernel32.dll",
EntryPoint = "GetStdHandle",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll",
EntryPoint = "AllocConsole",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern int AllocConsole();
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();
private const int STD_OUTPUT_HANDLE = -11;
private const int STD_ERROR_HANDLE = -12;
private static bool _consoleAttached = false;
private static IntPtr consoleWindow;
[STAThread]
static void Main()
{
args = new List<string>(Environment.GetCommandLineArgs());
int prId;
consoleWindow = GetForegroundWindow();
GetWindowThreadProcessId(consoleWindow, out prId);
Process process = Process.GetProcessById(prId);
if (args.Count > 1 && process.ProcessName == "cmd")
{
if (AttachConsole((uint)prId)) {
_consoleAttached = true;
IntPtr stdHandle = GetStdHandle(STD_ERROR_HANDLE); // must be error dunno why
SafeFileHandle safeFileHandle = new SafeFileHandle(stdHandle, true);
FileStream fileStream = new FileStream(safeFileHandle, FileAccess.Write);
Encoding encoding = Encoding.ASCII;
StreamWriter standardOutput = new StreamWriter(fileStream, encoding);
standardOutput.AutoFlush = true;
Console.SetOut(standardOutput);
if (args.Contains("/debug")) Debug.Listeners.Add(new TextWriterTraceListener(Console.Out));
Console.WriteLine(Application.ProductName + " was launched from a console window and will redirect output to it.");
}
}
// ... do whatever, use console.writeline or debug.writeline
// if you started the app with /debug from a console
Cleanup();
}
private static void Cleanup() {
try
{
if (_consoleAttached)
{
SetForegroundWindow(consoleWindow);
SendKeys.SendWait("{ENTER}");
FreeConsole();
}
}
}