Console.Out 输出显示在输出 Window 中,在 AllocConsole() 中需要

Console.Out Output is showing in Output Window, Needed in AllocConsole()

好的,在需要 Winform 时,我也一直使用 AllocConsole() 方法进行控制台输出,因为在写入控制台时我使用了多种颜色。

使用 VS 2015 及以下版本,调试模式下的 AllocConsole 始终正常工作,Console.WriteLine 正确写入。现在使用 VS 2017,控制台显示何时调用 AllocConsole,但是,不是 console.WriteLine 输出到该控制台,而是转到 Visual Studio 的输出 window。

我更喜欢使用 AllocConsole 而不是输出 window 因为我非常依赖颜色。我已经搜索了很多关于如何解决这个问题的方法,但我似乎找不到答案。

AllocConsole() 不能单独工作,因为 VS 2017 做了一些 "debug stdout redirect magic"。要解决此问题,您需要使用 AllocConsole() 创建一个控制台并修复标准输出句柄。

这是我找到的片段:

[DllImport("kernel32.dll",
    EntryPoint = "AllocConsole",
    SetLastError = true,
    CharSet = CharSet.Auto,
    CallingConvention = CallingConvention.StdCall)]
private static extern int AllocConsole();

[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFile(
    string lpFileName,
    uint dwDesiredAccess,
    uint dwShareMode,
    uint lpSecurityAttributes,
    uint dwCreationDisposition,
    uint dwFlagsAndAttributes,
    uint hTemplateFile);

private const int MY_CODE_PAGE = 437;
private const uint GENERIC_WRITE = 0x40000000;
private const uint FILE_SHARE_WRITE = 0x2;
private const uint OPEN_EXISTING = 0x3;

public static void CreateConsole()
{
    AllocConsole();

    IntPtr stdHandle = CreateFile(
        "CONOUT$",
        GENERIC_WRITE,
        FILE_SHARE_WRITE,
        0, OPEN_EXISTING, 0, 0
    );

    SafeFileHandle safeFileHandle = new SafeFileHandle(stdHandle, true);
    FileStream fileStream = new FileStream(safeFileHandle, FileAccess.Write);
    Encoding encoding = System.Text.Encoding.GetEncoding(MY_CODE_PAGE);
    StreamWriter standardOutput = new StreamWriter(fileStream, encoding);
    standardOutput.AutoFlush = true;
    Console.SetOut(standardOutput);

    Console.Write("This will show up in the Console window.");
}

特别感谢 Ramkumar Ramesh 提供的解决方法: Console Output is gone in VS2017

基于 wischi 的回答,如果您希望静态控制台 class 的属性正常工作,例如 Console.ForegroundColor,将 desiredAccess 设置为 GENERIC_READ 很重要 | GENERIC_WRITE。我认为这是因为 Console 在内部使用 GetConsoleScreenBufferInfo,当我尝试在 CreateFile 返回的句柄上使用该方法时,只有 GENERIC_WRITE 给我一个 ACCESS_DENIED 错误。

    [DllImport("kernel32.dll")]
private static extern bool AllocConsole();

[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFile(string lpFileName
    , [MarshalAs(UnmanagedType.U4)] DesiredAccess dwDesiredAccess
    , [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode
    , uint lpSecurityAttributes
    , [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition
    , [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes
    , uint hTemplateFile);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetStdHandle(StdHandle nStdHandle, IntPtr hHandle);

private enum StdHandle : int
{
    Input = -10,
    Output = -11,
    Error = -12
}

[Flags]
enum DesiredAccess : uint
{
    GenericRead = 0x80000000,
    GenericWrite = 0x40000000,
    GenericExecute = 0x20000000,
    GenericAll = 0x10000000
}

public static void CreateConsole()
{
    if (AllocConsole())
    {
        //https://developercommunity.visualstudio.com/content/problem/12166/console-output-is-gone-in-vs2017-works-fine-when-d.html
        // Console.OpenStandardOutput eventually calls into GetStdHandle. As per MSDN documentation of GetStdHandle: http://msdn.microsoft.com/en-us/library/windows/desktop/ms683231(v=vs.85).aspx will return the redirected handle and not the allocated console:
        // "The standard handles of a process may be redirected by a call to  SetStdHandle, in which case  GetStdHandle returns the redirected handle. If the standard handles have been redirected, you can specify the CONIN$ value in a call to the CreateFile function to get a handle to a console's input buffer. Similarly, you can specify the CONOUT$ value to get a handle to a console's active screen buffer."
        // Get the handle to CONOUT$.    
        var stdOutHandle = CreateFile("CONOUT$", DesiredAccess.GenericRead | DesiredAccess.GenericWrite, FileShare.ReadWrite, 0, FileMode.Open, FileAttributes.Normal, 0);

        if (stdOutHandle == new IntPtr(-1))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        if (!SetStdHandle(StdHandle.Output, stdOutHandle))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        var standardOutput = new StreamWriter(Console.OpenStandardOutput());
        standardOutput.AutoFlush = true;
        Console.SetOut(standardOutput);
    }
}