|
|
@@ -595,9 +595,38 @@ UINT CWindowWnd::ShowModal()
|
|
|
{
|
|
|
pManager->DetachPaintWindow();
|
|
|
}
|
|
|
- // 销毁旧窗口(旧窗口上的窗口过程会把 m_hWnd 清空,但我们已经在上面置 NULL 了,
|
|
|
- // 且 manager 已摘除,不会再触发 DuiLib 的绘制路径)。
|
|
|
- if( ::IsWindow(hWndOldCaptured) ) ::DestroyWindow(hWndOldCaptured);
|
|
|
+ // ---------------------------------------------------------------------
|
|
|
+ // ⚠️⚠️⚠️【第 45 轮 · 悬垂 this 根治】销毁"壳窗口"之前,必须先把它的
|
|
|
+ // GWLP_USERDATA 摘掉,否则本对象会被自己删掉。
|
|
|
+ //
|
|
|
+ // 机制(真机断言实证,崩溃点在 UIBase.cpp:674):
|
|
|
+ // 这个壳窗口是本对象 `Create` 出来的 —— CWindowWnd::Create 走
|
|
|
+ // `CreateWindowEx(..., this)`,__WndProc 的 WM_NCCREATE 会把 `this`
|
|
|
+ // 写进该 HWND 的 GWLP_USERDATA。于是 ::DestroyWindow 时
|
|
|
+ // __WndProc 必走 WM_NCDESTROY 分支 ⇒ `pThis->OnFinalMessage(hWnd)`。
|
|
|
+ // 而**业务侧 40+ 个弹框类都把 OnFinalMessage 实现成 `delete this`**
|
|
|
+ // (例:zhipuzi_pos_windows/wnd/CChufangSettingWnd.h:32):
|
|
|
+ // void OnFinalMessage(HWND) { /*WindowImplBase::...*/ m_pm.RemovePreMessageFilter(this); delete this; }
|
|
|
+ // ⇒ 对象在本函数**还没跑完**时就被自我销毁,随后
|
|
|
+ // `DialogBoxIndirectParam(..., (LPARAM)this)` ← 第 674 行那个断言的现场
|
|
|
+ // 交出去的是**已释放对象的指针**;__DialogProc 读 `pThis->m_hWnd` 拿到的是
|
|
|
+ // Debug 堆 free 填充 0xDDDDDDDD(≠ NULL)⇒ `ASSERT(pThis->m_hWnd == NULL)` 失败。
|
|
|
+ // (调用栈:CSettingPageUI::HandleClickMsg:1512 → ShowModal:630 → __DialogProc)
|
|
|
+ //
|
|
|
+ // 为什么"只摘 USERDATA"就够(不新增任何机制):
|
|
|
+ // `delete this` 的职责**已经转移到对话框窗口**上 ——
|
|
|
+ // __DialogProc 会把 `this` 写进**对话框**的 GWLP_USERDATA,
|
|
|
+ // 对话框销毁时由 __DialogWndProc 的 WM_NCDESTROY 调 OnFinalMessage ⇒ 仍然只删一次。
|
|
|
+ // 壳窗口被摘掉 USERDATA 后销毁时,__WndProc 里 `pThis == NULL` ⇒ 直接走
|
|
|
+ // DefWindowProc,不会触发第二次 OnFinalMessage(无双重释放)。
|
|
|
+ // 壳窗口此时已经没有存在价值:m_hWnd 已置空、manager 已 DetachPaintWindow、
|
|
|
+ // 控件树已由 AttachPaintWindow 跟着 manager 搬到对话框。
|
|
|
+ // ---------------------------------------------------------------------
|
|
|
+ if( ::IsWindow(hWndOldCaptured) )
|
|
|
+ {
|
|
|
+ ::SetWindowLongPtr(hWndOldCaptured, GWLP_USERDATA, 0L);
|
|
|
+ ::DestroyWindow(hWndOldCaptured);
|
|
|
+ }
|
|
|
|
|
|
// ---- 【Win7"咚"声 · 根治】把"在途的提升鼠标副本"从消息队列里摘掉 ----
|
|
|
//
|
|
|
@@ -633,6 +662,25 @@ UINT CWindowWnd::ShowModal()
|
|
|
CWindowWnd::__DialogProc,
|
|
|
(LPARAM)this);
|
|
|
|
|
|
+ // ---------------------------------------------------------------------
|
|
|
+ // ⚠️⚠️【第 45 轮】从这里往下**只能碰局部变量与静态函数**,绝不能再读写本对象的成员。
|
|
|
+ //
|
|
|
+ // 为什么:DialogBoxIndirectParam 返回时,对话框窗口已经销毁 ——
|
|
|
+ // __DialogWndProc 的 WM_NCDESTROY 已经调过 `pThis->OnFinalMessage(hWnd)`,
|
|
|
+ // 而业务侧弹框类的 OnFinalMessage 是 `delete this`
|
|
|
+ // ⇒ 本对象此刻**已经被释放**。
|
|
|
+ // 原来这里的 `m_bDoModal = false;` 与 `return m_nModalRet;` 就是在已释放内存上
|
|
|
+ // 读写(一直没炸,只因那块堆内存尚未被复用),属同源隐患,一并收掉。
|
|
|
+ //
|
|
|
+ // 返回值从哪来:__DialogWndProc → Close() 里是 `::EndDialog(m_hWnd, (INT_PTR)nRet)`,
|
|
|
+ // 而 DialogBoxIndirectParam 的返回值**就是**那个 nResult,与 m_nModalRet 恒等
|
|
|
+ // ⇒ 直接取局部变量 nRet 即可,无需回头读成员。
|
|
|
+ //
|
|
|
+ // m_bDoModal 为什么正常路径不用复位:需要它的那个对象此刻已被销毁;若某个弹框类
|
|
|
+ // 不删自己,它下次 ShowModal 的开头会重新 `m_bDoModal = true`,语义不变。
|
|
|
+ // ---------------------------------------------------------------------
|
|
|
+ const UINT nModalRetVal = ( nRet == -1 ) ? 0u : (UINT)nRet;
|
|
|
+
|
|
|
// 资源可以释放了(DialogBoxIndirectParam 内部已把模板复制走)——
|
|
|
// 但谨慎起见放在 EndDialog 之后,与 nim_duilib 一致。
|
|
|
::GlobalUnlock(hResource);
|
|
|
@@ -641,18 +689,19 @@ UINT CWindowWnd::ShowModal()
|
|
|
// 兜底:若 __DialogProc 因故没跑(创建失败 / WM_INITDIALOG 里 early-return),
|
|
|
// 这里把中转变量清干净,避免悬垂指针影响后续 ShowModal。
|
|
|
SetDetachedManager(NULL);
|
|
|
-
|
|
|
- m_bDoModal = false;
|
|
|
CPaintManagerUI::SetModalWindow(hPrevModal);
|
|
|
|
|
|
-
|
|
|
// DialogBoxIndirectParam 返回值:-1 表示创建失败
|
|
|
if( nRet == -1 )
|
|
|
{
|
|
|
+ // 创建失败 ⇒ 对话框窗口从未存在 ⇒ __DialogProc 没跑 ⇒ 本对象还活着,
|
|
|
+ // 这时才允许安全复位成员(否则下一次 ShowModal 会误判状态)。
|
|
|
+ m_bDoModal = false;
|
|
|
+ m_hWnd = NULL;
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
- return m_nModalRet;
|
|
|
+ return nModalRetVal;
|
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
@@ -1077,7 +1126,11 @@ LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
|
|
|
DuiLib::CPaintManagerUI* pMgrNc = FindManagerByPaintWindow(hWnd);
|
|
|
if( pMgrNc != NULL ) pMgrNc->ClearDefProcRelay(hWnd);
|
|
|
BindRelayTarget(NULL, NULL);
|
|
|
- ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
|
|
|
+ // ⚠️【第 45 轮】这里必须用实参 hWnd,不能写 pThis->m_hWnd:
|
|
|
+ // 真模态路径下 ShowModal 会**先把 m_hWnd 置空**再去销毁窗口(见那里的长注释),
|
|
|
+ // 此时 pThis->m_hWnd == NULL,用它去 SetWindowLongPtr 等于什么都没做
|
|
|
+ // ⇒ 该 HWND 的 GWLP_USERDATA 会留着已死的 this,直到句柄被系统复用。
|
|
|
+ ::SetWindowLongPtr(hWnd, GWLP_USERDATA, 0L);
|
|
|
if( pThis->m_bSubclassed ) pThis->Unsubclass();
|
|
|
pThis->m_hWnd = NULL;
|
|
|
pThis->OnFinalMessage(hWnd);
|