为什么关闭以AllocConsole启动的控制台会导致我的整个应用程序退出?我可以改变这种行为吗?

时间:2012-08-14 19:43:26

标签: c# .net winapi console console-application

我想要发生的事情是控制台窗口消失了,或者更好的是它被隐藏了,但我希望我的应用程序继续运行。那可能吗?我希望能够使用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并不会阻止应用程序终止。

5 个答案:

答案 0 :(得分:27)

啊,是的,这是使用Windows控制台子系统的一个警告。当用户关闭控制台窗口时(无论控制台的分配方式如何),连接到控制台的所有进程都将终止。这种行为对于控制台应用程序(即那些专门针对控制台子系统的应用程序,而不是标准的Windows应用程序)显而易见,但在像你这样的情况下,这可能是一个很大的痛苦。

我所知道的唯一解决方法是使用SetConsoleCtrlHandler function,它允许您为 Ctrl + C 注册处理函数Ctrl + 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 + C Ctrl + Break 信号(否则会导致你的应用程序也可以终止),但是当用户关闭控制台窗口时,系统会生成CTRL_CLOSE_EVENT,这不适用于您所询问的那个。

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

也许更令人担忧的是,文档让我觉得不可能阻止这个:

  

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

这是引起我注意的最后一句话。如果控制台子系统在立即>之后立即开始清理以响应用户试图关闭窗口,则事后可能无法将其停止。

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

答案 1 :(得分:23)

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

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

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

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

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

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

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

答案 2 :(得分:10)

关闭使用AllocConsoleAttachConsole获得的控制台窗口时,关联的进程将退出。没有逃避的可能性。

在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();
            }
        }
    }
} 

然后,不是将控制台分配/附加到当前应用程序,而是可以从应用程序内启动echo服务器,并且可以重定向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;
    }
}

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

答案 3 :(得分:2)

我想在控制台中显示IronPython脚本的输出,我希望从我的程序中控制它。所以我这样做的方式与此处提出的相似 - 但如果有多个脚本,我需要多个控制台窗口并根据 http://msdn.microsoft.com/en-us/library/windows/desktop/ms681944(v=vs.85).aspx Windows程序不能有多个shell(羞耻你,微软!)。

这是交易破坏者。我不得不忽略解决方案,现在我将IronPython的输出重定向到一个模拟shell的自编写窗口类。看到 http://www.codeproject.com/Articles/335909/Embedding-a-Console-in-a-C-Application

答案 4 :(得分:-2)

使用FreeConsole而不是AllocConsole

定义..

delimiters.Select(m => Regex.Escape(m))

用法..

     [DllImport("kernel32.dll", SetLastError = true,ExactSpelling = true)]
     static extern bool FreeConsole();