Browse Source

修复崩溃的bug

张洋 1 week ago
parent
commit
e9613eb459
3 changed files with 42 additions and 3 deletions
  1. 31 3
      DuiLib/Core/UIManager.cpp
  2. 6 0
      DuiLib/Core/UIRender.cpp
  3. 5 0
      zhipuzi_pos_windows/wnd/CToastWnd.cpp

+ 31 - 3
DuiLib/Core/UIManager.cpp

@@ -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);
 

+ 6 - 0
DuiLib/Core/UIRender.cpp

@@ -1702,6 +1702,9 @@ namespace DuiLib {
 	void CRenderEngine::DrawText(HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText,DWORD dwTextColor, int iFont, UINT uStyle, DWORD dwTextBKColor)
 	{
 		ASSERT(::GetObjectType(hDC)==OBJ_DC || ::GetObjectType(hDC)==OBJ_MEMDC);
+		// 【R42】Debug 靠上面那条 ASSERT 拦,Release 下 ASSERT 是空宏 ⇒ 这里必须
+		// 显式判空,否则 NULL DC 会一路进 GDI / GDI+(未定义行为,且无声无息)。
+		if (hDC == NULL) return;
 		if( pstrText == NULL || pManager == NULL ) return;
 		DrawColor(hDC, rc, dwTextBKColor);
 		DrawText(hDC, pManager, rc, pstrText, dwTextColor, iFont, uStyle);
@@ -1710,6 +1713,9 @@ namespace DuiLib {
 	void CRenderEngine::DrawText(HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText, DWORD dwTextColor, int iFont, UINT uStyle)
 	{
 		ASSERT(::GetObjectType(hDC)==OBJ_DC || ::GetObjectType(hDC)==OBJ_MEMDC);
+		// 【R42】2026-10-08 真机 dump 的崩溃点就是这一行下面的绘制:toast 的 WM_PAINT
+		// 拿到了 NULL 的 m_hDcOffscreen。Release 下 ASSERT 为空 ⇒ 显式判空早退。
+		if (hDC == NULL) return;
 		if( pstrText == NULL || pManager == NULL ) return;
 
 		if (pManager->IsLayered() || pManager->IsUseGdiplusText())

+ 5 - 0
zhipuzi_pos_windows/wnd/CToastWnd.cpp

@@ -65,6 +65,11 @@ LRESULT CToastWnd::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandl
 		return 0;
 	}
 
+	// 【R42】先关掉定时器再 Close。
+	// Close() 走的是 PostMessage(WM_CLOSE),窗口不会立刻消失;若定时器还挂着,
+	// 销毁前可能再收一次 WM_TIMER 并重复 Close(),而 toast 只有 1.5s 寿命、
+	// 「建资源 / 重绘 / 销毁」本来就挤在一瞬间(2026-10-08 dump 就崩在这段时序上)。
+	::KillTimer(m_hWnd, WM_TIMER_WND_CLOSE);
 	Close();
 
 	bHandled = TRUE;