|
|
@@ -4406,8 +4406,17 @@ namespace DuiLib
|
|
|
// Render screen
|
|
|
//
|
|
|
// Prepare offscreen bitmap
|
|
|
- if (m_bOffscreenPaint && m_hbmpOffscreen == NULL)
|
|
|
+ // 【R42 · NULL 离屏 DC 崩溃】判据必须同时看 DC 与位图。
|
|
|
+ // 2026-10-08 dump 实证(toast 的 WM_PAINT):现场 m_hbmpOffscreen != NULL 而
|
|
|
+ // m_hDcOffscreen == NULL,旧判据只看位图 ⇒ 整块跳过创建,把 NULL DC 直送
|
|
|
+ // m_pRoot->Paint(),最终炸在 UIRender.cpp 的 DrawText 断言(Release 下则是未定义行为)。
|
|
|
+ // 二者是「耦合创建、耦合销毁」的,一旦出现半截残留就先彻底清干净再重建,
|
|
|
+ // 绝不允许带着 NULL DC 往下走。
|
|
|
+ if (m_bOffscreenPaint && (m_hbmpOffscreen == NULL || m_hDcOffscreen == NULL))
|
|
|
{
|
|
|
+ if (m_hDcOffscreen != NULL) { ::DeleteDC(m_hDcOffscreen); m_hDcOffscreen = NULL; }
|
|
|
+ if (m_hbmpOffscreen != NULL) { ::DeleteObject(m_hbmpOffscreen); m_hbmpOffscreen = NULL; }
|
|
|
+ m_pOffscreenBits = NULL;
|
|
|
m_hDcOffscreen = ::CreateCompatibleDC(m_hDcPaint);
|
|
|
m_hbmpOffscreen = CRenderEngine::CreateARGB32Bitmap(m_hDcPaint, dwWidth, dwHeight, (LPBYTE*)&m_pOffscreenBits);
|
|
|
ASSERT(m_hDcOffscreen);
|
|
|
@@ -4422,7 +4431,18 @@ namespace DuiLib
|
|
|
// Begin Windows paint
|
|
|
PAINTSTRUCT ps = { 0 };
|
|
|
::BeginPaint(m_hWndPaint, &ps);
|
|
|
- if (m_bOffscreenPaint)
|
|
|
+ // 【R42】离屏资源完整性闸门:DC / 位图只要还缺一样,本帧就退回普通渲染
|
|
|
+ // (走下面 else 分支的 m_hDcPaint 路径)。这样即便 CreateCompatibleDC /
|
|
|
+ // CreateARGB32Bitmap 失败(GDI 句柄耗尽、窗口尺寸 0 等),也只是这一帧画得
|
|
|
+ // 朴素一点,而不是把 NULL DC 传进整棵控件树。
|
|
|
+ // 这里刻意不去改 m_bOffscreenPaint 这个持久标志:只降级本帧,下一帧还会
|
|
|
+ // 照常重试建离屏资源 ⇒ 偶发的资源紧张不会把窗口永久降级成无离屏渲染。
|
|
|
+ const bool bOffscreenReady = (m_hDcOffscreen != NULL && m_hbmpOffscreen != NULL);
|
|
|
+ if (m_bOffscreenPaint && !bOffscreenReady)
|
|
|
+ {
|
|
|
+ ::OutputDebugString(_T("[DuiLib][R42] 离屏 DC/位图不完整,本帧退回普通渲染\r\n"));
|
|
|
+ }
|
|
|
+ if (m_bOffscreenPaint && bOffscreenReady)
|
|
|
{
|
|
|
HBITMAP hOldBitmap = (HBITMAP) ::SelectObject(m_hDcOffscreen, m_hbmpOffscreen);
|
|
|
int iSaveDC = ::SaveDC(m_hDcOffscreen);
|
|
|
@@ -4801,9 +4821,11 @@ namespace DuiLib
|
|
|
::SelectObject(m_hDcPaint, hOldPen);
|
|
|
}
|
|
|
}
|
|
|
- else
|
|
|
+ else if (m_hDcPaint != NULL)
|
|
|
{
|
|
|
// A standard paint job
|
|
|
+ // 【R42】注意:不能在这里 return —— 下面还有 EndPaint / SetPainting(false)
|
|
|
+ // 必须走到,否则会引入新的状态泄漏。缺 DC 时用分支条件跳过即可。
|
|
|
int iSaveDC = ::SaveDC(m_hDcPaint);
|
|
|
m_pRoot->Paint(m_hDcPaint, rcPaint, NULL);
|
|
|
for (int i = 0; i < m_aPostPaintControls.GetSize(); i++)
|
|
|
@@ -4813,6 +4835,12 @@ namespace DuiLib
|
|
|
}
|
|
|
::RestoreDC(m_hDcPaint, iSaveDC);
|
|
|
}
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 【R42】连 m_hDcPaint 都拿不到(窗口正在销毁 / GDI 异常):本帧什么都不画。
|
|
|
+ // 旧实现会直接把 NULL 传进 m_pRoot->Paint(),最终炸在 UIRender.cpp 的
|
|
|
+ // DrawText 断言;Release 下 ASSERT 是空宏,则是赤裸裸的未定义行为。
|
|
|
+ }
|
|
|
// All Done!
|
|
|
::EndPaint(m_hWndPaint, &ps);
|
|
|
|