优雅地关闭控制台窗口

8
我正在尝试在控制台应用程序中优雅地退出,当关闭按钮被点击时。
bool done = false;

BOOL ctrl_handler(DWORD event)
{
    if (event == CTRL_CLOSE_EVENT) {
        done = true;
        return TRUE;
    }
    return FALSE;
}

int main()
{
    SetConsoleCtrlHandler((PHANDLER_ROUTINE)(ctrl_handler), TRUE);

    while (!done)
        do_stuff();

    cleanup();
    cout << "bye";
}

我的处理程序被正确调用,但主线程之后没有恢复,所以“bye”从未出现。如果我捕获CTRL_C_EVENT并在控制台上按^C,则允许主线程继续并平稳退出。是否有一些方法可以在控制台关闭时允许主线程平稳退出?
我还尝试使用std::signal,但效果相同。对于^C有效,但对于窗口关闭无效。

2
一旦您的事件处理程序返回,进程就会终止,您无法覆盖关闭操作。因此,您需要在事件处理程序本身中进行任何清理工作。 - Jonathan Potter
2个回答

8

感谢Jonathan的提示。

我看到处理程序在它自己的线程上被调用。如果处理程序返回,则整个进程将被强制退出,因此我们需要给主线程时间让它自己退出。

bool done = false;

BOOL ctrl_handler(DWORD event)
{
    if (event == CTRL_CLOSE_EVENT) {
        done = true;
        Sleep(20000); // force exit after 20 seconds
        return TRUE;
    }
    return FALSE;
}

int main()
{
    SetConsoleCtrlHandler((PHANDLER_ROUTINE)(ctrl_handler), TRUE);

    while (!done)
        do_stuff();

    cleanup();
    cout << "bye";
}

尝试并测试了tukra的答案,它非常有效。我的程序在Windows上正确终止,并且在关闭控制台时不再出现段错误。令人荒谬的是,SDL默认情况下不处理这个问题,并将其转换为类似于终止信号的SDL_QUIT。无论如何,感谢您提供的解决方案。 - Kenji
如果你必须使用Seep,那么解决方案可能不太好。 - Steve
1
这会引入潜在的竞态条件。你说过你想要干净地退出。CRT在main返回后会执行一些操作,比如调用全局析构函数、atexit处理程序、刷新缓冲区等等。 - Steve
我更新了代码,使其更接近我最终得到的结果。 - tukra

3

我认为更好的方法是使用:

if (event == CTRL_CLOSE_EVENT)
{
    ExitThread(0);
//  return TRUE; // warning C4702: unreachable code
}
< p > ExitThread 将终止调用 ctrl_handler 的线程。这样,ExitProcess 不会被调用,您的主线程将能够执行所有必要的清理工作。< /p >

这是一个不错的选择。我猜这取决于你想要实现什么。我回顾了一下与这个问题相关的真实代码,它会睡眠5秒钟然后返回,所以即使优雅退出不起作用,进程仍将终止。 - tukra
这个选项有些不确定。请记住,附加到控制台的每个进程都会创建一个新线程来调用它们的处理程序:“系统在进程中创建一个新线程来执行函数”。正是“系统”在协调所有这些,并且退出或终止您进程中的线程并不会阻止“系统”处理事件。那么问题就变成了:在线程得到答案之前,如果线程突然消失,“系统”如何处理?我没有看到描述这种行为的地方。甚至在不同版本之间可能也不稳定。 - Steve
我尝试了你的解决方案,它有点奏效:我添加了 "SetConsoleCtrlHandler(ctrl_handler, FALSE);" 来注销函数,并且在代码中加入这个后,如果我们强制退出处理程序线程,它会挂起(我猜测它正在等待处理程序线程完成,但因为我们杀死了它,它无法注册完成),所以系统不会预料到这种情况。可能还有更多注意事项。(在 Windows 10 的 x64 版本上运行的 x86 代码)。 - Steve
Steve,使用“ExitThread”是一种“hack”。当然可能会有一些细微差别。逻辑上,“SetConsoleCtrlHandler”与FALSE不再起作用,因为在调用“ExitThread”后处理程序线程已经死亡。(该解决方案已在x64和x86项目、Windows 10上进行了测试,没有任何问题。) - Gediminas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接