一、共享表面(为「整个应用共享一张后台位图」落地基础设施)
- 新增 Components/Shared/{Inc,Src}/UiSurface.*:{ dc, rect, stride, bits, version, scale }。
SubView() 把子矩形换算到宿主坐标、夹取在本表面内、按 stride 重算 bits 偏移、传递 version。
⚠️ 注释里写明:bits 只在 CPU 侧合成时使用;画控件只需 dc + rect(用 bits 画字会破坏 ClearType)。
- Application 增 BackBufferSurface();后备缓冲由 CreateCompatibleBitmap 改为
**top-down 32bpp CreateDIBSection**(biHeight 取负,y 向下递增),并记录 bits/stride/version
(重建位图时 ++version,旧句柄因此可判定作废)。
二、修复「退出时访问违例」(真实缺陷)
- Application::Shutdown() 中 **DestroyWindow 必须在 GdiplusShutdown 之前**:前一帧的 InvalidateRect
可能留下未处理更新区域,DestroyWindow 期间派发 WM_PAINT ⇒ Render() ⇒ 在已关闭的 GDI+ 上构造
Graphics ⇒ 崩溃。
- Render() 开头加保护:!m_gdiplusReady 或 m_hwnd 为空时直接返回。
验证:
- 像素:8 场景(A 全览 / B 复杂浮层 / C 平移 / D 悬停 / E 按下 / F 松开 / G 悬停主按钮 / H 移出)
哈希与 DIB 改造前**逐一相同**(A 2166933…、B C444ECF…、C 7984446…、D BF85587…、
E 1C663E4…、F 0535292…、G 7A58172…、H 23F390D…)。
- 功能:locate-cursor-button (1291,73) / click-cursor-changes-mode (1->2) /
release-outside-does-not-fire 全部 PASS。
- uicheck 69 passed / 0 failed(新增 7 条 UiSurface 断言:bits 按 stride 偏移、矩形夹取、
完全在外时返回无效表面、version/scale 传递)。
- make release / make debug 零警告;Target.exe 883,200 B(< 900KB)。
附注:探针此前的退出崩溃其实来自「探针自己又调了一次 GdiplusShutdown」(Application::Shutdown
内部已 shutdown),与本轮修复无关;本轮的顺序调整与 Render 保护仍保留为防御性正确做法。
56 lines
2.1 KiB
C++
56 lines
2.1 KiB
C++
#ifndef __UI_SURFACE_HPP__
|
||
#define __UI_SURFACE_HPP__
|
||
|
||
#include <windows.h>
|
||
#include <objidl.h>
|
||
#include <gdiplus.h>
|
||
|
||
#include <cstdint>
|
||
|
||
#include "Theme.hpp"
|
||
|
||
namespace jdi
|
||
{
|
||
|
||
/**
|
||
* @brief 一块可绘制表面(DC + 矩形 + 可选像素直写视图)。
|
||
*
|
||
* 这是"整个应用共享一张后台位图"这一设想的落地载体:宿主创建一次,
|
||
* 之后把"我负责哪一块"以 `UiSurface::SubView()` 的形式发给子元素。
|
||
*
|
||
* ## 为什么需要 `version`
|
||
*
|
||
* 位图会在 `WM_SIZE` / `WM_DPICHANGED` 时被销毁重建。任何缓存下来的 `bits`
|
||
* **立刻变成野指针**,因此像素指针必须与"位图代次"绑定:版本不一致就作废重取。
|
||
* ⚠️ **绝不缓存裸 `bits` 跨帧使用**(本项目历史上因缓存裸指针踩过悬垂访问)。
|
||
*
|
||
* ## 什么时候才需要 `bits`
|
||
*
|
||
* `dc + rect` 已经足够做 GDI/GDI+ 绘制(往子矩形里画本来就是 O(1))。
|
||
* `bits` 只在**CPU 侧直接改像素**(memcpy / alpha 混合 / 重采样)时才有意义;
|
||
* 用它来"画控件"是净亏损(会破坏 ClearType、引入 stride/翻转/写序错误)。
|
||
*/
|
||
struct UiSurface
|
||
{
|
||
HDC dc = nullptr; /**< 目标 DC(GDI / GDI+ 都画这个)。 */
|
||
gd::RectF rect; /**< 该表面在宿主坐标系中的矩形(设备像素)。 */
|
||
int stride = 0; /**< 行字节数(**不可**假设等于 width × 4)。 */
|
||
std::uint32_t* bits = nullptr; /**< `rect` 左上角那一像素(不是位图首地址)。 */
|
||
std::uint64_t version = 0; /**< 位图代次;失配 ⇒ 该表面作废。 */
|
||
float scale = 1.0f; /**< DPI 缩放系数(逻辑像素 → 设备像素)。 */
|
||
|
||
/** @brief 表面是否可用。 */
|
||
bool Valid() const { return dc != nullptr && rect.Width > 0.0f && rect.Height > 0.0f; }
|
||
|
||
/**
|
||
* @brief 派生子视图(相对本表面左上角偏移)。
|
||
*
|
||
* `bits` 会按 `stride` 正确换算到子矩形左上角;子矩形会被夹在本表面之内。
|
||
*/
|
||
UiSurface SubView(const gd::RectF& localRect) const;
|
||
};
|
||
|
||
} // namespace jdi
|
||
|
||
#endif // __UI_SURFACE_HPP__
|