|
@@ -588,6 +588,84 @@ namespace DuiLib
|
|
|
CStdPtrArray CPaintManagerUI::m_aPreMessages;
|
|
CStdPtrArray CPaintManagerUI::m_aPreMessages;
|
|
|
CStdPtrArray CPaintManagerUI::m_aPlugins;
|
|
CStdPtrArray CPaintManagerUI::m_aPlugins;
|
|
|
|
|
|
|
|
|
|
+ // =====================================================================
|
|
|
|
|
+ // 【第 47 轮】GDI+ 运行时:从「每窗口一份」提升为「进程级一份」
|
|
|
|
|
+ // ---------------------------------------------------------------------
|
|
|
|
|
+ // 原实现把 GdiplusStartup/GdiplusShutdown 当成 CPaintManagerUI 的构造/析构副作用,
|
|
|
|
|
+ // 而 CPaintManagerUI 在本项目是【每窗口一份】(CToastWnd::m_pm / CMainWnd::m_pm
|
|
|
|
|
+ // 都是业务窗口类的内联成员) ⇒ 每开/关一个弹框、每弹一次 Toast(1.5 秒自关)
|
|
|
|
|
+ // 都会来一次 GDI+ 启动 + 一次关闭。
|
|
|
|
|
+ //
|
|
|
|
|
+ // 为什么这是错的(三条,都有官方依据):
|
|
|
|
|
+ // 1) MSDN《GdiplusShutdown》Remarks 原文:
|
|
|
|
|
+ // "you must delete all of your GDI+ objects (or have them go out of scope)
|
|
|
|
|
+ // before you call GdiplusShutdown."
|
|
|
|
|
+ // ⇒ 调 Shutdown 之前必须销毁【所有】GDI+ 对象。但 GDI+ 对象并不都受
|
|
|
|
|
+ // 单个 manager 约束:
|
|
|
|
|
+ // · 库内 static TResInfo m_SharedResInfo 的 m_ImageHash 里存着
|
|
|
|
|
+ // TImageInfo::pImage(Gdiplus::Image*),生命周期 = 整个进程;
|
|
|
|
|
+ // 而 ~CPaintManagerUI 调的是 RemoveAllImages()(bShared 默认 false),
|
|
|
|
|
+ // 【不清】这份共享缓存。(本 app 暂未启用 bShared=true,
|
|
|
|
|
+ // 但库一旦被这样用,Shutdown 就必然踩到这条约束。)
|
|
|
|
|
+ // · 库外业务侧也在直接用 GDI+ 且从不调 Startup:
|
|
|
|
|
+ // helper/CBitmapHelper.cpp:110 "new Gdiplus::Bitmap(wsPath)"
|
|
|
|
|
+ // print/CPosPrinterData.cpp:5516 "CBitmapHelper helper;"
|
|
|
|
|
+ // print/CPosPrinterData.cpp:5549 "Gdiplus::Color pixelColor;"
|
|
|
|
|
+ // ⇒ 业务对象靠 DuiLib 的窗口对象"顺带"把 GDI+ 撑活着。
|
|
|
|
|
+ // 2) MSDN 同一页的 Windows 7 专条原文:
|
|
|
|
|
+ // "For Windows 7 and earlier, if GDI+ can't create a font family, it substitutes
|
|
|
|
|
+ // the generic Sans Serif family and client-side caches the pointer ... the
|
|
|
|
|
+ // operating system retains the pointer for the generic family object past the
|
|
|
|
|
+ // object's lifetime, which causes the operating system to crash."
|
|
|
|
|
+ // ⇒ Win7 上「反复 Startup/Shutdown」存在官方写明的崩溃路径。本项目必须支持 Win7。
|
|
|
|
|
+ // 3) DuiLib 是 DLL。若改成"由静态对象析构时 Shutdown",那份析构可能发生在
|
|
|
|
|
+ // DllMain 里,而 MSDN 明令禁止在 DllMain 调 GdiplusStartup/GdiplusShutdown。
|
|
|
|
|
+ //
|
|
|
|
|
+ // 为什么"至今没炸":GdiplusStartup/Shutdown 在 gdiplus.dll 内部是【引用计数】的 ——
|
|
|
|
|
+ // 非末次 Shutdown 是 no-op(Raymond Chen《The case of the hung Explorer window》原文:
|
|
|
|
|
+ // "That call to GdiplusShutdown will not shut down GDI+ because there is still an
|
|
|
|
|
+ // outstanding client.")。目前主窗口的 manager 一直活着 ⇒ 计数恒 ≥1 ⇒
|
|
|
|
|
+ // 那些每窗口的 Startup/Shutdown 全是空转。【不是代码对,是运气好。】
|
|
|
|
|
+ //
|
|
|
|
|
+ // 修法(只改生命周期归属,不动任何绘制/时序):
|
|
|
|
|
+ // · 静态存储保存 token / StartupInput(天然活到进程结束,满足 MSDN 对
|
|
|
|
|
+ // "input 必须在 Shutdown 之前有效"的要求);
|
|
|
|
|
+ // · 引用计数:0→1 时做唯一一次 Startup;递减时【不】调用 GdiplusShutdown;
|
|
|
|
|
+ // · 进程退出时由 OS 回收 GDI+(业界通行做法:GDI+ 是进程级资源,
|
|
|
|
|
+ // 进程结束本就不需要"手动卸载")。
|
|
|
|
|
+ // 注:本 app 至今未发生"进程内 manager 计数归零",所以这次改动【不改变现有行为】,
|
|
|
|
|
+ // 只是把"靠运气"换成"靠结构";它同时消除了 Win7 那条反复 Startup/Shutdown 的路径。
|
|
|
|
|
+ // =====================================================================
|
|
|
|
|
+ namespace
|
|
|
|
|
+ {
|
|
|
|
|
+ ULONG_PTR g_gdiplusToken = 0;
|
|
|
|
|
+ volatile LONG g_gdiplusRefs = 0;
|
|
|
|
|
+ Gdiplus::GdiplusStartupInput g_gdiplusStartupInput; // 静态存储:天然覆盖整个进程
|
|
|
|
|
+ bool g_gdiplusStarted = false; // 仅供诊断:Startup 是否成功
|
|
|
|
|
+
|
|
|
|
|
+ // 每构造一个 CPaintManagerUI 调用一次(保持"引用计数"语义,便于将来若真要
|
|
|
|
|
+ // 在某个确定时机收尾时有据可依)。只有 0→1 那一次才真正 GdiplusStartup。
|
|
|
|
|
+ inline void GdiplusAddRef()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (::InterlockedIncrement(&g_gdiplusRefs) == 1)
|
|
|
|
|
+ {
|
|
|
|
|
+ const Gdiplus::Status st = Gdiplus::GdiplusStartup(&g_gdiplusToken,
|
|
|
|
|
+ &g_gdiplusStartupInput,
|
|
|
|
|
+ NULL);
|
|
|
|
|
+ g_gdiplusStarted = (st == Gdiplus::Ok);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 每析构一个 CPaintManagerUI 调用一次。
|
|
|
|
|
+ // ★ 这里【故意不调用】GdiplusShutdown:见上方 1)/2)/3) 三条理由 ——
|
|
|
|
|
+ // 进程内可能仍有不受本 manager 约束的 GDI+ 对象(共享图片缓存 / 业务侧
|
|
|
|
|
+ // CBitmapHelper 的 Gdiplus::Bitmap),而 GdiplusShutdown 会让全局 GDI+ 失效。
|
|
|
|
|
+ inline void GdiplusRelease()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (g_gdiplusRefs > 0) ::InterlockedDecrement(&g_gdiplusRefs);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
CPaintManagerUI::CPaintManagerUI() :
|
|
CPaintManagerUI::CPaintManagerUI() :
|
|
|
m_hWndPaint(NULL),
|
|
m_hWndPaint(NULL),
|
|
|
m_hDcPaint(NULL),
|
|
m_hDcPaint(NULL),
|
|
@@ -712,8 +790,18 @@ namespace DuiLib
|
|
|
::ZeroMemory(&m_rcLayeredUpdate, sizeof(m_rcLayeredUpdate));
|
|
::ZeroMemory(&m_rcLayeredUpdate, sizeof(m_rcLayeredUpdate));
|
|
|
m_ptLastMousePos.x = m_ptLastMousePos.y = -1;
|
|
m_ptLastMousePos.x = m_ptLastMousePos.y = -1;
|
|
|
|
|
|
|
|
- m_pGdiplusStartupInput = new Gdiplus::GdiplusStartupInput;
|
|
|
|
|
- Gdiplus::GdiplusStartup(&m_gdiplusToken, m_pGdiplusStartupInput, NULL); // 加载GDI接口
|
|
|
|
|
|
|
+ // 【第 47 轮】GDI+ 已提升为进程级(见文件上方 "进程级 GDI+ 运行时" 的长注释):
|
|
|
|
|
+ // 这里只递增引用计数,0→1 时才做进程内唯一一次 GdiplusStartup。
|
|
|
|
|
+ GdiplusAddRef();
|
|
|
|
|
+
|
|
|
|
|
+ // 两个成员降级为【借用的只读快照】,不再拥有任何资源:
|
|
|
|
|
+ // · m_pGdiplusStartupInput 指向文件级静态对象 ⇒ ★ 绝对禁止 delete ★(它不是你 new 的)
|
|
|
|
|
+ // · m_gdiplusToken 只是当时 token 的快照 ⇒ 仅供调试观察,禁止再传给 GdiplusShutdown
|
|
|
|
|
+ // 保留这两个成员(而不是删掉)是为了不改动 CPaintManagerUI 的布局:
|
|
|
|
|
+ // 本类被业务侧当内联成员按值持有(CToastWnd::m_pm / CMainWnd::m_pm),
|
|
|
|
|
+ // 布局一变业务 exe 就必须重编,否则内存错位。
|
|
|
|
|
+ m_pGdiplusStartupInput = &g_gdiplusStartupInput;
|
|
|
|
|
+ m_gdiplusToken = g_gdiplusToken;
|
|
|
|
|
|
|
|
CShadowUI::Initialize(m_hInstance);
|
|
CShadowUI::Initialize(m_hInstance);
|
|
|
|
|
|
|
@@ -762,9 +850,12 @@ namespace DuiLib
|
|
|
m_aPreMessages.Remove(m_aPreMessages.Find(this));
|
|
m_aPreMessages.Remove(m_aPreMessages.Find(this));
|
|
|
// 销毁拖拽图片
|
|
// 销毁拖拽图片
|
|
|
if (m_hDragBitmap != NULL) ::DeleteObject(m_hDragBitmap);
|
|
if (m_hDragBitmap != NULL) ::DeleteObject(m_hDragBitmap);
|
|
|
- //卸载GDIPlus
|
|
|
|
|
- Gdiplus::GdiplusShutdown(m_gdiplusToken);
|
|
|
|
|
- delete m_pGdiplusStartupInput;
|
|
|
|
|
|
|
+ // 【第 47 轮】GDI+ 收尾:只递减进程级引用计数,【故意不调用 GdiplusShutdown】。
|
|
|
|
|
+ // 理由见文件上方 "进程级 GDI+ 运行时" 的注释(MSDN 要求 Shutdown 前销毁所有 GDI+ 对象、
|
|
|
|
|
+ // Win7 反复 Startup/Shutdown 的官方崩溃路径、DLL 静态析构不能碰 Gdiplus*)。
|
|
|
|
|
+ // ★ 原实现那两行 `GdiplusShutdown(m_gdiplusToken); delete m_pGdiplusStartupInput;`
|
|
|
|
|
+ // 已删除:前者会误伤仍存活的 GDI+ 对象,后者会 delete 掉静态对象(本实例并不拥有它)。
|
|
|
|
|
+ GdiplusRelease();
|
|
|
// DPI管理对象
|
|
// DPI管理对象
|
|
|
if (m_pDPI != NULL)
|
|
if (m_pDPI != NULL)
|
|
|
{
|
|
{
|