C# GUI 程序显示控制台

C# GUI 程序显示控制台

我们在调试程序时,通常会将日志输出到 控制台,以监控其运行状态是否符合预期。对于一个 C# GUI 程序(如 WinFormsWPF)而言,如果在 IDE 中进行调试运行,Console.WriteLine() 方法会将内容输出到 IDE 的控制台中。然而在同一时间内,IDE 只能呈现单个程序的控制台输出,如果涉及到多个 GUI 程序的联合调试,应该如何将其它程序的控制台输出也呈现出来呢?
例如,一个 Visual Studio(VS) 解决方案(Solution)中有 AB 两个 WPF 项目(Project),其对应的编译产物分别为 A.exeB.exe ,假设 A.exe 在启动时会调起 B.exe ;当我们在 VS 中调试运行 A 项目时,B 项目会随之启动,但是 B 项目中 Console.WriteLine() 的内容不会呈现在 VS 的控制台中,那么我们应该如何调出控制台来呈现呢?另外,我们在生产环境中运行 GUI 程序时,有时也需要将一些信息输出到 控制台 来辅助我们定位问题。

原理介绍

如上所述,本文将要讨论的便是 如何在 C# GUI 程序中显示控制台。通常来讲,在 Windows 系统上,每个进程最多可以和一个控制台关联,每个控制台可以被多个进程关联GUI 程序初始化的时候并没有控制台,在 IDE 中进行调试时,之所以能看见 Console.WriteLine() 输出,是因为 IDEGUI 进程关联了控制台。要对控制台进行操作,需要了解如下 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();
  • 源码:https://github.com/Iron-YeHong/.NETUtilities
  • 其它方案:No output to console from a WPF application? - StackOverflow

参考资料

  • AllocConsole function - Microsoft
  • AttachConsole function - Microsoft
  • FreeConsole function - Microsoft

你可能感兴趣的:(C#/WinForm/WPF)