多次调用 AllocConsole 和 FreeConsole 并在 C# .NET 中保留控制台对象的输入和输出功能

Calling AllocConsole & FreeConsole multiple times and keep In- & Output functionality of Console object in C# .NET

OS: Windows 10 企业版 1703 64 位

我无法在不丢失 windows 表单应用程序中的 Console.ReadKey() 功能的情况下多次分配和释放 控制台 。 此问题与 Exception when using console window in a form application 类似,但 Console.ReadKey() 函数中缺少答案。

以下代码仅适用于第一次执行。收到一个新控制台 window 并且用户必须按任意键。在第二次执行时,文本也会显示,但 Console.ReadKey() 抛出 System.InvalidOperationException.

System.InvalidOperationException: 'Cannot read keys when either application does not have a console or when console input has been redirected from a file. Try Console.Read.'

为简洁起见,省略了静态 Win32Wrapper class 中的 DllImport 声明。

Win32Wrapper.AllocConsole();

// set standard out handle to console window
var ConOut = Win32Wrapper.CreateFile(
    "CONOUT$",
    GenericAccessRight.GENERIC_READ | GenericAccessRight.GENERIC_WRITE,
    ShareMode.FILE_SHARE_WRITE,
    IntPtr.Zero,
    CreationDisposition.OPEN_EXISTING,
    0,
    IntPtr.Zero
);
Win32Wrapper.SetStdHandle(Win32Wrapper.STD_OUTPUT_HANDLE, ConOut);

// set stadard in handle to console window
var ConIn = Win32Wrapper.CreateFile(
    "CONIN$",
    GenericAccessRight.GENERIC_READ | GenericAccessRight.GENERIC_WRITE,
    ShareMode.FILE_SHARE_READ,
    IntPtr.Zero,
    CreationDisposition.OPEN_EXISTING,
    0,
    IntPtr.Zero
);
Win32Wrapper.SetStdHandle(Win32Wrapper.STD_INPUT_HANDLE, ConIn);

Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true });
Console.SetIn(new StreamReader(Console.OpenStandardInput()));

Console.WriteLine("Hello World!"); // works every time
Console.Read();                    // works every time
Console.ReadKey(true);             // second call: InvalidOperationException

Win32Wrapper.CloseHandle(ConIn)
Win32Wrapper.CloseHandle(ConOut)

return Win32Wrapper.FreeConsole();

我找到了一种模拟上述行为的方法,即不破坏我之前创建的控制台,而是简单地隐藏并再次显示它。

if (FirstTime)
{
  FirstTime = false;
  Win32Wrapper.SetStdHandle(Win32Wrapper.STD_OUTPUT_HANDLE, HWND.Zero);
  Win32Wrapper.SetStdHandle(Win32Wrapper.STD_INPUT_HANDLE, HWND.Zero);
  Win32Wrapper.AllocConsole(); // show implicitly
}
else
{
  Console.Clear(); // clear => simulate new console
  Win32Wrapper.ShowWindow(Win32Wrapper.GetConsoleWindow(), 5); // show (again)
}

Console.WriteLine("Hello World!");
Console.Read();
Console.ReadKey(true);

Win32Wrapper.ShowWindow(Win32Wrapper.GetConsoleWindow(), 0); // hide

只有第一个函数调用会分配一个新的控制台,以后的调用只会再次显示已经存在的控制台。我只需要一个静态变量来跟踪它。