简体   繁体   English

如何在 WM_PAINT 处理程序中捕获 assert()?

[英]How to catch assert() in WM_PAINT handler?

I have MSVC 2019, vc142 x64, SDK 10.0.18362.0, WINAPI game project, JIT debugging is enabled, _DEBUG is defined.我有MSVC 2019,vc142 x64,SDK 10.0.18362.0,WINAPI游戏项目,启用JIT调试,定义_DEBUG I use assert() from standard lib #include <cassert> .我使用标准库中的assert() #include <cassert> assert(expr) call expands to _wassert . assert(expr)调用扩展为_wassert If test code assert(false) is placed wherever excluding WM_PAINT handler, a window with Abort/Retry/Ignore options is shown and it's intended behavior.如果将测试代码assert(false)放置在除WM_PAINT处理程序之外的任何位置,则会显示带有 Abort/Retry/Ignore 选项的 window 及其预期行为。

But in case I have assert(false) in case WM_PAINT , assert window is not being shown.但是如果我在case WM_PAINT中有assert(false) ,则不会显示 assert window 。 Program just aborts and writes to stderr.程序只是中止并写入标准错误。 Problem is that a lot of game logic is called from WM_PAINT 's handler (eg Core::Update(dt) ) and I cannot catch any assert produced by my code.问题是从WM_PAINT的处理程序(例如Core::Update(dt) )调用了很多游戏逻辑,我无法捕获我的代码产生的任何断言。

WndProc code: WndProc 代码:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_PAINT:
    {
        assert(false);
    }
    break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

Program spams this message and does not stop:程序垃圾邮件此消息并且不会停止:

Program: ...t\source\repos\Test3\x64\Debug\WindowsProject1.exe
File: C:\Users\b2soft\source\repos\Test3\...\Windows...ct1.cpp
Line: 149

Expression: false

For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts

(Press Retry to debug the application - JIT must be enabled)Assertion failed!

I want to have the same debug window with Abort/Retry/Ignore options even if assert() is fired from WM_PAINT即使从WM_PAINT触发了assert() ,我也希望使用 Abort/Retry/Ignore 选项进行相同的调试 window

We have to be careful when handling the WM_PAINT message.在处理WM_PAINT消息时我们必须小心。 Windows manager constantly monitors which parts of the screen need to be redrawn and adds this information to window's update region. Windows 管理器不断监视屏幕的哪些部分需要重绘,并将此信息添加到窗口的更新区域。 When the GetMessage or the PeekMessage is called and there is no message with higher priority, the WM_PAINT message is generated for windows with nonempty update region.当调用GetMessagePeekMessage并且没有更高优先级的消息时,将为具有非空更新区域的 windows 生成WM_PAINT消息。

The BeginPaint prepares window's device context using update region to limit painting area. BeginPaint使用更新区域准备窗口的设备上下文以限制绘画区域。 After this update region is cleared, so the window can collect new areas for painting even before it starts drawing current ones.清除此更新区域后,window 甚至可以在开始绘制当前区域之前收集新的区域进行绘制。

If we omit the BeginPaint call, the update region is not cleared (unles we use alternative solutions as for example ValidateRect ) and WM_PAINT is always ready to be dispatched.如果我们省略BeginPaint调用,更新区域不会被清除(除非我们使用替代解决方案,例如ValidateRect )并且WM_PAINT始终准备好被调度。 This leads to endless stream of WM_PAINT messages with high processor usage.这导致无休止的 stream 的WM_PAINT消息具有高处理器使用率。 Remember that DefWindowProc handles the WM_PAINT internally so these problems manifests only when you explicitly intercept WM_PAINT messages.请记住, DefWindowProc在内部处理WM_PAINT ,因此这些问题仅在您显式拦截WM_PAINT消息时才会出现。

Using the asert in WM_PAINT handler without or before the BeginPaint call, leads to unwanted interaction between message boxes and WM_PAINT messages.在没有或之前调用BeginPaint的情况下在WM_PAINT处理程序中使用asert会导致消息框和WM_PAINT消息之间发生不必要的交互。 Depending on settings assert could display message box which starts new (nested) message loop.根据设置, assert可以显示消息框,该消息框开始新的(嵌套的)消息循环。 When message box build by assert is to be shown WM_PAINT for original window is generated again.当要显示由assert构建的消息框时,将再次生成原始 window 的WM_PAINT This leads to next nested loop again and again.这会一次又一次地导致下一个嵌套循环。 After 32 nested calls MessageBox fails and program is aborted (by call to abort function).在 32 次嵌套调用后, MessageBox失败并且程序被中止(通过调用abort函数)。

Putting BeginPaint (or any alternative which clears update region) before assert stops recursive loop nesting (at least until next WM_PAINT is dispatched) and assert can display message box correctly.assert停止递归循环嵌套之前放置BeginPaint (或任何清除更新区域的替代方法)(至少直到下一个WM_PAINT被调度)并且assert可以正确显示消息框。

So if you want to use assert in WM_PAINT handler, put it after BeginPaint or ValidateRect(hWnd, NULL) .因此,如果您想在WM_PAINT处理程序中使用assert ,请将其放在BeginPaintValidateRect(hWnd, NULL)之后。

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // assert here could lead to abort

    switch (message)
    {
    case WM_PAINT:
    {
        // assert here could lead to abort

        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        // Update region cleared, no WM_PAINT will be generated
        // until some event creates new dirty area

        // assert here have chance to be handled properly
        assert(false);

        EndPaint(hWnd, &ps);

        // assert here have chance to be handled properly
    }
    break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    // assert here have chance to be handled properly

    return 0;
}

Remember this is based on observed behavior not on any specific documentation, so you could get different results depending on settings, runtime version and even debugger being attached or not.请记住,这是基于观察到的行为而不是任何特定文档,因此您可能会根据设置、运行时版本甚至调试器是否附加而获得不同的结果。

Tests done on Windows 10 with VC2017 x86 Debug build ucrt 10.0.17763.0使用 VC2017 x86 在 Windows 10 上完成的测试 Debug build ucrt 10.0.17763.0

Personally I didn't know about this limitation and it looks somewhat bad.就我个人而言,我不知道这个限制,它看起来有点糟糕。 Especially for eventual asserts placed at a top of the window procedure.特别是对于放置在 window 过程顶部的最终断言。 Fortunately attached debugger at least displays errors in the output window and I never run debug builds without debugger attached.幸运的是,附加的调试器至少在 output window 中显示错误,而且我从来没有在没有附加调试器的情况下运行调试版本。

If you do not use the BeginPaint and EndPaint functions, the system will continuously send WM_PAINT messages when it is idle, which will cause some abnormal situations.如果不使用BeginPaintEndPaint函数,系统会在空闲时不断发送WM_PAINT消息,这会导致一些异常情况。

So only need to modify the code as follows:所以只需要修改代码如下:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        HDC hdc;
        PAINTSTRUCT ps;
        RECT rect;
    switch (message)
    {
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        assert(false);
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

Output: Output: 在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM