Browse Source

触摸相关的问题暂时全部修复了。

张洋 3 weeks ago
parent
commit
7f2ad1ea6b

+ 61 - 8
DuiLib/Core/UIBase.cpp

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

BIN
bin/Win32/Debug/zhipuzi_pos_windows/DuiLib_d.dll


BIN
bin/Win32/Release/zhipuzi_pos_windows/DuiLib.dll


BIN
bin/Win32/Release/zhipuzi_pos_windows/zhipuzi_pos_windows.exe


BIN
dll/debug/DuiLib_d.dll


BIN
dll/release/DuiLib.dll


BIN
pdb/release/DuiLib.pdb