多次创建后WinApi窗口异常破坏

时间:2018-04-05 07:50:14

标签: c++ winapi

我正在重构通过移动桌面快照的位获得的屏幕效果的 C 实现。

该代码的灵感源自此视频:Screen Melting Effect

我已将代码包装在名为 ScreenMelter 的单例类中:

class ScreenMelter
{
private:
    HWND hWnd;
    static unsigned int TimerID;

    static unsigned int nScreenWidth;
    static unsigned int nScreenHeight;
protected:

    bool InitClass()
    {
        WNDCLASS wndClass = { 0, MelterProc, 0, 0, GetModuleHandle(NULL), NULL, LoadCursor(NULL, IDC_ARROW), 0, NULL, L"Melter" };

        if (!GetClassInfo(GetModuleHandle(NULL), L"Melter", &wndClass))
        {
            if (!RegisterClass(&wndClass))
            {
                MessageBox(NULL, L"Cannot register class!", NULL, MB_ICONERROR | MB_OK);
                return false;
            }
        }

        return true;
    }

    bool InitWindow()
    {
        nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
        nScreenHeight = GetSystemMetrics(SM_CYSCREEN);

        hWnd = CreateWindow(L"Melter", NULL, WS_POPUP, 0, 0, nScreenWidth, nScreenHeight, HWND_DESKTOP, NULL, GetModuleHandle(NULL), NULL);

        if (!hWnd)
        {
            MessageBox(NULL, L"Cannot create window!", NULL, MB_ICONERROR | MB_OK);
            return false;
        }

        return true;
    }

    void FreeWindow()
    {
        if (hWnd)
            DestroyWindow(hWnd);
    }

    void FreeClass()
    {
        UnregisterClass(L"Melter", GetModuleHandle(NULL));
    }

    static LRESULT WINAPI MelterProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
    {
        switch (Msg)
        {
        case WM_CREATE:
        {
            std::cout << "I got created! \n";

            HDC hdcWindow = GetDC(hWnd);
            HDC hdcDesktop = GetDC(HWND_DESKTOP);

            BitBlt(hdcWindow, 0, 0, nScreenWidth, nScreenHeight, hdcDesktop, 0, 0, SRCCOPY);

            ReleaseDC(hWnd, hdcWindow);
            ReleaseDC(HWND_DESKTOP, hdcDesktop);

            TimerID = SetTimer(hWnd, 0,
                1,      // Speed of the timer
                NULL);

            ShowWindow(hWnd, SW_SHOW);
            break;
        }
        case WM_ERASEBKGND:
            break;
        case WM_PAINT:
            ValidateRect(hWnd, NULL);
            break;
        case WM_TIMER:
        {
            HDC hdcWindow = GetDC(hWnd);

            int nWidth = (rand() % 150);

            int nYPos = (rand() % 15);
            int nXPos = (rand() % nScreenWidth) - (150 / 2);

            BitBlt(hdcWindow, nXPos, nYPos, nWidth, nScreenHeight, hdcWindow, nXPos, 0, SRCCOPY);

            ReleaseDC(hWnd, hdcWindow);
            break;
        }
        case WM_KEYDOWN:
        {
            if (wParam != VK_ESCAPE)
                break;
        }
        case WM_CLOSE:
        case WM_DESTROY:
        {
            // It kills the timer, but I wonder, why that doens't responde after one call
            KillTimer(hWnd, TimerID);

            TimerID = NULL;
            PostQuitMessage(0);
            std::cout << "-> I got destroyed!\n";
            break;
        }
        default:
            return DefWindowProc(hWnd, Msg, wParam, lParam);
        }

        return 0;
    }

public:

    void StartMelting(int32_t duration)
    {
        InitClass();
        InitWindow();

        MSG Msg = { 0 };

        auto Start = std::chrono::steady_clock::now();

        while (Msg.message != WM_QUIT)
        {
            if (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
            {
                TranslateMessage(&Msg);
                DispatchMessage(&Msg);
                std::cout << "Msg ";
            }

            auto End = std::chrono::steady_clock::now();

            if (std::chrono::duration_cast<std::chrono::seconds>(End - Start).count() >= duration) break;
        }

        FreeWindow();
        FreeClass();
    }

protected:
    ScreenMelter()
    { }
public:
    ScreenMelter(const ScreenMelter&) = delete;
    ScreenMelter& operator=(const ScreenMelter&) = delete;

    static ScreenMelter& GetInstance()
    {
        static ScreenMelter melter;
        return melter;
    }

    ~ScreenMelter()
    {
        FreeWindow();
        FreeClass();
    }
};

该类可以正常工作,但仅限于第一次创建窗口。经过一些调试后,我发现在第一次调用 StartMelting(秒)后,窗口会被核心创建,但是在收到关闭它的WM_DESTROY/WM_CLOSE消息后不久。

以下代码显示了这一点:

int main() 
{
    ScreenMelter& melter = ScreenMelter::GetInstance();

    int input;
    int32_t TimeInSeconds(2);
    while (1)
    {
        std::cin >> input;

        if (input == 0)
            break;

        melter.StartMelting(TimeInSeconds);
    }
}

在盯着屏幕看了将近一个小时之后,我决定我需要一些帮助。

问题:

  • 是什么原因导致窗口在第一个窗口之后收到关闭消息 创建窗口?资源在核心之后被释放 计时器到期,或按下ESC键!

编辑1:

为了使我的问题更清楚,这是应用程序的输出:

// First call
1
I got created!
Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg
Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg
Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg
Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg
Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg
Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg
Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg Msg -> I got destroyed!
// Second call
1
I got created!
Msg -> I got destroyed!  
// Third call
1
I got created!
Msg -> I got destroyed!

1 个答案:

答案 0 :(得分:2)

处理WM_DESTROY消息时,使用PostQuitMessage。不。

稍后处理WM_QUIT消息,第二个HWND已经创建,但由于您在StartMelting中退出循环而立即销毁。

您的代码存在许多问题。 只有一个,信息:UnregisterClass在窗口还活着时失败。