C++ COM、Direct2D、Win32 和 WM_CLOSE

Posted

技术标签:

【中文标题】C++ COM、Direct2D、Win32 和 WM_CLOSE【英文标题】:C++ COM, Direct2D, Win32 and WM_CLOSE 【发布时间】:2013-07-01 14:57:25 【问题描述】:

我在一系列情况下遇到了非常具体的清理崩溃,我完全不知道发生了什么,所以我只描述条件/程序以及它在做什么。

我有一个 win32 程序,使用 Direct2D 在黑屏上绘制测试字符串和测试位图。这一切都有效。没有崩溃,根本没有内存泄漏。但是,只有当我通过以下方式退出程序时:

case WM_KEYDOWN:
    if (wParam == VK_ESCAPE)
        PostQuitMessage(0); // close program
    break;

这反过来导致我的消息循环结束,调用我的清理代码:

Application app(hWnd);

// message loop
while(true)

    // Check to see if any messages are waiting in the queue
    while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    
        // Translate the message and dispatch it to WindowProc()
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    

    // If the message is WM_QUIT, exit the while loop
    if(msg.message == WM_QUIT)
        break;

    // runtime code
    app.Update();

    // continue...
    Sleep(FRAME_DELAY);


// cleanup code
app.Delete();

这行得通。如果我在调用 WM_QUIT 的窗口上单击红色“X”,我没有处理(并且不应该有理由),这不起作用。发生这种情况时,我的消息循环仍然结束,我的应用程序调用 Delete(),但它在释放 D2D 对象时特别崩溃。顺序无关紧要,它似乎不依赖于任何其他现有或正在发布/未发布的内容。即使我不加载我的图像,我的 IWICImagingFactory* 也无法在不崩溃的情况下释放...

D2D::~D2D()

// release all bitmaps
for (list<tSprite>::iterator iter = m_vSprites.begin();iter != m_vSprites.end();++iter)

    SafeRelease(&(iter->m_pImage));


SafeRelease(&m_pWICFactory);// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<THIS LINE
SafeRelease(&m_pText);
SafeRelease(&m_pDWFactory);
SafeRelease(&m_pBrush);
SafeRelease(&m_pD2D);
SafeRelease(&m_pD2DFactory);

最后我添加了一些代码,偶然地修复了崩溃,但我想知道为什么以及这样做是否正确。我将应用程序的生命封装到 COM 初始化/单元调用中:

   // init code
CoInitializeEx(0,COINIT_DISABLE_OLE1DDE);//<<<<<<<<<<<<<<
Application app(hWnd);

// message loop
while(true)
...
// cleanup code
app.Delete();
CoUninitialize();//<<<<<<<<<<<<<

综上所述,即使没有COM代码,程序也可以正常运行并正常退出,除非我点击红色的“X”,在这种情况下只有D2D WIC映像工厂无法发布。

类似的问题(只有我的只有通过 WM_CLOSE 清理时才有问题?): Calling Inherited IUnknown::Release() in a destructor

【问题讨论】:

这不仅是正确的事情,而且是必需的事情。 WIC 需要初始化 COM。唯一的困惑是为什么程序在没有它的情况下运行。让我们不要担心。 【参考方案1】:

单击“红色 X”不会生成 WM_QUIT,它会生成 WM_CLOSE。如果您的消息循环没有处理该消息并将其传递给DefWindowProc,那么您的主窗口将被销毁,然后生成WM_DESTROY

这是窗口破坏的“正常”流程 - WM_CLOSEWM_DESTROYPostQuitMessage,消息循环结束。

相反,通过自己发布WM_QUIT,您将立即跳出消息循环,使您的窗口和其他所有内容保持不变。

这本身并没有什么问题,只要您编写了代码来处理它,但是通过允许DefWindowProc 处理正常的窗口破坏以及您正在创建一个您不知道您的窗口是否存在或不是当您退出消息循环时,我怀疑是第二种情况导致了您的问题。

我建议更改按 Escape 时发生的情况 - 向窗口发布 WM_CLOSE 消息,然后对该消息和 WM_DESTROY 进行处理,以便始终以相同的受控方式退出消息循环。

此外,您的消息循环结构不正确,如果它恰好是 PeekMessage 循环检索到的最后一条消息,当前只会看到 WM_QUIT - 如果不是,您将完全错过它。你可能想要这样的东西:

bool fGotQuit = false;
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

    // If the message is WM_QUIT, exit the while loop
    if (fGotQuit = (msg.message == WM_QUIT))
        break;
    // Translate the message and dispatch it to WindowProc()
    TranslateMessage(&msg);
    DispatchMessage(&msg);


// If the message is WM_QUIT, exit the while loop
if(fGotQuit)
    break;

【讨论】:

非常感谢。听从您的建议后,这似乎是我对 windows 消息的理解(主要是我不理解 QUIT、CLOSE 和 DESTROY 的关系/顺序)而不是 COM 的问题。这正是您所说的, CLOSE 导致了 DestroyWindow() (默认情况下),导致窗口在我的 D2D 清理代码之前被破坏。我通过允许 WM_CLOSE 案例落入 WndProc 开关中的 WM_DESTROY 来修复它。额外感谢有关消息循环错误的说明。 为了进一步说明,我或者通过不处理 WM_DESTROY 来修复它,而是将我的清理代码放在 DestroyWindow() 之前的 WM_CLOSE 中。我觉得这更正确,因为微软 WM_CLOSE 的目的似乎是“嘿,这个窗口即将被破坏。你想提前做点什么吗?”而消息循环后的清理很愚蠢。 您所做的事情本身并没有错。 Windows 可以在消息循环开始之前存在,因此它们没有理由在它退出后不能持续存在。您只需要确保无论它是如何启动的,都始终如一地处理关闭。无论如何,很高兴它现在对你有用! 在 COM 方面,我很好奇为什么我的 D2D 可以在我没有初始化 COM 的情况下工作。但是,为了防止在启动时绘制 win32 背景,我终于收到了 HRESULT 错误,指出在尝试使用第一次尚未显示的窗口进行渲染时 COM 未初始化。仍然不完全确定 D2D init 的 COM 本身如何。

以上是关于C++ COM、Direct2D、Win32 和 WM_CLOSE的主要内容,如果未能解决你的问题,请参考以下文章

使用 Direct2D 在非客户区绘图

《COM本质论》读书笔记

使用 Direct2D 和 DirectWrite(C++、DirectX)制作按钮

使用 ID2D1Device3 的 direct2d 快速入门

Direct2D C++编程约定

Direct2D 设备中的 C++ 绘图弧