31

我想要发生的是控制台窗口消失了,或者更好的是它被隐藏了,但我希望我的应用程序继续运行。那可能吗?我希望能够使用 Console.WriteLine 并将控制台用作输出窗口。我希望能够隐藏和显示它,我不希望整个应用程序仅仅因为控制台关闭而死掉。

编辑

代码:

internal class SomeClass {

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

    private static void Main() {
        AllocConsole();
        while(true) continue;
    }
}

编辑 2

根据对此问题的评论中的建议,我在这里尝试了接受的解决方案 [ Capture console exit C# ]。示例代码存在错误,因为 DLLImport 需要是“kernel32.dll”或“kernel32”,而不是“Kernel32”。进行该更改后,当我单击控制台窗口上的 X 时,我会收到一条消息给我的 CTRL_CLOSE_EVENT 处理程序。但是,调用 FreeConsole 和/或返回 true 不会阻止应用程序终止。

4

4 回答 4

30

啊,是的,这是使用 Windows 控制台子系统的注意事项之一。当用户关闭控制台窗口时(不管控制台是如何分配的),所有连接到控制台的进程都会终止。这种行为对于控制台应用程序(即那些专门针对控制台子系统的应用程序,而不是标准的 Windows 应用程序)来说是很有意义的,但在像您这样的情况下它可能是一个主要的痛苦。

我知道的唯一解决方法是使用SetConsoleCtrlHandlerfunction,它允许您为Ctrl+CCtrl+Break信号以及系统事件(如用户关闭控制台窗口、用户注销或系统关闭)注册处理函数. 文档说,如果您只想忽略这些事件,则可以传递null第一个参数。例如:

[DllImport("kernel32")]
static extern bool SetConsoleCtrlHandler(HandlerRoutine HandlerRoutine, bool Add);

delegate bool HandlerRoutine(uint dwControlType);

static void Main()
{
    AllocConsole();
    SetConsoleCtrlHandler(null, true);
    while (true) continue;
}

这对于Ctrl+CCtrl+Break信号非常有效(否则也会导致您的应用程序终止),但它不适用于您要询问的那个CTRL_CLOSE_EVENT,即用户关闭时系统生成的控制台窗口。

老实说,我不知道如何防止这种情况。即使SDK 中的示例实际上也不允许您忽略CTRL_CLOSE_EVENT. 我在一个小测试应用程序中尝试了它,当您关闭窗口并打印消息时它会发出哔哔声,但该过程仍然会终止。

也许更令人担忧的是,文档让我认为不可能阻止这种情况:

当用户关闭控制台、注销或关闭系统时,系统会生成CTRL_CLOSE_EVENTCTRL_LOGOFF_EVENT和信号,以便进程在终止之前有机会进行清理。CTRL_SHUTDOWN_EVENT控制台函数,或调用控制台函数的任何 C 运行时函数,在处理前面提到的三个信号中的任何一个时,可能无法可靠地工作。原因是在执行进程信号处理程序之前,可能已经调用了部分或全部内部控制台清理例程。

吸引我眼球的是最后一句话。如果控制台子系统在响应用户尝试关闭窗口后立即开始清理,则可能无法在事后停止它。

(至少现在你明白了这个问题。也许其他人可以提出解决方案!)

于 2012-08-14T20:33:40.717 回答
25

不幸的是,您无法真正改变这种行为。

控制台窗口是“特殊的”,因为它们由另一个进程托管并且不允许子类化。这限制了你修改他们行为的能力。

据我所知,您的两个选择是:

1.完全禁用关闭按钮。您可以使用以下代码片段执行此操作:

HWND hwnd = ::GetConsoleWindow();
如果 (hwnd != NULL)
{
   HMENU hMenu = ::GetSystemMenu(hwnd, FALSE);
   if (hMenu != NULL) DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
}

2. 完全停止使用控制台,实现自己的文本输出解决方案。

选项 #2 是更复杂的选项,但会为您提供最大的控制。我在CodeProject上找到了一篇文章,它使用富编辑控件来实现类似控制台的应用程序来显示文本(富编辑控件能够像控制台一样流式传输文本,因此它们非常适合此类应用程序)。

于 2012-08-18T01:41:38.900 回答
11

关闭使用AllocConsole或获得的控制台窗口AttachConsole时,相关进程将退出。没有办法逃脱。

在 Windows Vista 之前,关闭控制台窗口会向用户显示一个确认对话框,询问他是否应该终止进程,但 Windows Vista 和更高版本不提供任何此类对话并且进程被终止。

解决此问题的一种可能解决方案是完全避免 AttachConsole 并通过其他方式实现所需的功能。

例如,在 OP 描述的情况下,需要控制台窗口才能使用Console静态类在控制台上输出一些文本。

这可以很容易地使用进程间通信来实现。例如,可以开发控制台应用程序来充当回显服务器

namespace EchoServer
{
    public class PipeServer
    {
        public static void Main()
        {
            var pipeServer = new NamedPipeServerStream(@"Com.MyDomain.EchoServer.PipeServer", PipeDirection.In);
            pipeServer.WaitForConnection();

            StreamReader reader = new StreamReader(pipeServer);

            try
            {
                int i = 0;
                while (i >= 0)
                {
                    i = reader.Read();
                    if (i >= 0)
                    {
                        Console.Write(Convert.ToChar(i));
                    }
                }
            }
            catch (IOException)
            {
                //error handling code here
            }
            finally
            {
                pipeServer.Close();
            }
        }
    }
} 

然后不是将控制台分配/附加到当前应用程序,而是可以从应用程序内部启动回显服务器,并且Console's可以重定向输出流以写入管道服务器。

class Program
{
    private static NamedPipeClientStream _pipeClient;

    static void Main(string[] args)
    {
        //Current application is a Win32 application without any console window
        var processStartInfo = new ProcessStartInfo("echoserver.exe");

        Process serverProcess = new Process {StartInfo = processStartInfo};
        serverProcess.Start();

        _pipeClient = new NamedPipeClientStream(".", @"Com.MyDomain.EchoServer.PipeServer", PipeDirection.Out, PipeOptions.None);
        _pipeClient.Connect();
        StreamWriter writer = new StreamWriter(_pipeClient) {AutoFlush = true};
        Console.SetOut(writer);

        Console.WriteLine("Testing");

        //Do rest of the work. 
        //Also detect that the server has terminated (serverProcess.HasExited) and then close the _pipeClient
        //Also remember to terminate the server process when current process exits, serverProcess.Kill();
        while (true)
            continue;
    }
}

这只是可能的解决方案之一。本质上,解决方法是将控制台窗口分配给它自己的进程,以便它可以在不影响父进程的情况下终止。

于 2012-08-22T07:43:02.220 回答
-1

您可以通过名为 Keyfreez 的外部程序禁用键盘鼠标输入来做到这一点。

您可以在不需要用户输入的程序中多次使用它。如果任何用户输入需要你可以添加一个进程 Takskkill /f /IM 。

https://www.sordum.org/7921/bluelife-keyfreeze-v1-4-block-keyboard-and-mouse/

希望这对大家有帮助

于 2022-02-18T19:48:20.523 回答