【问题标题】:WinApi Window abnormal destruction after multiple creationsWinApi Window多次创建后异常销毁
【发布时间】:2018-09-14 22:55:51
【问题描述】:

我正在重构屏幕效果的 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(seconds) 后,窗口会正确创建,但在收到 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!

【问题讨论】:

  • 仅供参考,即使 处理完消息后,您仍在返回 DefWindowProc 调用。检查您的文件;这是不对的。通常只有在处理消息时才调用 DefWindowProc。
  • 请出示minimal reproducible example,不要只是转储代码。
  • 我已将交换机中的 breaks 替换为 return 0;(表示消息已处理的信号),但我仍然遇到同样的问题:程序正常工作仅在第一次初始化时
  • @Lundin 在构造函数中用false初始化!
  • 您在StartMelting 的底部调用FreeClass,这会取消注册该类,但您从未将init 重置为false,因此下次创建窗口将失败,因为该类不是t 注册。

标签: c++ winapi


【解决方案1】:

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

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

您的代码存在许多问题。 只有一个,供参考:UnregisterClass 在窗口仍然存在时失败。

【讨论】:

  • 是的,只有在成功销毁窗口后,您才需要取消注册类!我可以用什么来代替 PostQuitMessage
  • 只需删除该行。工作正常,在这里。
  • 解释不太正确。 WM_QUIT 在创建下一个窗口实例之前按顺序处理。但是,WM_QUIT 消息不是真正的消息。它更像是一个标志,一旦调用PostQuitMessage,该标志就会保持设置状态。在下一个循环中,窗口创建得很好,但WM_QUIT 消息仍然存在,因此它立即被拆除。有关详细信息,请参阅Why is there a special PostQuitMessage function?
  • @IInspectable 我不明白你所说的processed in order, before the next window instance is created 是什么意思。我在代码中添加了cout &lt;&lt; "msg " &lt;&lt; Msg.message;,并看到 WM_QUIT 在下一次创建后达到峰值。通过处理,我的意思是“通过 OP 的代码”
  • 事件的顺序是:窗口类注册-> 窗口创建-> [ESC] 按下-> PostQuitMessage 被调用-> 窗口被销毁-> 窗口类被注销。然后代码循环回到开始:Windows 类已注册-> 窗口已创建-> WM_QUIT 已处理(因为它仍然存在)-> 窗口已销毁-> 窗口类未注册。一切都按顺序发生。正如您的回答所暗示的那样,“错误”窗口实例不会处理任何消息(“稍后处理 WM_QUIT 消息”)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-18
相关资源
最近更新 更多