C++ Windows:如何关闭控制台窗口?

8
我使用"AllocConsole()"打开了一个控制台窗口,除了主Win32窗口之外。控制台窗口在主窗口之前打开。
当我关闭主窗口并从主函数返回时,控制台仍然保持打开状态(进程也是如此)。它实际上会在"ntdll"中卡住,正如从Visual Studio 2012暂停程序时调试器所显示的那样。
通过单击"X"按钮关闭它可以退出进程,但通过"FreeConsole()"关闭它则不行,进程仍然在无窗口工作。
"CloseWindow(GetConsoleWindow())"无法关闭它,它只会将其最小化(!?!)
"PostMessage(GetConsoleWindow(),WM_QUIT,0,0)"会关闭窗口,但进程仍然在工作(从视觉上来看,这与"FreeConsole()"相同)。
偶尔情况下,程序以正确的方式退出,但是每次单击"X"按钮都有效。
我应该如何实现与单击"X"按钮相同的操作?

当你从主函数返回并退出主线程(以及其他线程)时,你不必关闭控制台窗口,它应该自行消失。如果你没有做什么危险的事情,那么控制台窗口就不会导致程序停滞。你的程序可能即使没有控制台窗口也会挂起,但首先让我们尝试在不分配控制台的情况下进行一些操作。在ntdll中卡住并不一定意味着你的控制台窗口有责任,这可能是应用程序的GUI/逻辑部分之前所做的某些事情的最终结果。 - pasztorpisti
据我所知,每个进程只能有一个控制台窗口,但我可能是错的。 - pasztorpisti
1
@Vladivarius CloseWindow() 应该是用于最小化窗口的(请查看文档),它是一个来自黑暗时代的古老函数,而且它的命名非常糟糕/令人困惑。如果你想模拟按下窗口的 X 按钮,则需要向窗口发送 WM_CLOSE 消息。 - pasztorpisti
所以,您有带有附加控制台的 Windows 应用程序?也许您可以通过将应用程序类型更改为 SUBSYSTEM:Console 来获得相同的效果。在这种情况下,您不需要使用 AllocConsole。 - Alex F
@pasztorpisti,请将您关于WM_CLOSE的评论发布为答案(或者更好的是两个评论),它起作用了。那正是我想做的,只是混淆了并使用了WM_QUIT -.- 此外,程序的其余部分可能存在问题,因为nvoglv32.dll线程仍在运行,并且调用堆栈中充满了对它和ntdll的调用。一定是一个非常讨厌的OpenGL泄漏 :/ - Vladivarius
显示剩余2条评论
3个回答

7
使用PostMessage(wnd, WM_CLOSE, 0, 0)关闭控制台窗口,但如果这只是一个临时解决方法,那么问题可能出现在程序的其他地方。当你从main()WinMain()返回时,控制台窗口应该自动关闭/消失。

@Vladivarius 谢谢您! :-) - pasztorpisti
我认为值得注意的是,可以使用“wnd = GetConsoleWindow()”获取正在运行的控制台的“HANDLE wnd”。效果很好,谢谢! - Kirill Starostin

4

你需要在 WM_DESTROY 消息中使用 FreeConsole 销毁控制台。通常我会在一个包装了我的控制台的类中完成这个操作。这样可以在构造函数中将输入/输出重定向到控制台,在析构函数中重置输入/输出以及分别分配/销毁控制台。

然而,如果不使用类或任何包装器,可以按照以下方式完成。。。

示例:

#include <windows.h>
#include <streambuf>
#include <fstream>
#include <iostream>

std::streambuf *CinBuffer, *CoutBuffer, *CerrBuffer;
std::fstream ConsoleInput, ConsoleOutput, ConsoleError;

void RedirectIO()
{
    CinBuffer = std::cin.rdbuf();
    CoutBuffer = std::cout.rdbuf();
    CerrBuffer = std::cerr.rdbuf();
    ConsoleInput.open("CONIN$", std::ios::in);
    ConsoleOutput.open("CONOUT$", std::ios::out);
    ConsoleError.open("CONOUT$", std::ios::out);
    std::cin.rdbuf(ConsoleInput.rdbuf());
    std::cout.rdbuf(ConsoleOutput.rdbuf());
    std::cerr.rdbuf(ConsoleError.rdbuf());
}

void ResetIO()
{
    ConsoleInput.close();
    ConsoleOutput.close();
    ConsoleError.close();
    std::cin.rdbuf(CinBuffer);
    std::cout.rdbuf(CoutBuffer);
    std::cerr.rdbuf(CerrBuffer);
    CinBuffer = NULL;
    CoutBuffer = NULL;
    CerrBuffer = NULL;
}

LRESULT __stdcall WindowProcedure(HWND Hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    switch(Msg)
    {
        case WM_CREATE:
            AllocConsole();
            RedirectIO();
            std::cout<<"HELLO CONSOLE!"<<std::endl;
            break;

        case WM_DESTROY:
            std::cout<<"BYE-BYE CONSOLE!"<<std::endl;
            ResetIO();
            FreeConsole();
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(Hwnd, Msg, wParam, lParam);
    }
    return 0;
};

int main()
{
    WNDCLASSEX WndClass =
    {
        sizeof(WNDCLASSEX), CS_DBLCLKS, WindowProcedure,
        0, 0, GetModuleHandle(NULL), LoadIcon(NULL, IDI_APPLICATION),
        LoadCursor(NULL, IDC_ARROW), HBRUSH(COLOR_WINDOW + 1),
        NULL, "WindowClass", LoadIcon (NULL, IDI_APPLICATION)
    };

    if(RegisterClassEx(&WndClass))
    {
        HWND WindowHandle = CreateWindowEx(0, "WindowClass", "Window Title", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 500, NULL, NULL, GetModuleHandle(NULL), NULL);
        if(WindowHandle)
        {
            MSG msg = {NULL};
            ShowWindow(WindowHandle, SW_SHOWDEFAULT);
            while(GetMessage(&msg, NULL, 0, 0))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    }
}

这正是我正在做的事情(关于WM_DESTROY的想法),但它没有退出进程 :/ 看起来问题出在我设置控制台的方式上,现在当我从你那里复制时它可以工作了。事实上,我很惊讶我的代码似乎在从Stackoverflow以外的其他地方复制粘贴后就可以工作了 xD - Vladivarius

-1
#include <windows.h>

HWND myConsole = GetConsoleWindow(); //window handle
ShowWindow(myConsole, 0); //handle window

我最终采用的解决方案是将链接器(Linker) -> 系统(System) -> 子系统(SubSystem)设置为“Windows (/SUBSYSTEM:WINDOWS)”,而不是“Console (/SUBSYSTEM:CONSOLE)”。这样可以避免控制台出现,从而避免闪烁。第二个解决方案对我来说更好,它使第一个解决方案变得过时。

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