【问题标题】:How to close / open console within C# using Win32 calls?如何使用 Win32 调用在 C# 中关闭/打开控制台?
【发布时间】:2012-03-08 14:01:32
【问题描述】:

以下程序在“Console.ReadKey()”上引发错误。

如何在禁用控制台后重新启用它?

    using System;
    using System.Threading;
    using System.Runtime.InteropServices;

    namespace ConsoleApplication
    {
        class Program
        {
            static void Main(string[] args)
            {
                ThreadPool.QueueUserWorkItem((o) =>
                {
                    Thread.Sleep(1000);
                    IntPtr stdin = GetStdHandle(StdHandle.Stdin);
                    CloseHandle(stdin);
                });
                Console.ReadLine();
                Console.Write("ReadLine() successfully aborted by background thread.\n");
                Console.Write("[any key to exit]");
                Console.ReadKey(); // Throws an exception "Cannot read keys when either application does not have a console or when console input has been redirected from a file. Try Console.Read."
            }

            // P/Invoke:
            private enum StdHandle { Stdin = -10, Stdout = -11, Stderr = -12 };
            [DllImport("kernel32.dll")]
            private static extern IntPtr GetStdHandle(StdHandle std);
            [DllImport("kernel32.dll")]
            private static extern bool CloseHandle(IntPtr hdl);
        }
    }

专家补充

如果您想知道,我需要能够在 C# 中终止运行 ReadLine() 的后台线程。这似乎是唯一的方法(thread.Abort 不起作用,因为 ReadLine() 在操作系统的内部深处以非托管代码运行)。 StackOverflow 上有很多关于这个话题的讨论,没有人真正发现(或发布)中止 Console.ReadLine() 的令人满意的方法。我认为这段代码在正确的轨道上——如果我们能在禁用控制台后重新启用它。

【问题讨论】:

  • 如果您关闭手柄...您将不得不再次手动打开它。考虑到您刚刚关闭了 Handle,因此无法保证您可以再次使用相同的 Handle。您确定关闭正确的手柄吗?我必须相信有一种更简单的方法可以中止 Readline,然后关闭标准输入的血腥句柄。
  • @Ramhound 我希望有一种更简单的方法来中止 ReadLine。我已经尝试了一切 - .Abort,向应用程序发送密钥(仅适用于当前焦点)等。如果有更好的方法,我会全力以赴。
  • @Ramhound 我能想到的唯一其他方法是在 100% 托管的 C# 中编写 ReadLine 的克隆,因此 .Abort 将起作用,与几行 Win32 代码相比,这肯定更复杂.

标签: c# .net visual-studio


【解决方案1】:

使用 PostMessage 发送 [enter] 进入当前进程:

    class Program
    {
        [DllImport("User32.Dll", EntryPoint = "PostMessageA")]
        private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);

        const int VK_RETURN = 0x0D;
        const int WM_KEYDOWN = 0x100;

        static void Main(string[] args)
        {
            Console.Write("Switch focus to another window now to verify this works in a background process.\n");

            ThreadPool.QueueUserWorkItem((o) =>
            {
                Thread.Sleep(4000);

                var hWnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
                PostMessage(hWnd, WM_KEYDOWN, VK_RETURN, 0);
            });

            Console.ReadLine();

            Console.Write("ReadLine() successfully aborted by background thread.\n");
            Console.Write("[any key to exit]");
            Console.ReadKey();
        }
    }

这个答案也适用于在 ReadLine() 上调用 .Abort 不起作用的事实,因为代码在 Windows 内核深处的非托管代码中运行。

此答案优于任何仅在当前进程具有焦点时才有效的答案,例如SendKeysInput Simulator

这个答案优于关闭当前控制台句柄的方法,因为关闭当前控制台句柄的行为会导致以后调用 ReadLine() 时抛出错误。

【讨论】:

  • 我已经使用了这个解决方案,但是如果使用 CTRL + F5(visual studio 2017) 运行项目,它将无法工作,如果使用 F5 或只是可执行文件就可以了
【解决方案2】:

不知道它是否有帮助,但是当我需要能够在 win 表单应用程序中写入控制台时,我使用了这个类:

public class ConsoleHelper
{
    /// <summary>
    /// Allocates a new console for current process.
    /// </summary>
    [DllImport("kernel32.dll")]
    public static extern Boolean AllocConsole();

    /// <summary>
    /// Frees the console.
    /// </summary>
    [DllImport("kernel32.dll")]
    public static extern Boolean FreeConsole();
}

调用 AllocConsole 来创建一个控制台,然后您可以写入(和读取)它。完成后调用 FreeConsole。

【讨论】:

  • 感谢您的提示。我试过了。如果调用 FreeConsole() 然后 AllocConsole(),任何现有的 ReadLine() 调用都会抛出异常“尝试读取或写入受保护的内存。这通常表明其他内存已损坏。”。 C# 中的 try/catch 无法捕获此异常。它还有一个不好的副作用:它会丢失控制台中的任何历史记录,因为它有效地启动了一个新的控制台。不知道有没有别的方法?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多