Search code examples
cwindowswinapigdiwindows-messages

How to gracefully close this simple Windows GUI program? Because it doesn't (sometimes)


I'm having an issue with my Windows application where upon closing it from the taskbar or via hotkey it will occasionally hang. I'm wondering how to gracefully exit the following program:

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK OwnedWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

static HWND mainHwnd;
static HWND ownedHwnd;

void create_windows()
{
    HMODULE thisMod = GetModuleHandleA(NULL);

    WNDCLASSA wc;
    wc.style         = CS_VREDRAW | CS_HREDRAW;
    wc.lpfnWndProc   = MainWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = thisMod; 
    wc.hIcon         = 0;
    wc.hCursor       = 0;
    wc.hbrBackground = 0;
    wc.lpszMenuName  = NULL; 
    wc.lpszClassName = "MAINWIN";

    RegisterClassA(&wc);

    wc.lpfnWndProc   = OwnedWndProc;
    wc.lpszClassName = "OWNEDWIN";

    RegisterClassA(&wc);

    mainHwnd = CreateWindowExA(WS_EX_TOPMOST, "MAINWIN", "MAINWIN", WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), 0, 0, thisMod, NULL);

    ShowWindow(mainHwnd, SW_SHOWNORMAL);

    ownedHwnd = CreateWindowExA(WS_EX_LAYERED | WS_EX_TOPMOST, "OWNEDWIN", "OWNEDWIN", WS_POPUP, 0, 0, 200, 200, mainHwnd, 0, thisMod, NULL);

    ShowWindow(ownedHwnd, SW_SHOWNORMAL);
}


int main(int argc, char **argv)
{
    if (!RegisterHotKey(NULL, 1, MOD_NOREPEAT, VK_ESCAPE)) {
        return 0;
    }

    create_windows();

    BOOL bRet;
    MSG  msg;

    while((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
        if (bRet == -1) {
            /* I'm never reached */
        } else if (msg.message == WM_HOTKEY) {
            UnregisterHotKey(NULL, 1);
            PostMessageA(mainHwnd, WM_CLOSE, 0, 0);
        } else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    /* Do a bit of cleanup */

    return 0;
}

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static BOOL condition = FALSE;

    switch (uMsg) {
        case WM_CREATE:
            SetTimer(hwnd, 1, 20, NULL);
            return 0;
        case WM_TIMER:
            if (condition) {
                KillTimer(hwnd, 1);
                PostMessageA(ownedHwnd, WM_CLOSE, 0, 0);
            } else {
                /* Do processing here on both windows. The condition variable is
                   updated in here after the program does its thing. */
            }
            return 0;
        case WM_CLOSE:
            DestroyWindow(hwnd);
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }

    return DefWindowProcA(hwnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK OwnedWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    /* Letting DefWindowProcA handle everything since I don't need this window to
       do anything but close afterwards. */
    return DefWindowProcA(hwnd, uMsg, wParam, lParam);
}

When it does hang, it always seems to occur after the timer has already been disabled and the owned window has been closed. Never before. Whether in console mode or windows mode its always the same, always after those two things happen and I try to close the window.

With printf statements (because I'm not entirely sure how to debug this) I've noticed that when it freezes WM_CLOSE and subsequently WM_DESTROY are never reached in MainWndProc, as if it's stuck somewhere deep in GetMessage or DispatchMessage, or my message loop, I'm not doing anything fancy in this program so I have no clue. When I manage to make this happen in the debugger it ends up still running but I'm not able to pause it and step to see where where it is executing.

Strangely, though not anymore I've observed, when I would close it in console mode the window would disappear but the process would continue to run in the background until the cmd window from which I launched the program received keyboard input or closes. Conversely in windows mode the same would happen but there'd be no cmd window, instead having to end it from the task manager.

I've never had any trouble with simple Windows GUI applications where only one window is needed. It's only when there are more that I run into this problem of it never fully closing and not knowing how to gracefully exit.


Solution

  • Welp it turns out using WinMain, instead of main, and not specifying it as the entry point (thus the C run-time library is initialized correctly) solved it. And that's all. I still cannot fully wrap my head around it.

    Before I discovered this I noticed in the debugger that when the program got out of the message loop and exited, there were a number of threads still running. What's odd is I never created any threads...

    I don't know what led me to try WinMain unfortunately as I'd been wildly experimenting with different configuration at that point in console and windows mode until I arrived at the one that worked.