如何在 WM_PAINT 处理程序中捕获 assert()?
How to catch assert() in WM_PAINT handler?
我有 MSVC 2019,vc142 x64,SDK 10.0.18362.0,WINAPI 游戏项目,启用了 JIT 调试,定义了 _DEBUG
。我使用标准库 #include <cassert>
中的 assert()
。 assert(expr)
呼叫扩展到 _wassert
。如果测试代码 assert(false)
放置在除 WM_PAINT
处理程序之外的任何位置,则会显示带有 Abort/Retry/Ignore 选项的 window,这是预期的行为。
但如果我在 case WM_PAINT
中有 assert(false)
,则断言 window 未显示。程序只是中止并写入 stderr。问题是很多游戏逻辑是从 WM_PAINT
的处理程序(例如 Core::Update(dt)
)调用的,我无法捕获我的代码生成的任何断言。
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: ...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!
即使 assert()
是从 WM_PAINT
触发的,我也希望使用 Abort/Retry/Ignore 选项进行相同的调试 window
闲置时如果不使用BeginPaint
and EndPaint
functions, the system will continuously send WM_PAINT
消息,会出现一些异常情况。
所以只需要修改代码如下:
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;
}
输出:
我们在处理 WM_PAINT
消息时必须小心。 Windows 管理器不断监视屏幕的哪些部分需要重绘并将此信息添加到 window 的更新区域。当调用GetMessage
或PeekMessage
并且没有更高优先级的消息时,为windows生成具有非空更新区域的WM_PAINT
消息。
BeginPaint
准备 window 的设备上下文使用更新区域来限制绘画区域。清除此更新区域后,window 甚至可以在开始绘制当前区域之前收集新区域进行绘制。
如果我们省略 BeginPaint
调用,更新区域不会被清除(除非我们使用替代解决方案,例如 ValidateRect
)并且 WM_PAINT
始终准备好被调度。这会导致无休止的 WM_PAINT
消息流,处理器使用率很高。请记住,DefWindowProc
在内部处理 WM_PAINT
,因此这些问题仅在您明确拦截 WM_PAINT
消息时才会出现。
在 WM_PAINT
处理程序中使用 asert
,而不是 BeginPaint
调用或在 BeginPaint
调用之前,会导致消息框和 WM_PAINT
消息之间出现不必要的交互。根据设置 assert
可以显示启动新(嵌套)消息循环的消息框。当 assert
构建的消息框要显示时 WM_PAINT
将再次生成原始 window。这导致下一个嵌套循环一次又一次。 32 次嵌套调用 MessageBox
失败后程序中止(通过调用 abort
函数)。
在 assert
停止递归循环嵌套之前放置 BeginPaint
(或清除更新区域的任何替代方法)(至少直到调度下一个 WM_PAINT
)和 assert
可以正确显示消息框。
因此,如果您想在 WM_PAINT
处理程序中使用 assert
,请将其放在 BeginPaint
或 ValidateRect(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;
}
请记住,这是基于观察到的行为而不是基于任何特定文档,因此您可能会根据设置、运行时间版本甚至是否附加调试器获得不同的结果。
使用 VC2017 x86 Debug build ucrt 10.0.17763.0
在 Windows 10 上完成的测试
我个人并不知道这个限制,它看起来有点糟糕。特别是对于放置在 window 过程顶部的最终断言。幸运的是附加的调试器至少在输出中显示错误 window 并且我从来没有 运行 在没有附加调试器的情况下调试构建。
我有 MSVC 2019,vc142 x64,SDK 10.0.18362.0,WINAPI 游戏项目,启用了 JIT 调试,定义了 _DEBUG
。我使用标准库 #include <cassert>
中的 assert()
。 assert(expr)
呼叫扩展到 _wassert
。如果测试代码 assert(false)
放置在除 WM_PAINT
处理程序之外的任何位置,则会显示带有 Abort/Retry/Ignore 选项的 window,这是预期的行为。
但如果我在 case WM_PAINT
中有 assert(false)
,则断言 window 未显示。程序只是中止并写入 stderr。问题是很多游戏逻辑是从 WM_PAINT
的处理程序(例如 Core::Update(dt)
)调用的,我无法捕获我的代码生成的任何断言。
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: ...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!
即使 assert()
是从 WM_PAINT
闲置时如果不使用BeginPaint
and EndPaint
functions, the system will continuously send WM_PAINT
消息,会出现一些异常情况。
所以只需要修改代码如下:
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;
}
输出:
我们在处理 WM_PAINT
消息时必须小心。 Windows 管理器不断监视屏幕的哪些部分需要重绘并将此信息添加到 window 的更新区域。当调用GetMessage
或PeekMessage
并且没有更高优先级的消息时,为windows生成具有非空更新区域的WM_PAINT
消息。
BeginPaint
准备 window 的设备上下文使用更新区域来限制绘画区域。清除此更新区域后,window 甚至可以在开始绘制当前区域之前收集新区域进行绘制。
如果我们省略 BeginPaint
调用,更新区域不会被清除(除非我们使用替代解决方案,例如 ValidateRect
)并且 WM_PAINT
始终准备好被调度。这会导致无休止的 WM_PAINT
消息流,处理器使用率很高。请记住,DefWindowProc
在内部处理 WM_PAINT
,因此这些问题仅在您明确拦截 WM_PAINT
消息时才会出现。
在 WM_PAINT
处理程序中使用 asert
,而不是 BeginPaint
调用或在 BeginPaint
调用之前,会导致消息框和 WM_PAINT
消息之间出现不必要的交互。根据设置 assert
可以显示启动新(嵌套)消息循环的消息框。当 assert
构建的消息框要显示时 WM_PAINT
将再次生成原始 window。这导致下一个嵌套循环一次又一次。 32 次嵌套调用 MessageBox
失败后程序中止(通过调用 abort
函数)。
在 assert
停止递归循环嵌套之前放置 BeginPaint
(或清除更新区域的任何替代方法)(至少直到调度下一个 WM_PAINT
)和 assert
可以正确显示消息框。
因此,如果您想在 WM_PAINT
处理程序中使用 assert
,请将其放在 BeginPaint
或 ValidateRect(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;
}
请记住,这是基于观察到的行为而不是基于任何特定文档,因此您可能会根据设置、运行时间版本甚至是否附加调试器获得不同的结果。
使用 VC2017 x86 Debug build ucrt 10.0.17763.0
在 Windows 10 上完成的测试我个人并不知道这个限制,它看起来有点糟糕。特别是对于放置在 window 过程顶部的最终断言。幸运的是附加的调试器至少在输出中显示错误 window 并且我从来没有 运行 在没有附加调试器的情况下调试构建。