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

Kel*_*sie 27 .net c# console winapi console-application

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

编辑

码:

internal class SomeClass {

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

    private static void Main() {
        AllocConsole();
        while(true) continue;
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑2

我根据这个问题的评论中的建议尝试了这里接受的解决方案[ Capture console exit C# ].示例代码的错误在于DLLImport需要是"kernel32.dll"或"kernel32",而不是"Kernel32".进行更改后,当我单击控制台窗口上的X时,我正在向CTRL_CLOSE_EVENT的处理程序发送消息.但是,调用FreeConsole和/或返回true并不会阻止应用程序终止.

Cod*_*ray 27

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

我所知道的唯一解决方法是使用该SetConsoleCtrlHandler函数,它允许您为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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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


Jon*_*ter 24

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

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

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

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

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

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

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


Ami*_*tal 10

在关闭使用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();
            }
        }
    }
} 
Run Code Online (Sandbox Code Playgroud)

然后,不是将控制台分配/附加到当前应用程序,而是可以从应用程序内启动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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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