我们在调试程序时,通常会将日志输出到 控制台
,以监控其运行状态是否符合预期。对于一个 C# GUI
程序(如 WinForms
或 WPF
)而言,如果在 IDE
中进行调试运行,Console.WriteLine()
方法会将内容输出到 IDE
的控制台中。然而在同一时间内,IDE
只能呈现单个程序的控制台输出,如果涉及到多个 GUI
程序的联合调试,应该如何将其它程序的控制台输出也呈现出来呢?
例如,一个 Visual Studio(VS)
解决方案(Solution
)中有 A
和 B
两个 WPF
项目(Project
),其对应的编译产物分别为 A.exe
和 B.exe
,假设 A.exe
在启动时会调起 B.exe
;当我们在 VS
中调试运行 A
项目时,B
项目会随之启动,但是 B
项目中 Console.WriteLine()
的内容不会呈现在 VS
的控制台中,那么我们应该如何调出控制台来呈现呢?另外,我们在生产环境中运行 GUI
程序时,有时也需要将一些信息输出到 控制台
来辅助我们定位问题。
如上所述,本文将要讨论的便是 如何在 C# GUI
程序中显示控制台。通常来讲,在 Windows
系统上,每个进程最多可以和一个控制台关联,每个控制台可以被多个进程关联。GUI
程序初始化的时候并没有控制台,在 IDE
中进行调试时,之所以能看见 Console.WriteLine()
输出,是因为 IDE
为 GUI
进程关联了控制台。要对控制台进行操作,需要了解如下 Windows API
:
BOOL WINAPI AllocConsole(void);
为当前进程分配一个新的控制台,如果已有控制台和当前进程关联,将会分配失败。
BOOL WINAPI FreeConsole(void);
释放当前进程关联的控制台,如果该控制台没有同时被其它进程关联,则会将其销毁。
BOOL WINAPI AttachConsole(
_In_ DWORD dwProcessId
);
将当前进程关联到某个进程的附加控制台,-1
表示当前进程的父进程。
HWND WINAPI GetConsoleWindow(void);
获取当前进程所关联的控制台的窗口句柄。
有了如上的 API
,我们便可轻松地为 GUI
程序分配或附加控制台,其对应的 C# API
为:
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool FreeConsole();
[DllImport("kernel32", SetLastError = true)]
internal static extern bool AttachConsole(int dwProcessId);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetConsoleWindow();
首先,使用 GetConsoleWindow
来判断当前进程是否有关联的控制台;如果没有,则使用 AttachConsole
来关联父进程的控制台;如果关联失败(父进程也没有控制台),则使用 AllocConsole
来分配新的控制台。使用 C#
来实现如下的 控制台管理器
类:
///
/// Manage the attached console for calling process.
///
public static class ConsoleManager
{
private static bool _hasConsole;
private static bool _hasCreated;
///
/// Show the attached console.
///
public static void Show()
{
// Already has attached console?
if (GetConsoleWindow() != IntPtr.Zero)
{
_hasConsole = true;
return;
}
// Try to attach the console of parent process
if (AttachConsole(-1))
{
_hasConsole = true;
return;
}
// To alloc a new console
_hasCreated = AllocConsole();
_hasConsole = _hasCreated;
}
///
/// Close the attached console.
///
public static void Close()
{
if (_hasCreated)
{
FreeConsole();
}
_hasCreated = false;
_hasConsole = false;
}
///
/// Writes the message, followed by the current line terminator, to the attached console.
///
///
public static void WriteLine(string message)
{
EnsureConsole();
Console.WriteLine(message);
}
///
/// Writes the message to the attached console.
///
///
public static void Write(string message)
{
EnsureConsole();
Console.Write(message);
}
///
/// Make sure the attached console exists.
///
private static void EnsureConsole()
{
if (!_hasConsole)
{
Show();
}
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FreeConsole();
[DllImport("kernel32", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();
}
使用方法非常简单,在 GUI
程序启动时调用 ConsoleManager.Show()
来为进程附加控制台;通过 ConsoleManager.WriteLine()
或 Console.WriteLine()
将信息输出到控制台;在程序结束时调用 ConsoleManager.Close()
来释放控制台。
ConsoleManager.Show();
...
ConsoleManager.WriteLine("Testing");
// Alternatively
Console.WriteLine("Testing")
...
ConsoleManager.Close();