ReactOS 图形系统分析(46):DC 工具 --- dcutil.c
1. 概述
1.1 文件定位
dcutil.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcutil.c)(约 963 行)位于 win32ss/gdi/ntgdi/ 目录,是 ntgdi 内核模式 GDI 中 DC(Device Context)工具函数集 。它与 dcobjs.c(DC 的创建/销毁/对象选择)分工互补:
| 文件 | 职责 |
|---|---|
| dcobjs.c | DC 生命周期(创建、删除、清理)、对象选择(SelectBrush/SelectPen/SelectBitmap/SelectFont/SelectClipPath)、画刷更新(DC_vUpdate*Brush) |
| dcutil.c | DC 属性读写 (BkColor/BkMode/TextColor/TextAlign/MapMode/StretchBltMode/GraphicsMode/BrushOrg)、坐标换算 (LPtoDP/DPtoLP)、边界矩形 (Get/SetBoundsRect)、特殊 DC 查询(GetDCDword/GetAndSetDCDword)、杂项(CancelDC/HookFlags/SetDcUndeletable/SetDefaultRegion/TranslateCOLORREF) |
| coord.c | 映射模式/变换(SetMapMode/SetViewportOrg/SetWindowExt 等,XFORM 矩阵构建) |
| bitblt.c | 位块传输/填充(IntPatBlt 等)、SetPixel/GetPixel 的 NtGdi 入口 |
dcutil.c 中的函数大致分为四类:
- 6 个 NtGdi* 系统调用入口 :
NtGdiCancelDC、NtGdiGetDCDword、NtGdiGetAndSetDCDword、NtGdiGetBoundsRect、NtGdiSetBoundsRect------这些函数从用户态(gdi32.dll)经系统调用进入,带有 SEH 异常保护,直接访问进程地址空间参数。 - Gre* 内部 API (约 9 个):
GreDPtoLP、GreLPtoDP、GreGetBkMode、GreGetBkColor、GreGetMapMode、GreGetTextColor、GreSetBrushOrg、GreSetStretchBltMode、GreGetGraphicsMode------Gre 前缀表明它们同时可被 win32k 内部其它模块调用(win32ss 约定:Gre 前缀 = 可在内核内跨模块调用的 GDI 服务),也可被 gdi32.dll 的用户模式模拟层调用。 - IntGdi*/IntSet*/Int* 内部工具 :
IntGdiSetBkColor、IntGdiSetBkMode、IntGdiSetTextAlign、IntGdiSetTextColor、IntSetDCBrushColor、IntSetDCPenColor、IntSetDefaultRegion、IntUpdateBoundsRect、IntGdiSetHookFlags、DCU_SetDcUndeletable、TranslateCOLORREF------这些只在 win32k 内核内部使用,被其它 ntgdi/ntuser 模块直接调用。 - 辅助/禁用代码 :
IntIsPrimarySurface(被#if 0禁用)。
1.2 架构总览(ASCII 图)
用户态 (gdi32.dll)
SetBkColor / SetBkMode / SetTextColor / SetTextAlign / SetBrushOrg
SetStretchBltMode / SetGraphicsMode / GetBkColor / GetMapMode ...
GetBoundsRect / SetBoundsRect / GetDCBrushColor / GetDCPenColor
│ (系统调用: NtGdiSetBkColor, NtGdiSetTextColor, ...)
▼
win32k.sys ── ntgdi ── dcutil.c
├─ 属性读写组
│ ├─ IntGdiSetBkColor ──► 脏标记 DIRTY_BACKGROUND|DIRTY_LINE|DIRTY_FILL
│ ├─ IntGdiSetBkMode ──► jBkMode/lBkMode(MIX 背景 ROP 输入,见《分析_17》)
│ ├─ IntGdiSetTextColor ──► DIRTY_TEXT|DIRTY_LINE|DIRTY_FILL
│ │ └─► DC_vUpdateTextBrush/LineBrush/FillBrush(dcobjs.c)
│ ├─ IntGdiSetTextAlign ──► flTextAlign(RTL 翻转 TA_RIGHT↔TA_LEFT)
│ ├─ IntSetDCBrushColor ──► crBrushClr + DIRTY_FILL(DC_BRUSH 画刷颜色)
│ ├─ IntSetDCPenColor ──► crPenClr + DIRTY_LINE(DC_PEN 画笔颜色)
│ ├─ GreSetBrushOrg ──► DC_vSetBrushOrigin(ptlBrushOrigin/ptlFillOrigin)
│ ├─ GreSetStretchBltMode ──► lStretchBltMode/jStretchBltMode
│ └─ Gre*Get* 系列 ──► 无锁竞争的直接字段读取
├─ 坐标换算组
│ ├─ GreLPtoDP / GreDPtoLP ──► IntLPtoDP / IntDPtoLP(coord.h)
│ │ └─► INTERNAL_APPLY_MATRIX(FLOATOBJ 矩阵乘法)
│ └─ NtGdiGetDCDword case GdiGetMapMode ──► IntGdiSetMapMode(coord.c)
├─ 边界矩形组(BoundsRect)
│ ├─ NtGdiGetBoundsRect ──► erclBoundsApp / erclBounds
│ ├─ NtGdiSetBoundsRect ──► DC_ACCUM_APP / DC_ACCUM_WMGR
│ └─ IntUpdateBoundsRect ──► RECTL_bUnionRect 累计(被 bitblt/fillshap 等调用)
├─ 像素颜色转换
│ └─ TranslateCOLORREF ──► EXLATEOBJ_vInitialize(&exlo, &gpalRGB, surf->ppal, ...)
│ └─► XLATEOBJ_iXlate(见《分析_19》)
├─ 杂项
│ ├─ NtGdiCancelDC(UNIMPLEMENTED stub)
│ ├─ IntGdiSetHookFlags(DCE 可见区失效通知,被 ntuser/windc.c 调用)
│ ├─ DCU_SetDcUndeletable(DC_PERMANANT,防意外删除)
│ └─ IntSetDefaultRegion(初始化可见区 prgnVis)
└─ NtGdiGetDCDword / NtGdiGetAndSetDCDword(通用 DWORD 属性读写,驱动兼容用)
输出端(被本文件触发的重建):
dcobjs.c: DC_vUpdateTextBrush / DC_vUpdateLineBrush / DC_vUpdateFillBrush
└─► EBRUSHOBJ_vUpdateFromDC → 驱动画刷(见《分析_39》)
1.3 设计动机
DC 是 GDI 一切绘制操作的状态容器。dcutil.c 存在的根本动机有四点:
- 属性与绘制的解耦(惰性重建) :DC 属性(颜色、模式、对齐等)被高频修改,而画刷/画笔的实际"物化"(EBRUSHOBJ、驱动级画刷)代价高昂。dcutil.c 采用"写属性 → 只置脏标记 → 下次绘制前重建画刷 "的惰性策略,避免每次 Set* 都触发昂贵的画刷重建。脏标记机制(
pdcattr->ulDirty_)是整个模块的核心设计(详见第 4 节)。 - 统一锁定协议 :所有函数都以
DC_LockDc(hdc)开头、DC_UnlockDc(dc)结尾。DC 是共享 GDI 对象(可在多线程/多进程间共享),必须经 GDI 句柄表加锁才能安全访问;所有失败路径统一EngSetLastError(ERROR_INVALID_HANDLE)并返回CLR_INVALID/FALSE/GDI_ERROR。 - 内核态与用户态的边界安全 :NtGdi* 入口对用户传入的指针一律用
_SEH2_TRY/ProbeForWrite/ProbeForRead保护,防止越权访问内核地址;返回前通过SetLastNtError传播 NTSTATUS。 - 坐标变换的集中实现:LPtoDP/DPtoLP 是 GDI 坐标体系(逻辑坐标↔设备坐标)的唯一换算入口,内部依赖 XFORM 矩阵(FLOATOBJ 定点浮点),并支持 MM_ISOTROPIC 自动修正、RTL 布局镜像等特殊语义。
2. 核心数据结构
2.1 DC 对象(dc.h,struct _DC)
DC 是 GDI 句柄表中的一种对象(GDILoObjType_LO_DC_TYPE / GDILoObjType_LO_ALTDC_TYPE)。dc.h 中定义的 _DC 结构(95~137 行)关键字段:
c
typedef struct _DC
{
BASEOBJECT BaseObject; /* GDI 对象公共头(句柄表管理) */
DHPDEV dhpdev; /* 设备 PDEVOBJ.hPDev,驱动句柄 */
DCTYPE dctype; /* DCTYPE_DIRECT / DCTYPE_MEMORY / DCTYPE_INFO */
INT fs; /* DCFLAGS 位标志(见 2.3) */
PPDEVOBJ ppdev; /* 物理设备对象(PDEV) */
PVOID hsem; /* 每-DC 锁(PERESOURCE) */
FLONG flGraphicsCaps;
FLONG flGraphicsCaps2;
_Notnull_ PDC_ATTR pdcattr; /* ┐ 属性指针:指向本对象内嵌的 dcattr 成员 */
DCLEVEL dclevel; /* │ DCLEVEL 层(对象选择、字体、矩阵缓存) */
DC_ATTR dcattr; /* ┘ DC_ATTR 内嵌实例(pdcattr 指向它) */
HDC hdcNext, hdcPrev; /* DC 链表(同一进程的 DC 集合) */
RECTL erclClip; /* 裁剪矩形 */
POINTL ptlDCOrig; /* DC 原点(设备坐标,窗口滚动等引起) */
RECTL erclWindow; /* 窗口矩形(设备坐标) */
RECTL erclBounds; /* 边界矩形(WMGR 累计,设备坐标) */
RECTL erclBoundsApp; /* 边界矩形(APP 累计,逻辑坐标) */
PREGION prgnAPI; /* API 区域 = Meta∩Clip */
_Notnull_ PREGION prgnVis; /* 可见区域(系统区域,永不为 0) */
PREGION prgnRao; /* Rao 区域 = API∩Vis */
POINTL ptlFillOrigin; /* 填充原点(画刷原点 + DC 原点) */
EBRUSHOBJ eboFill; /* 填充画刷对象(驱动接口包装) */
EBRUSHOBJ eboLine; /* 线条画笔对象 */
EBRUSHOBJ eboText; /* 文本画刷对象(恒为纯色) */
EBRUSHOBJ eboBackground; /* 背景画刷对象(恒为纯色) */
...
ULONG ulCopyCount; /* 打印副本数(GdiGetSetCopyCount) */
} DC;
注意 pdcattr 与 dcattr 的关系:DC 结构内嵌了一个 DC_ATTR 实例 dcattr,而 pdcattr 在 DC_bAllocDcAttr(dcobjs.c)中通常指向它。pdcattr 被设计为指针是为了将来支持"局部 DC"(LDC)扩展。
2.2 DC_ATTR(ntgdihdl.h,291~351 行)
_DC_ATTR 是 DC 属性的核心容器,全部属性字段按功能分组:
c
typedef struct _DC_ATTR
{
PVOID pvLDC; /* 指向 LDC(局部 DC,打印/EMF 用),一般 NULL */
ULONG ulDirty_; /* 脏标记(见 2.4) */
HANDLE hbrush; /* 已选画刷句柄 */
HANDLE hpen; /* 已选画笔句柄 */
/* ---- 颜色对(两套:cr* 为"当前生效色",ul* 为"API 设置色") ---- */
COLORREF crBackgroundClr; /* 生效背景色(画刷重建依据) */
ULONG ulBackgroundClr; /* API 背景色(GetBkColor 返回值) */
COLORREF crForegroundClr; /* 生效前景/文本色 */
ULONG ulForegroundClr; /* API 文本色(GetTextColor 返回值) */
COLORREF crBrushClr; /* 生效 DC 画刷色(SetDCBrushColor) */
ULONG ulBrushClr; /* API DC 画刷色 */
COLORREF crPenClr; /* 生效 DC 画笔色(SetDCPenColor) */
ULONG ulPenClr; /* API DC 画笔色 */
DWORD iCS_CP; /* 颜色空间代码页 */
INT iGraphicsMode; /* GM_COMPATIBLE / GM_ADVANCED */
/* ---- 字节型"简化字段"(j 前缀):绘制热路径用,与 LONG 型并存 ---- */
BYTE jROP2; /* 光栅操作(R2_*,见《分析_17》) */
BYTE jBkMode; /* 背景模式 OPAQUE/TRANSPARENT */
BYTE jFillMode; /* 填充模式 ALTERNATE/WINDING */
BYTE jStretchBltMode; /* 拉伸模式(规范化后) */
POINTL ptlCurrent; /* 当前画笔位置(MoveToEx/LineTo) */
POINTL ptfxCurrent; /* 当前点的定点表示(28.4 定点) */
/* ---- LONG 型"完整字段":API 语义层用 ---- */
LONG lBkMode;
LONG lFillMode;
LONG lStretchBltMode;
FLONG flFontMapper; /* 字体映射标志(GdiGetSetMapperFlagsInternal) */
LONG lIcmMode;
HANDLE hcmXform; HCOLORSPACE hColorSpace; FLONG flIcmFlags;
INT IcmBrushColor; INT IcmPenColor; PVOID pvLIcm; /* ICM 相关 */
FLONG flTextAlign; /* 生效文本对齐(TA_MASK 内,含 RTL 修正) */
LONG lTextAlign; /* API 文本对齐 */
LONG lTextExtra; /* 字符间距(SetTextCharacterExtra) */
LONG lRelAbs; /* 相对/绝对移动标志 */
LONG lBreakExtra; /* 断字间距 */
LONG cBreak; /* 断字字符数 */
HANDLE hlfntNew; /* 当前选中字体句柄 */
/* ---- 坐标变换矩阵 ---- */
MATRIX mxWorldToDevice; /* 世界→设备(最终合成矩阵) */
MATRIX mxDeviceToWorld; /* 设备→世界(mxWorldToDevice 的逆) */
MATRIX mxWorldToPage; /* 世界→页面(SetWorldTransform 结果) */
FLOATOBJ efM11PtoD, efM22PtoD, efDxPtoD, efDyPtoD; /* 页→设备缩放/平移缓存 */
INT iMapMode; /* MM_TEXT/MM_ISOTROPIC/...(见 coord.c) */
DWORD dwLayout; /* LAYOUT_LTR / LAYOUT_RTL / LAYOUT_BITMAPORIENTATIONPRESERVED */
LONG lWindowOrgx; /* 窗口原点(32 位兼容字段) */
POINTL ptlWindowOrg; /* 窗口原点(逻辑) */
SIZEL szlWindowExt; /* 窗口范围 */
POINTL ptlViewportOrg; /* 视口原点(设备) */
SIZEL szlViewportExt; /* 视口范围 */
FLONG flXform; /* Xform 状态位(见 2.5) */
SIZEL szlVirtualDevicePixel; /* 虚拟设备像素尺寸(用于度量映射) */
SIZEL szlVirtualDeviceMm; /* 虚拟设备毫米尺寸 */
SIZEL szlVirtualDeviceSize; /* 虚拟设备总尺寸 */
POINTL ptlBrushOrigin; /* 画刷原点(相对 DC 原点) */
RGN_ATTR VisRectRegion; /* 可见区矩形属性缓存 */
} DC_ATTR, *PDC_ATTR;
关键设计点:cr*/ul* 双份颜色 。ul* 是 API 看到的颜色(GetBkColor/GetTextColor 返回它),cr* 是"已生效"的颜色。IntGdiSetBkColor 只有在新颜色与 crBackgroundClr 不同时才置脏标记并更新 cr*;相同则跳过(避免无谓重建)。这与"惰性重建"动机一致。
关键设计点:BYTE 简化字段(j 前缀) 。绘制热路径(如 line.c、fillshap.c 构造 MIX)只读取 jBkMode/jROP2/jFillMode/jStretchBltMode 这类单字节字段以提高缓存友好度;API 语义层(如 IntGdiSetBkMode 同时写 jBkMode 与 lBkMode)保持 LONG 型完整字段。GreSetStretchBltMode 的写法最典型:lStretchBltMode 无条件保存 API 值(不校验),jStretchBltMode 则保存规范化后的值(越界强制 WHITEONBLACK)。
2.3 DC 状态标志(dc.h,_DCFLAGS,17~37 行)
dc->fs 位标志(本文件大量用到):
| 标志 | 值 | 用途(在本文件中的体现) |
|---|---|---|
| DC_DISPLAY | 0x0001 | 显示 DC |
| DC_DIRECT | 0x0002 | 直接 DC |
| DC_CANCELED | 0x0004 | 已取消(NtGdiCancelDC 本应设置,但当前是 stub) |
| DC_PERMANANT | 0x0008 | 不可删除(DCU_SetDcUndeletable 设置) |
| DC_DIRTY_RAO | 0x0010 | Rao 区域失效(IntGdiSetHookFlags 设置;NtGdiGetBoundsRect/NtGdiGetRandomRgn 据此调用 CLIPPING_UpdateGCRegion) |
| DC_ACCUM_WMGR | 0x0020 | 窗口管理器累计边界(NtGdiSetBoundsRect 的 DCB_WINDOWMGR 路径) |
| DC_ACCUM_APP | 0x0040 | 应用程序累计边界(NtGdiSetBoundsRect 默认路径) |
| DC_RESET | 0x0080 | 重置累计边界 |
| DC_EPSPRINTINGESCAPE | 0x0200 | EPS 打印转义(NtGdiGetAndSetDCDword 的 GdiGetSetEPSPrintingEscape 读写并清除) |
| DC_TEMPINFODC | 0x0400 | 临时信息 DC |
| DC_FULLSCREEN | 0x0800 | 全屏 |
| DC_PREPARED | 0x8000 | 调试用(仅 DBG 构建,ASSERT_DC_PREPARED) |
2.4 脏标记(ntgdihdl.h,122~145 行,ulDirty_)
pdcattr->ulDirty_ 是惰性重建机制的中枢,本文件相关位:
| 位 | 名称 | 本文件中的置位/清除者 |
|---|---|---|
| 0x00000001 | DIRTY_FILL | IntGdiSetBkColor(连带)、IntGdiSetTextColor、IntSetDCBrushColor;清除于 DC_vUpdateFillBrush |
| 0x00000002 | DIRTY_LINE | IntGdiSetBkColor、IntGdiSetTextColor、IntSetDCPenColor;清除于 DC_vUpdateLineBrush |
| 0x00000004 | DIRTY_TEXT | IntGdiSetTextColor;清除于 DC_vUpdateTextBrush |
| 0x00000008 | DIRTY_BACKGROUND | IntGdiSetBkColor;清除于 DC_vUpdateBackgroundBrush |
| 0x00000010 | DIRTY_CHARSET | DC_hSelectFont(dcobjs.c)置位 |
| 0x00000100 | DIRTY_PTLCURRENT | 当前点位置变化(coord.c) |
| 0x00001000 | DC_BRUSH_DIRTY | DC_BRUSH 画刷颜色变化 |
| 0x00002000 | DC_PEN_DIRTY | DC_PEN 画笔颜色变化 |
| 0x00004000 | DC_DIBSECTION | 已选 DIB 段位图(dcobjs.c NtGdiSelectBitmap) |
| 0x00200000 | DC_MODE_DIRTY | 映射模式变化 |
清除与重建的对称性 :置位在 dcutil.c,清除在 dcobjs.c 的四个 DC_vUpdate* 函数中。任何一个 EBRUSHOBJ 只要对应脏位被置位,下次绘制前(IntEng* 系列 / DC_* 准备函数)就会调用对应 DC_vUpdate* 完成重建并清位。
2.5 Xform 状态标志(ntgdihdl.h,173~188 行,flXform)
坐标变换的惰性更新依赖这些位(详见第 4.2 节):
| 标志 | 值 | 含义 |
|---|---|---|
| METAFILE_TO_WORLD_IDENTITY | 0x1 | 元文件→世界为恒等 |
| WORLD_TO_PAGE_IDENTITY | 0x2 | 世界→页面恒等 |
| DEVICE_TO_PAGE_INVALID | 0x8 | 设备→页面矩阵失效 |
| DEVICE_TO_WORLD_INVALID | 0x10 | 设备→世界矩阵失效(DC_pmxDeviceToWorld 检查) |
| WORLD_TRANSFORM_SET | 0x20 | 已设置世界变换 |
| POSITIVE_Y_IS_UP | 0x40 | Y 轴向上(MM_* 映射模式) |
| INVALIDATE_ATTRIBUTES | 0x80 | 属性失效 |
| PTOD_EFM11_NEGATIVE / PTOD_EFM22_NEGATIVE | 0x100 / 0x200 | 页→设备缩放为负 |
| ISO_OR_ANISO_MAP_MODE | 0x400 | 各向同性/异性映射 |
| PAGE_TO_DEVICE_IDENTITY | 0x800 | 页→设备恒等 |
| PAGE_TO_DEVICE_SCALE_IDENTITY | 0x1000 | 页→设备仅缩放恒等 |
| PAGE_XLATE_CHANGED | 0x2000 | 页平移(原点)变化 |
| PAGE_EXTENTS_CHANGED | 0x4000 | 页范围变化(DC_pszlViewportExt 检查,MM_ISOTROPIC 时触发修正) |
| WORLD_XFORM_CHANGED | 0x8000 | 世界变换变化(DC_pmxWorldToDevice 检查) |
3. 函数逐一展开
3.1 坐标换算组:GreLPtoDP / GreDPtoLP
GreLPtoDP(20~32 行)
c
BOOL FASTCALL GreLPtoDP(HDC hdc, LPPOINT lpPoints, INT nCount)
{
PDC dc;
if (!(dc = DC_LockDc(hdc))) /* ① 锁定 DC(失败置 ERROR_INVALID_HANDLE) */
{
EngSetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
IntLPtoDP(dc, lpPoints, nCount); /* ② 核心换算(coord.h 内联函数) */
DC_UnlockDc(dc); /* ③ 解锁 */
return TRUE;
}
- 签名 :
BOOL GreLPtoDP(HDC, LPPOINT, INT)。lpPoints是逻辑坐标数组(原地修改),nCount为点数。 - 作用 :将一组逻辑坐标 点变换为设备坐标 。设备坐标 = 逻辑坐标经过"世界→页面→设备"三级矩阵变换(
mxWorldToDevice)后的结果。注意:如果当前 DC 是 GM_COMPATIBLE 且没有设置世界变换,则等价于"窗口/视口原点平移 + 范围缩放"。 - 实现流程 :①
DC_LockDc(GDI 句柄表锁 + 类型/字体断言)→ ②IntLPtoDP(见 4.2 节,内部先DC_vUpdateWorldToDevice保证矩阵最新,再INTERNAL_APPLY_MATRIX用 FLOATOBJ 逐点计算)→ ③ 解锁。 - 使用方式 :gdi32.dll 的
LPtoDP(objects/dc.c)最终经系统调用到达此函数;win32k 内部NtGdiGetBoundsRect(本文件 746 行)也用它把边界矩形从设备坐标转回逻辑坐标。NtGdiSetBoundsRect(855 行)则反向用IntLPtoDP。 - 注意事项 :失败只返回 FALSE 而不修改点数组;点数
nCount为负数时调用方需自行保证(实际循环用while (count--),负数会导致死循环------调用方均传正数)。IntLPtoDP返回 BOOLEAN,但GreLPtoDP忽略其失败结果(FLOATOBJ 溢出时点坐标可能不可靠)。
GreDPtoLP(6~18 行)
c
BOOL FASTCALL GreDPtoLP(HDC hdc, LPPOINT lpPoints, INT nCount)
{
PDC dc;
if (!(dc = DC_LockDc(hdc))) { EngSetLastError(ERROR_INVALID_HANDLE); return FALSE; }
IntDPtoLP(dc, lpPoints, nCount);
DC_UnlockDc(dc);
return TRUE;
}
- 作用 :
GreLPtoDP的逆变换------设备坐标 → 逻辑坐标。内部IntDPtoLP先DC_vUpdateDeviceToWorld(若DEVICE_TO_WORLD_INVALID,对mxWorldToDevice求逆得到mxDeviceToWorld),再INTERNAL_APPLY_MATRIX(&mxDeviceToWorld, ...)。 - 使用方式 :gdi32.dll 的
DPtoLP;本文件NtGdiGetBoundsRect(746 行)把 APP 边界矩形转回逻辑坐标后返回给用户。 - 注意 :若矩阵不可逆(如缩放为 0),
XFORMOBJ_iInverse会失败,此时mxDeviceToWorld保持旧值,结果不可靠------这是已知的简化。
3.2 属性查询组:GreGet*
四个 GreGet 函数结构完全一致:锁定 → 读字段 → 解锁 → 返回。它们都返回 CLR_INVALID(0xFFFFFFFF)作为错误码(虽然 BkMode/MapMode 语义上应返回 GDI_ERROR,ReactOS 统一用 CLR_INVALID,与 Wine 兼容)。
GreGetBkMode(34~47 行)
- 读取
dc->pdcattr->lBkMode(LONG 完整字段,而非 jBkMode),返回 OPAQUE(1)/TRANSPARENT(2)。 - 对应 gdi32.dll
GetBkMode。
GreGetBkColor(49~62 行)
- 读取
dc->pdcattr->ulBackgroundClr(API 背景色,注意不是 crBackgroundClr)。 - 返回 COLORREF;对应 gdi32.dll
GetBkColor。之所以返回ulBackgroundClr而非crBackgroundClr,是因为cr*是内部生效色(可能经调色板换算),而 API 语义要求返回用户上次设置的颜色。
GreGetMapMode(64~77 行)
- 读取
dc->pdcattr->iMapMode,返回 MM_TEXT(1)~MM_TWIPS(8) 等。 - 对应 gdi32.dll
GetMapMode。
GreGetTextColor(79~92 行)
- 读取
dc->pdcattr->ulForegroundClr,返回 COLORREF。 - 对应 gdi32.dll
GetTextColor。
3.3 属性设置组:IntGdiSet* / IntSet*
IntGdiSetBkColor(94~121 行)
c
COLORREF FASTCALL IntGdiSetBkColor(HDC hDC, COLORREF color)
{
PDC dc; PDC_ATTR pdcattr; HBRUSH hBrush;
if (!(dc = DC_LockDc(hDC))) { EngSetLastError(ERROR_INVALID_HANDLE); return CLR_INVALID; }
pdcattr = dc->pdcattr;
oldColor = pdcattr->ulBackgroundClr;
pdcattr->ulBackgroundClr = color;
if (pdcattr->crBackgroundClr != color)
{
pdcattr->ulDirty_ |= (DIRTY_BACKGROUND|DIRTY_LINE|DIRTY_FILL); // Clear Flag if set.
pdcattr->crBackgroundClr = color;
}
hBrush = pdcattr->hbrush;
DC_UnlockDc(dc);
NtGdiSelectBrush(hDC, hBrush); /* 解锁后重选画刷,触发重建 */
return oldColor;
}
- 签名 :
COLORREF IntGdiSetBkColor(HDC, COLORREF),返回旧背景色。 - 作用:设置背景色。背景色用于:① 文本/图形中"背景"像素的填充色(配合 BkMode=OPAQUE);② 虚线样式中的空隙色。
- 实现流程 :① 保存旧 API 值
ulBackgroundClr并写入新值 → ② 比较 cr 与 ul :若生效色不同,置三个脏位(背景/线条/填充画刷全部失效)并同步crBackgroundClr→ ③ 取出当前画刷句柄,解锁后调用NtGdiSelectBrush(hDC, hBrush)重选同一个画刷 ------这是关键技巧:SelectBrush 内部(dcobjs.c 255~280 行)会调用DC_vUpdateFillBrush,从而立即消费 DIRTY_FILL 位重建填充画刷(使其携带新背景色)→ ④ 返回旧值。 - 使用方式 :gdi32.dll
SetBkColor入口;win32k 内部其它模块也可直接调用。 - 注意事项 :
ulDirty_注释 "Clear Flag if set" 表明这三位置位后由DC_vUpdateBackgroundBrush/LineBrush/FillBrush消费。注意此处未调用DC_vUpdateBackgroundBrush立即重建 ------背景画刷的重建被推迟到下次真正使用背景时(NtGdiGetBoundsRect之外的绘制路径),这与IntGdiSetTextColor的"立即重建"策略不同(见下)。
IntGdiSetBkMode(123~141 行)
c
INT FASTCALL IntGdiSetBkMode(HDC hDC, INT Mode)
{
...
oldMode = pdcattr->lBkMode;
pdcattr->jBkMode = Mode; /* 热路径简化字段 */
pdcattr->lBkMode = Mode; /* API 完整字段 */
...
return oldMode;
}
- 签名 :
INT IntGdiSetBkMode(HDC, INT),返回旧模式。 - 作用:设置背景模式 OPAQUE(1)/TRANSPARENT(2)。BkMode 决定绘制时背景像素如何处理:OPAQUE 用背景色填充背景,TRANSPARENT 保持背景不变。
- 实现流程 :同步写
jBkMode与lBkMode两个字段,不设置任何脏标记 ------因为 BkMode 不改变画刷本身,只改变绘制时 MIX(ROP)的背景部分构造(见《分析_17》),MIX 是每次绘制时按jBkMode现算的。 - 注意事项 :返回值类型声明为
COLORREF oldMode(实为 INT),错误时返回 CLR_INVALID,与 Windows 的 GDI_ERROR(-1) 兼容。
IntGdiSetTextAlign(143~168 行)
c
UINT FASTCALL IntGdiSetTextAlign(HDC hDC, UINT Mode)
{
...
prevAlign = pdcattr->lTextAlign;
pdcattr->lTextAlign = Mode;
if (pdcattr->dwLayout & LAYOUT_RTL)
{
if ((Mode & TA_CENTER) != TA_CENTER) Mode ^= TA_RIGHT; /* RTL 镜像 */
}
pdcattr->flTextAlign = Mode & TA_MASK;
...
return prevAlign;
}
- 签名 :
UINT IntGdiSetTextAlign(HDC, UINT),返回旧对齐。 - 作用 :设置文本对齐(TA_LEFT/TA_RIGHT/TA_CENTER × TA_TOP/TA_BOTTOM/TA_BASELINE,外加 TA_UPDATECP 等)。
flTextAlign是绘制时(IntExtTextOutW)计算文本起点的依据。 - 实现流程 :① 保存旧
lTextAlign→ ② 写lTextAlign = Mode→ ③ RTL 修正 :若 DC 布局为 LAYOUT_RTL 且非 TA_CENTER,则将Mode的 TA_LEFT↔TA_RIGHT 翻转(异或 TA_RIGHT:TA_LEFT(0)TA_RIGHT=TA_RIGHT,TA_RIGHT(2)TA_RIGHT=0)→ ④flTextAlign = Mode & TA_MASK(TA_MASK 过滤掉 TA_UPDATECP/TA_NOUPDATECP 等非对齐位)→ ⑤ 返回旧值。 - 注意事项 :
lTextAlign保存用户原始值(含 TA_UPDATECP),flTextAlign保存 RTL 修正后的对齐位(不含更新标志)。此处不更新ptlCurrent相关脏位------TA_UPDATECP 的语义由文本输出函数自行处理。
IntGdiSetTextColor(170~203 行)
c
COLORREF FASTCALL IntGdiSetTextColor(HDC hDC, COLORREF color)
{
...
crOldColor = (COLORREF) pdcattr->ulForegroundClr;
pdcattr->ulForegroundClr = (ULONG)color;
if (pdcattr->crForegroundClr != color)
{
pdcattr->ulDirty_ |= (DIRTY_TEXT|DIRTY_LINE|DIRTY_FILL);
pdcattr->crForegroundClr = color;
}
DC_vUpdateTextBrush(pdc); /* 立即重建文本画刷 */
DC_vUpdateLineBrush(pdc); /* 立即重建线条画刷 */
DC_vUpdateFillBrush(pdc); /* 立即重建填充画刷 */
...
return crOldColor;
}
- 签名 :
COLORREF IntGdiSetTextColor(HDC, COLORREF),返回旧文本色。 - 作用:设置文本前景色。文本色同时影响:文本绘制(eboText)、直线绘制(SetBkMode 之前用文本色做线条色,DC_PEN 未选时)、填充(DC_BRUSH 未选时)。
- 实现流程 :① 保存旧值并写新
ulForegroundClr→ ② 若生效色crForegroundClr变化,置 DIRTY_TEXT|DIRTY_LINE|DIRTY_FILL 并同步 → ③ 立即 调用三个DC_vUpdate*重建画刷(与IntGdiSetBkColor的延迟策略不同------文本色使用频率极高,立即重建可避免每次文本输出前都检查脏位)→ ④ 返回旧值。 - 关键点 :三个
DC_vUpdate*(dcobjs.c)内部:置脏才重建 EBRUSHOBJ(EBRUSHOBJ_vUpdateFromDC),随后无条件EBRUSHOBJ_vSetSolidRGBColor(&eboText, crForegroundClr)设置纯色,最后清除脏位。由于 DC_PEN/DC_BRUSH 的处理(dcobjs.c 5054、96100 行)只在hbrush == StockObjects[DC_BRUSH]/hpen == StockObjects[DC_PEN]时才把crBrushClr/crPenClr写入画刷,因此文本色对线条/填充画刷的影响是条件性的。
IntSetDCBrushColor(205~228 行)
c
COLORREF FASTCALL IntSetDCBrushColor(HDC hdc, COLORREF crColor)
{
...
OldColor = (COLORREF) dc->pdcattr->ulBrushClr;
dc->pdcattr->ulBrushClr = (ULONG) crColor;
if ( dc->pdcattr->crBrushClr != crColor )
{
dc->pdcattr->ulDirty_ |= DIRTY_FILL;
dc->pdcattr->crBrushClr = crColor;
}
...
return OldColor;
}
- 签名 :
COLORREF IntSetDCBrushColor(HDC, COLORREF),返回旧色。 - 作用 :设置 DC 画刷色 (
SetDCBrushColor,Win2000+ 引入)。当 DC 中选中StockObjects[DC_BRUSH]时,填充使用的颜色由此值决定。返回旧颜色。 - 实现流程 :同 BkColor 模式:ul 先行 → cr 比较 → 置
DIRTY_FILL→ 返回旧值。不立即重建 (DC_vUpdateFillBrush中hbrush == DC_BRUSH分支会在下次绘制时把crBrushClr写入 eboFill)。 - 注意 :本函数不调用
NtGdiSelectBrush重选,比 IntGdiSetBkColor 少一步------因为 DC_BRUSH 颜色变化只需置 DIRTY_FILL,NtGdiGetDCObject/绘制路径会处理。
IntSetDCPenColor(258~279 行)
- 签名 :
COLORREF IntSetDCPenColor(HDC, COLORREF),返回旧色。与IntSetDCBrushColor完全对称,置DIRTY_LINE。 - 唯一不同 :锁定失败时返回
ERROR_INVALID_PARAMETER而非 ERROR_INVALID_HANDLE(源码如此,可视为笔误,但不影响功能)。 - 对应
SetDCPenColor;当 DC 中选中StockObjects[DC_PEN]时,线条颜色由此值决定。
3.4 GreSetBrushOrg(230~256 行)
c
BOOL FASTCALL GreSetBrushOrg(HDC hdc, INT x, INT y, LPPOINT pptOut)
{
PDC pdc = DC_LockDc(hdc);
if (pdc == NULL) { EngSetLastError(ERROR_INVALID_HANDLE); return FALSE; }
if (pptOut != NULL)
*pptOut = pdc->pdcattr->ptlBrushOrigin; /* 旧画刷原点 */
pdc->pdcattr->ptlBrushOrigin.x = x; /* 新原点(相对 DC 原点) */
pdc->pdcattr->ptlBrushOrigin.y = y;
DC_vSetBrushOrigin(pdc, x, y); /* 同步 dclevel 与填充原点 */
DC_UnlockDc(pdc);
return TRUE;
}
-
签名 :
BOOL GreSetBrushOrg(HDC, INT x, INT y, LPPOINT pptOut)。pptOut可选,输出旧原点。 -
作用 :设置画刷原点(
SetBrushOrgEx的底层)。画刷原点决定图案画刷(Pattern Brush)铺贴时相对 DC 的起始对齐位置。 -
实现流程 :① 锁定 → ② 可选输出旧
ptlBrushOrigin→ ③ 更新pdcattr->ptlBrushOrigin→ ④ 调用DC_vSetBrushOrigin(dcobjs.c 140~151 行):cpdc->dclevel.ptlBrushOrigin.x = x; pdc->dclevel.ptlBrushOrigin.y = y; pdc->ptlFillOrigin.x = x + pdc->ptlDCOrig.x; /* 填充原点 = 画刷原点 + DC 原点 */ pdc->ptlFillOrigin.y = y + pdc->ptlDCOrig.y;ptlFillOrigin是最终传给驱动/填充引擎的物理原点(考虑了窗口滚动导致的 DC 原点偏移)。 -
使用方式 :gdi32.dll
SetBrushOrgEx→NtGdiSetBrushOrg(dcobjs.c 162~195 行,SEH 保护输出指针)→GreSetBrushOrg。 -
注意事项 :不改
flXform、不置脏位------画刷原点不参与 EBRUSHOBJ 重建,只在绘制时作为填充原点使用。
3.5 GreSetStretchBltMode(281~303 行)
c
int FASTCALL GreSetStretchBltMode(HDC hDC, int iStretchMode)
{
...
oSMode = pdcattr->lStretchBltMode;
pdcattr->lStretchBltMode = iStretchMode; /* 原样保存 */
// Wine returns an error here. We set the default.
if ((iStretchMode <= 0) || (iStretchMode > MAXSTRETCHBLTMODE)) iStretchMode = WHITEONBLACK;
pdcattr->jStretchBltMode = iStretchMode; /* 规范化后保存 */
...
return oSMode;
}
- 签名 :
int GreSetStretchBltMode(HDC, int),返回旧模式。 - 作用 :设置拉伸位图模式:BLACKONWHITE(1)/WHITEONBLACK(2)/COLORONCOLOR(3)/HALFTONE(4)。影响
StretchBlt/StretchDIBits缩放时的像素取舍算法。 - 实现流程 :
lStretchBltMode无条件保存 API 原始值(即使非法);jStretchBltMode保存规范化值------非法(≤0 或 > MAXSTRETCHBLTMODE)时强制WHITEONBLACK。源码注释明确说明 Wine 此处返回错误,ReactOS 选择"设默认值"的宽容策略。 - 注意 :
MAXSTRETCHBLTMODE未在本文件定义(来自 win32k.h/ntgdi 头),值为 4(HALFTONE)。对应 gdi32.dllSetStretchBltMode。
3.6 GreGetGraphicsMode(305~318 行)
- 读取
dc->pdcattr->iGraphicsMode,返回GM_COMPATIBLE(1)/GM_ADVANCED(2)。 - 对应 gdi32.dll
GetGraphicsMode。图形模式决定是否启用世界变换(SetWorldTransform 需 GM_ADVANCED)以及文本布局细节。
3.7 DCU_SetDcUndeletable(320~334 行)
c
VOID FASTCALL DCU_SetDcUndeletable(HDC hDC)
{
...
dc->fs |= DC_PERMANANT; /* 标记为不可删除 */
...
}
- 作用 :将 DC 标记为"永久"(
DC_PERMANANT),防止DeleteDC误删(如被窗口 DC(DCE)借用的 DC)。返回 VOID,失败时仅设置错误码。 - 使用方式 :win32k 内部在创建某些共享/持久 DC 时调用。
DeleteDC路径(IntGdiDeleteDC/NtGdiDeleteDC)会检查此位。
3.8 IntSetDefaultRegion(348~410 行)
c
BOOL FASTCALL IntSetDefaultRegion(PDC pdc)
{
IntGdiReleaseRaoRgn(pdc); /* 释放旧 Rao 区域 */
rclWnd.left = 0; rclWnd.top = 0;
rclWnd.right = pdc->dclevel.sizl.cx; /* 表面尺寸 */
rclWnd.bottom = pdc->dclevel.sizl.cy;
rclClip = rclWnd;
if (pdc->ppdev->flFlags & PDEV_META_DEVICE) /* 元设备:叠加 PDEV 原点 */
{
pSurface = pdc->dclevel.pSurface;
if (pSurface && pSurface->flags & PDEV_SURFACE)
{
rclClip.left += pdc->ppdev->ptlOrigion.x; /* 注意源码拼写 ptlOrigion */
...
}
}
prgn = pdc->prgnVis;
if (prgn && prgn != prgnDefault)
REGION_SetRectRgn(prgn, rclClip.left, rclClip.top, rclClip.right, rclClip.bottom);
else
{
prgn = IntSysCreateRectpRgn(...); /* 新建系统矩形区域 */
pdc->prgnVis = prgn;
}
if (prgn)
{
pdc->ptlDCOrig.x = 0; pdc->ptlDCOrig.y = 0; /* DC 原点复位 */
pdc->erclWindow = rclWnd;
pdc->erclClip = rclClip;
pdc->ptlFillOrigin = pdc->dcattr.ptlBrushOrigin; /* 填充原点 = 画刷原点 */
return TRUE;
}
pdc->prgnVis = prgnDefault; /* 失败回退:默认系统区域 */
return FALSE;
}
- 签名 :
BOOL IntSetDefaultRegion(PDC),DC 已锁定状态下调用(不自行加锁)。 - 作用 :为 DC 建立"默认可见区"------将
prgnVis设置为覆盖整个表面(dclevel.sizl)的矩形区域,并复位 DC 原点、窗口/裁剪矩形、填充原点。用于 DC 初始化(DC_vInitDc)和恢复默认状态(如SaveDC/RestoreDC或清空裁剪后)。 - 实现流程 :①
IntGdiReleaseRaoRgn释放旧的 prgnRao → ② 构造表面矩形 → ③ 元设备特例 :若 PDEV 是元文件设备且表面是 PDEV 表面,裁剪矩形叠加ppdev->ptlOrigion(源码拼写如此,实为 ptlOrigin 的笔误)→ ④ 复用/新建 prgnVis → ⑤ 成功则复位ptlDCOrig、erclWindow、erclClip、ptlFillOrigin→ 返回。 - 注意事项 :
ptlDCOrig复位为 0 意味着窗口滚动等造成的 DC 原点偏移被清除;ptlFillOrigin直接从dcattr.ptlBrushOrigin拷贝(因为此刻 DC 原点为 0,DC_vSetBrushOrigin的公式x + ptlDCOrig.x退化为 x)。
3.9 NtGdiCancelDC(413~418 行)
c
BOOL APIENTRY NtGdiCancelDC(HDC hDC)
{
UNIMPLEMENTED; /* 打印 WARNING: ... is UNIMPLEMENTED!(见 AGENTS.md) */
return FALSE;
}
- 作用 :Windows 的
CancelDC语义是取消指定 DC 中所有未完成的绘制操作(常用于取消长打印作业中的某个 DC)。ReactOS 尚未实现,是纯 stub。 - 注意事项 :调用方(gdi32.dll
CancelDC)会收到 FALSE 并设置相应错误。这是本文件唯一未实现的 NtGdi 入口。
3.10 IntGdiSetHookFlags(421~451 行)
c
WORD APIENTRY IntGdiSetHookFlags(HDC hDC, WORD Flags)
{
...
wRet = dc->fs & DC_DIRTY_RAO; // FIXME: Wrong flag!
DPRINT("DC %p, Flags %04x\n", hDC, Flags);
if (Flags & DCHF_INVALIDATEVISRGN)
dc->fs |= DC_DIRTY_RAO; /* 可见区需更新 */
else if (Flags & DCHF_VALIDATEVISRGN || 0 == Flags)
{
//dc->fs &= ~DC_DIRTY_RAO; /* 被注释:不清除 */
}
...
return wRet;
}
- 签名 :
WORD IntGdiSetHookFlags(HDC, WORD Flags),返回旧标志(当前实现返回fs & DC_DIRTY_RAO,源码注释 "FIXME: Wrong flag!",即返回值语义未定)。 - 作用 :供 ntuser(窗口管理器)通知 ntgdi:窗口的可见区(VisRgn)已失效/有效,需要(或不需要)更新 DC 的可见区域。
- 使用方式与调用点 (ntuser/windc.c):
IntGdiSetHookFlags(Dce->hDC, DCHF_INVALIDATEVISRGN)(187、1158 行):窗口显示/滚动/层次变化后,DCE 的可见区已失效,置DC_DIRTY_RAO,后续任何使用 Rao/可见区的操作(如NtGdiGetBoundsRect732 行、NtGdiGetRandomRgn736 行)都会先调用CLIPPING_UpdateGCRegion(pdc)重建。IntGdiSetHookFlags(Dce->hDC, DCHF_VALIDATEVISRGN)(610、931 行):DCE 刷新完毕后通知。当前实现不 清除DC_DIRTY_RAO(清除代码被注释),即"验证"操作实际是空操作------这是已知简化,可能造成可见区被重复重建,但不影响正确性。
- 注意事项 :
DCHF_INVALIDATEVISRGN=0x0001、DCHF_VALIDATEVISRGN=0x0002 定义于 ntuser/dce.h(与 gdi32 侧 wine/gdi_driver.h 一致)。返回wRet是调用方判断"是否需要更新 VisRgn"的依据(windc.c 610 行if (IntGdiSetHookFlags(...)) bUpdateVisRgn = TRUE)。
3.11 NtGdiGetDCDword(454~554 行)
c
BOOL APIENTRY NtGdiGetDCDword(HDC hDC, UINT u, DWORD *Result)
{
...
switch (u)
{
case GdiGetJournal: break; /* 空操作 */
case GdiGetRelAbs: SafeResult = pdcattr->lRelAbs; break;
case GdiGetBreakExtra: SafeResult = pdcattr->lBreakExtra; break;
case GdiGetCharBreak: SafeResult = pdcattr->cBreak; break;
case GdiGetArcDirection: /* RTL 时取反 */ break;
case GdiGetEMFRestorDc: SafeResult = pdc->dclevel.lSaveDepth; break;
case GdiGetFontLanguageInfo: SafeResult = IntGetFontLanguageInfo(pdc); break;
case GdiGetIsMemDc: SafeResult = pdc->dctype; break;
case GdiGetMapMode: SafeResult = pdcattr->iMapMode; break;
case GdiGetTextCharExtra: SafeResult = pdcattr->lTextExtra; break;
default: EngSetLastError(ERROR_INVALID_PARAMETER); Ret = FALSE; break;
}
if (Ret)
{
_SEH2_TRY { ProbeForWrite(Result, sizeof(DWORD), 1); *Result = SafeResult; }
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); }
_SEH2_END;
if (!NT_SUCCESS(Status)) { SetLastNtError(Status); Ret = FALSE; }
}
DC_UnlockDc(pdc);
return Ret;
}
- 签名 :
BOOL NtGdiGetDCDword(HDC hDC, UINT u, DWORD *Result)。u是属性代号(GdiGet*枚举),Result为用户态输出指针。 - 作用 :按属性代号读取 DC 的一个 DWORD 属性。这是 NT 3.1 时代遗留的"通用 DC 属性查询"接口,主要供老式驱动/兼容代码与 gdi32 内部使用(user32 的
GetArcDirection等最终也走这类通道)。 - 各 case 语义 :
-
GdiGetJournal:无属性,仅占位(break 不设置 SafeResult,保持 0)。 -
GdiGetRelAbs:读取lRelAbs(相对/绝对坐标标志,MoveToEx 等维护)。 -
GdiGetBreakExtra/GdiGetCharBreak:读取lBreakExtra(断字额外间距)与cBreak(断字字符数),均为文本排版属性。 -
GdiGetArcDirection:读取弧线方向AD_COUNTERCLOCKWISE(1)/AD_CLOCKWISE(2)。实现与dclevel.flPath & DCPATH_CLOCKWISE相关:cif (pdcattr->dwLayout & LAYOUT_RTL) SafeResult = AD_CLOCKWISE - ((pdc->dclevel.flPath & DCPATH_CLOCKWISE) != 0); else SafeResult = ((pdc->dclevel.flPath & DCPATH_CLOCKWISE) != 0) + AD_COUNTERCLOCKWISE;RTL 布局下方向取反(顺时针↔逆时针互换)。
-
GdiGetEMFRestorDc:读取dclevel.lSaveDepth(SaveDC 嵌套深度,EMF 播放用)。 -
GdiGetFontLanguageInfo:调用IntGetFontLanguageInfo(pdc)(text.h,返回字体语言能力位,如 FLI_GLYPHS)。 -
GdiGetIsMemDc:返回dctype(0=DIRECT,1=MEMORY,2=INFO),即查询"是否为内存 DC"。 -
GdiGetMapMode:返回iMapMode(与GreGetMapMode等价)。 -
GdiGetTextCharExtra:返回lTextExtra(SetTextCharacterExtra 设置的字符间距)。 -
default:非法代号 →
ERROR_INVALID_PARAMETER+ FALSE。
-
- 实现流程 :① 参数校验(
Result为 NULL 直接失败)→ ② 锁定 DC → ③ switch 取值到本地SafeResult→ ④ SEH 保护写回 :ProbeForWrite(Result, sizeof(DWORD), 1)校验用户缓冲可写后赋值;异常捕获 NTSTATUS 经SetLastNtError传播 → ⑤ 解锁返回。 - 注意事项 :所有属性值先在内核本地 计算(
SafeResult),再一次性写回用户态------避免"锁内多次触碰用户内存"。IntGetFontLanguageInfo需要访问字体对象,调用时 DC 仍持锁。
3.12 NtGdiGetAndSetDCDword(556~690 行)
c
_Success_(return != FALSE)
BOOL APIENTRY NtGdiGetAndSetDCDword(_In_ HDC hdc, _In_ UINT u,
_In_ DWORD dwIn, _Out_ DWORD *pdwResult)
{
...
switch (u)
{
case GdiGetSetEPSPrintingEscape:
SafeResult = pdc->fs & DC_EPSPRINTINGESCAPE; /* 读 */
pdc->fs &= ~DC_EPSPRINTINGESCAPE; /* 且清除(一次性) */
break;
case GdiGetSetCopyCount:
SafeResult = pdc->ulCopyCount;
pdc->ulCopyCount = dwIn; /* 写 */
break;
case GdiGetSetTextAlign:
SafeResult = pdcattr->lTextAlign;
pdcattr->lTextAlign = dwIn;
// pdcattr->flTextAlign = dwIn; // Flags! /* 注释:不更新 flTextAlign */
break;
case GdiGetSetRelAbs: SafeResult = pdcattr->lRelAbs; pdcattr->lRelAbs = dwIn; break;
case GdiGetSetTextCharExtra:SafeResult = pdcattr->lTextExtra;pdcattr->lTextExtra = dwIn; break;
case GdiGetSetSelectFont: break; /* 空 */
case GdiGetSetMapperFlagsInternal:
if (dwIn & ~1) { EngSetLastError(ERROR_INVALID_PARAMETER); Ret = FALSE; break; }
SafeResult = pdcattr->flFontMapper; pdcattr->flFontMapper = dwIn;
break;
case GdiGetSetMapMode: SafeResult = IntGdiSetMapMode(pdc, dwIn); break;
case GdiGetSetArcDirection: /* 校验 + RTL 感知的方向设置 */ break;
default: EngSetLastError(ERROR_INVALID_PARAMETER); Ret = FALSE; break;
}
if (Ret) { /* SEH: ProbeForWrite + 写回 pdwResult,异常经 SetLastNtError 传播 */ }
DC_UnlockDc(pdc);
return Ret;
}
- 签名 :
BOOL NtGdiGetAndSetDCDword(HDC, UINT u, DWORD dwIn, DWORD *pdwResult)。与NtGdiGetDCDword的区别:读旧值的同时写入新值 ("Get-And-Set" 原子语义,类似InterlockedExchange的 DC 属性版)。 - 作用 :NT 遗留的 DC 属性"交换"接口,供 gdi32 内部(如
SetTextAlign的实现)一次系统调用完成"返回旧值 + 设置新值",避免 Get/Set 两次调用之间的竞态。 - 各 case 语义 :
-
GdiGetSetEPSPrintingEscape:读取fs & DC_EPSPRINTINGESCAPE并清除该位(一次性读清,语义为"消费一个 EPS 转义事件")。 -
GdiGetSetCopyCount:读ulCopyCount(打印份数),写dwIn。 -
GdiGetSetTextAlign:读lTextAlign、写lTextAlign = dwIn。注意注释// pdcattr->flTextAlign = dwIn; // Flags!------源码明确不更新flTextAlign(RTL 修正后的对齐位),这是一个已知不完整处:走此通道设置文本对齐不会产生 RTL 镜像修正(IntGdiSetTextAlign才会)。 -
GdiGetSetRelAbs/GdiGetSetTextCharExtra:对称读写lRelAbs/lTextExtra。 -
GdiGetSetSelectFont:空操作(占位,字体选择另有专路)。 -
GdiGetSetMapperFlagsInternal:设置字体映射标志flFontMapper。校验 :dwIn & ~1非零(即除位 0 外还有其它位)→ERROR_INVALID_PARAMETER+ FALSE;合法则返回旧值并写入新值。 -
GdiGetSetMapMode:核心 case ------直接调用IntGdiSetMapMode(pdc, dwIn)(coord.c 826~911 行),返回值(旧映射模式)作为SafeResult。注意IntGdiSetMapMode返回旧模式且失败返回 0;此处不额外校验。 -
GdiGetSetArcDirection:带参数校验与 RTL 感知的设置弧线方向:cif (dwIn != AD_COUNTERCLOCKWISE && dwIn != AD_CLOCKWISE) { /* ERROR_INVALID_PARAMETER; Ret=FALSE; */ } if (pdcattr->dwLayout & LAYOUT_RTL) { SafeResult = AD_CLOCKWISE - ((pdc->dclevel.flPath & DCPATH_CLOCKWISE) != 0); if (dwIn == AD_CLOCKWISE) { pdc->dclevel.flPath &= ~DCPATH_CLOCKWISE; break; } pdc->dclevel.flPath |= DCPATH_CLOCKWISE; } else { SafeResult = ((pdc->dclevel.flPath & DCPATH_CLOCKWISE) != 0) + AD_COUNTERCLOCKWISE; if (dwIn == AD_COUNTERCLOCKWISE) { pdc->dclevel.flPath &= ~DCPATH_CLOCKWISE; break; } pdc->dclevel.flPath |= DCPATH_CLOCKWISE; }方向状态实际存储在
dclevel.flPath & DCPATH_CLOCKWISE位中:设置AD_COUNTERCLOCKWISE(LTR 下)清位、AD_CLOCKWISE置位;RTL 布局下两个方向的操作互换,且返回旧值时也做了 RTL 取反。
-
- 实现流程 :① 校验
pdwResult非空 → ② 锁定 → ③ switch(读旧值→写新值,顺序因 case 而异)→ ④ SEH 保护写回pdwResult→ ⑤ 解锁。 - 注意事项 :本函数与
NtGdiGetDCDword覆盖的属性集合不重叠 (Get 版有 GdiGetJournal/ArcDirection/EMFRestorDc/FontLanguageInfo/IsMemDc/MapMode,GetAndSet 版有 EPS/CopyCount/TextAlign/RelAbs/TextCharExtra/SelectFont/MapperFlags/MapMode/ArcDirection)。共享的GdiGetSetMapMode/GdiGetSetArcDirection说明部分属性只有"读写一体"通道。
3.13 IntUpdateBoundsRect(692~704 行)
c
VOID FASTCALL IntUpdateBoundsRect(PDC pdc, PRECTL pRect)
{
if (pdc->fs & DC_ACCUM_APP)
RECTL_bUnionRect(&pdc->erclBoundsApp, &pdc->erclBoundsApp, pRect);
if (pdc->fs & DC_ACCUM_WMGR)
RECTL_bUnionRect(&pdc->erclBounds, &pdc->erclBounds, pRect);
}
-
签名 :
VOID IntUpdateBoundsRect(PDC, PRECTL),DC 已锁定,矩形为设备坐标(由调用方保证)。 -
作用 :把新矩形并入 DC 的累计边界矩形。**边界矩形(BoundsRect)**是 GDI 提供的"自上次查询以来本 DC 绘制过的区域外接框"服务(
GetBoundsRect/SetBoundsRect,见《分析_31》),用于应用程序减少重绘范围(先查边界框,只重画受影响区域)。 -
实现流程 :按
fs中启用的累计目标分别求并集:DC_ACCUM_APP(应用累计,SetBoundsRect默认启用)→erclBoundsApp(逻辑坐标语义,但调用方传入设备坐标矩形)。DC_ACCUM_WMGR(窗口管理器累计,SetBoundsRect(hdc, NULL, DCB_WINDOWMGR|DCB_ENABLE)启用)→erclBounds(设备坐标,供 win32k 内部使用)。
-
使用方式 :被 ntgdi 各绘制函数调用,如
NtGdiSetPixel(bitblt.c 1493~1507 行)在绘制 1×1 像素前:cif (pdc->fs & (DC_ACCUM_APP|DC_ACCUM_WMGR)) { RECTL_vSetRect(&rcDst, x, y, x+1, y+1); IntLPtoDP(pdc, (LPPOINT)&rcDst, 2); /* 逻辑→设备 */ rcDst.left += pdc->ptlDCOrig.x; ... /* 叠加 DC 原点 */ IntUpdateBoundsRect(pdc, &rcDst); }line.c(60 行)、fillshap.c等绘制路径同样在输出前调用。 -
注意事项 :
RECTL_bUnionRect(rect.c 16~51 行)对空矩形(left>=right 或 top>=bottom)有特殊处理:两空→置空返回 FALSE;一空一非空→直接拷贝非空者;两个非空→逐边 min/max。
3.14 NtGdiGetBoundsRect(706~797 行)
c
DWORD APIENTRY NtGdiGetBoundsRect(IN HDC hdc, OUT LPRECT prc, IN DWORD flags)
{
...
if (!(pdc = DC_LockDc(hdc))) return 0;
if (!(flags & DCB_WINDOWMGR)) /* ── APP 路径 ── */
{
rc = pdc->erclBoundsApp;
if (RECTL_bIsEmptyRect(&rc)) /* 空:报告 RESET */
{
rc.left = rc.top = rc.right = rc.bottom = 0;
ret = DCB_RESET;
}
else
{
RECTL rcRgn;
if (pdc->fs & DC_DIRTY_RAO) CLIPPING_UpdateGCRegion(pdc); /* 可见区重建 */
if (!REGION_GetRgnBox(pdc->prgnRao, &rcRgn)) /* 先 Rao */
REGION_GetRgnBox(pdc->prgnVis, &rcRgn); /* 回退 Vis */
rc.left = max( rc.left, 0 ); /* 与表面裁剪 */
rc.top = max( rc.top, 0 );
rc.right = min( rc.right, rcRgn.right );
rc.bottom = min( rc.bottom, rcRgn.bottom );
ret = DCB_SET;
}
IntDPtoLP(pdc, (PPOINTL)&rc, 2); /* 设备→逻辑 */
if (pdc->pdcattr->dwLayout & LAYOUT_RTL) /* RTL:交换左右边界 */
{
LONG temp = rc.left;
rc.left = rc.right;
rc.right = temp;
rc.right--; /* 独占右边界修正 */
}
}
else /* ── WMGR 路径 ── */
{
rc = pdc->erclBounds;
ret = DCB_SET;
}
_SEH2_TRY { ProbeForWrite(prc, sizeof(RECT), 1); *prc = rc; }
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { ret = 0; }
_SEH2_END;
if (flags & DCB_RESET) /* 查询后复位累计 */
{
if (!(flags & DCB_WINDOWMGR)) { pdc->erclBoundsApp.left = pdc->erclBoundsApp.top = INT_MAX;
pdc->erclBoundsApp.right = pdc->erclBoundsApp.bottom = INT_MIN; }
else { pdc->erclBounds.left = pdc->erclBounds.top = INT_MAX;
pdc->erclBounds.right = pdc->erclBounds.bottom = INT_MIN; }
}
DC_UnlockDc(pdc);
return ret;
}
- 签名 :
DWORD NtGdiGetBoundsRect(HDC, LPRECT prc, DWORD flags)。flags可取DCB_WINDOWMGR(查询窗口管理器累计)与DCB_RESET(查询后复位累计)。返回值是DCB_SET(1)/DCB_RESET(2)/DCB_ENABLE(4)/DCB_DISABLE(8) 的组合位(语义:当前边界是否有效/是否已复位)。 - 作用 :返回自上次(重置后)以来 DC 的绘制外接矩形。对应 gdi32.dll
GetBoundsRect。 - 实现流程(APP 路径) :① 取
erclBoundsApp→ ② 空矩形判定 (RECTL_bIsEmptyRect:left>=right 或 top>=bottom):空 → 全 0 +DCB_RESET;非空 → ③ 裁剪到可见区域 :若DC_DIRTY_RAO先CLIPPING_UpdateGCRegion重建 Rao 区,取prgnRao的包围盒,失败回退prgnVis,再将累计边界与表面区域相交(max(...,0)/min(..., rcRgn.*))→ ④ 坐标转换 :IntDPtoLP把设备坐标转回逻辑坐标(边界累计在设备坐标下进行,返回给应用时按当前变换换算)→ ⑤ RTL 修正 :LAYOUT_RTL 下交换 left/right 并right--(源码注释解释:RTL 下原左边界像素坐标成为独占右边界 ,需减 1 以符合"右开"矩形语义)→ ⑥ 返回DCB_SET。 - 实现流程(WMGR 路径) :直接取
erclBounds(设备坐标,不转换、不裁剪)→DCB_SET。窗口管理器(ntuser)用此路径获取自身绘制活动范围。 - 实现流程(尾部公共) :SEH 保护写回
prc(异常则 ret=0)→ 若flags & DCB_RESET,把对应累计矩形重置为"空集":left/top=INT_MAX、right/bottom=INT_MIN(这样下一次RECTL_bUnionRect的第一个矩形会直接拷贝进累计区)→ 解锁返回。 - 注意事项 :① APP 路径的裁剪语义意味着返回的矩形不超出 当前可见/表面区域;② 空边界报告为全 0 +
DCB_RESET(而不是DCB_SET),调用方据此知道"尚无绘制发生";③IntDPtoLP与 RTL 修正顺序不可颠倒------先按设备坐标转逻辑,再按 RTL 语义调整边界方向。
3.15 NtGdiSetBoundsRect(799~878 行)
c
DWORD APIENTRY NtGdiSetBoundsRect(IN HDC hdc, IN LPRECT prc, IN DWORD flags)
{
DWORD ret;
...
/* ① 参数校验:ENABLE 与 DISABLE 互斥 */
if ((flags & DCB_ENABLE) && (flags & DCB_DISABLE)) return 0;
if (!(pdc = DC_LockDc(hdc))) return 0;
/* ② 构造返回值:当前累计状态 + 边界有效性 */
ret = DCB_RESET; /* we don't have device-specific bounds */
ret = (pdc->fs & (DC_ACCUM_APP|DC_ACCUM_WMGR) ? DCB_ENABLE : DCB_DISABLE) |
(RECTL_bIsEmptyRect(&pdc->erclBoundsApp) ? ret & DCB_SET : DCB_SET );
ret |= (flags & DCB_WINDOWMGR);
/* ③ 复位累计 */
if (flags & DCB_RESET) { /* 同 NtGdiGetBoundsRect 的重置逻辑 */ }
/* ④ 累计新矩形 */
if (flags & DCB_ACCUMULATE && prc != NULL)
{
_SEH2_TRY { ProbeForRead(prc, sizeof(RECT), 1); rcl = *prc; }
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { DC_UnlockDc(pdc); _SEH2_YIELD(return 0;); }
_SEH2_END;
RECTL_vMakeWellOrdered(&rcl); /* 归一化方向 */
if (!(flags & DCB_WINDOWMGR))
{
IntLPtoDP( pdc, (POINT *)&rcl, 2 ); /* 逻辑→设备 */
RECTL_bUnionRect(&pdc->erclBoundsApp, &pdc->erclBoundsApp, &rcl);
}
else
RECTL_bUnionRect(&pdc->erclBounds, &pdc->erclBounds, &rcl);
}
/* ⑤ 启用/禁用累计 */
if (flags & DCB_ENABLE)
{ if (!(flags & DCB_WINDOWMGR)) pdc->fs |= DC_ACCUM_APP; else pdc->fs |= DC_ACCUM_WMGR; }
if (flags & DCB_DISABLE)
{ if (!(flags & DCB_WINDOWMGR)) pdc->fs &= ~DC_ACCUM_APP; else pdc->fs &= ~DC_ACCUM_WMGR; }
DC_UnlockDc(pdc);
return ret;
}
- 签名 :
DWORD NtGdiSetBoundsRect(HDC, LPRECT prc, DWORD flags)。flags为DCB_ACCUMULATE(2)/DCB_RESET(4)/DCB_ENABLE(8)/DCB_DISABLE(16)/DCB_WINDOWMGR(32) 的组合。对应 gdi32.dllSetBoundsRect。 - 作用 :控制边界矩形的累计开关 (ENABLE/DISABLE)、手动累计 (ACCUMULATE,把 prc 并入累计区)、复位 (RESET)。
prc为 NULL 时仅执行开关/复位操作。 - 实现流程 :① 校验 ENABLE 与 DISABLE 同传 → 返回 0 → ② 锁定 → ③ 构造返回值:
DCB_ENABLE(若任一累计已启用)或DCB_DISABLE(都未启用),并合并DCB_SET(erclBoundsApp非空时;空时表达式ret & DCB_SET------初始 ret=DCB_RESET(2),2 & 1 = 0,故空时返回不含 DCB_SET);随后ret |= (flags & DCB_WINDOWMGR)保持调用方的 WMGR 位 → ④DCB_RESET时重置累计矩形 → ⑤DCB_ACCUMULATE && prc时:SEH 读用户矩形 →RECTL_vMakeWellOrdered(保证 left<=right、top<=bottom,见 rect.c 79~97 行)→ APP 路径先IntLPtoDP(应用传入的是逻辑坐标)再并集到erclBoundsApp;WMGR 路径直接并集到erclBounds(WMGR 传入设备坐标)→ ⑥DCB_ENABLE/DCB_DISABLE设置fs对应位 → ⑦ 解锁返回 ret。 - 注意事项 :① 注意 APP 与 WMGR 两路径的坐标约定相反 :
IntUpdateBoundsRect(绘制路径调用)与NtGdiGetBoundsRect(WMGR 读)使用设备坐标;而NtGdiSetBoundsRect的 APP 路径把用户矩形当逻辑坐标 先转设备再累计,WMGR 路径则直接按设备坐标处理------三处必须一致,否则边界会偏移;② 返回值与GetBoundsRect一致,可在GetBoundsRect(hdc, NULL, DCB_RESET)之外直接通过返回值判断累计开关状态。
3.16 TranslateCOLORREF(880~962 行)
c
/* Translates a COLORREF to the right color in the specified DC color space */
ULONG TranslateCOLORREF(PDC pdc, COLORREF crColor)
{
PSURFACE psurfDC = pdc->dclevel.pSurface;
PPALETTE ppalDC;
ULONG index, ulColor;
EXLATEOBJ exlo;
if (!psurfDC) psurfDC = psurfDefaultBitmap; /* 无表面→默认位图 */
/* ① COLORREF 类型分类(高字节标志位) */
if ((crColor & 0xFF000000) == 0)
{ /* 纯 RGB:直接使用 */ }
else if (crColor & 0x01000000) /* PALETTEINDEX */
{
index = crColor & 0xFFFF;
ppalDC = pdc->dclevel.ppal;
if (index >= ppalDC->NumColors) index = 0;
crColor = PALETTE_ulGetRGBColorFromIndex(ppalDC, index);
}
else if (crColor & 0x02000000) /* PALETTERGB */
{
if (pdc->dclevel.hpal != StockObjects[DEFAULT_PALETTE])
{
ppalDC = pdc->dclevel.ppal;
index = PALETTE_ulGetNearestIndex(ppalDC, crColor & 0xFFFFFF);
crColor = PALETTE_ulGetRGBColorFromIndex(ppalDC, index);
}
else { crColor = crColor & 0x00FFFFFF; } /* 默认调色板→原样 RGB */
}
else if ((crColor & 0x10FF0000) == 0x10FF0000) /* DIBINDEX */
{
if (psurfDC->ppal->flFlags & PAL_INDEXED) /* 表面为索引色 */
{
index = crColor & 0xFF;
if (index >= psurfDC->ppal->NumColors) index = 0;
return index; /* 直接返回调色板索引 */
}
else { crColor = crColor & 0x00FFFFFF; }
}
else { DPRINT("Invalid COLORREF 0x08X\n", crColor); crColor = 0; }
/* ② 用 RGB→表面调色板的 XLATEOBJ 做最终换算 */
EXLATEOBJ_vInitialize(&exlo, &gpalRGB, psurfDC->ppal, CLR_INVALID, CLR_INVALID, CLR_INVALID);
ulColor = XLATEOBJ_iXlate(&exlo.xlo, crColor);
EXLATEOBJ_vCleanup(&exlo);
return ulColor;
}
- 签名 :
ULONG TranslateCOLORREF(PDC pdc, COLORREF crColor),DC 已锁定。返回目标表面格式下的颜色值(可能是索引、16/32 位 RGB 等)。 - 作用 :把用户态的
COLORREF翻译成"当前 DC 表面像素格式下的颜色值"。这是 SetPixel 等操作的第一步------内核中所有绘制颜色最终都要落到表面的具体位格式。 - COLORREF 编码回顾 (高字节标志):
0x00RRGGBB:直接 RGB。0x0100xxxx(PALETTEINDEX):低 16 位是调色板索引,用 DC 调色板把索引换算回 RGB(越界索引归 0)。0x0200RRGGBB(PALETTERGB):先经 DC 调色板找最近色索引再取回 RGB(相当于把颜色"贴"到 DC 调色板上);若 DC 用默认调色板则直接取 RGB 低 24 位。0x10FFxxxx(DIBINDEX,注意是0x10FF0000全掩码匹配):低 8 位是表面调色板索引;仅当表面为索引色(PAL_INDEXED) 时直接返回该索引(跳过 XLATE),否则按 RGB 处理。- 其它(含
0xFF000000未定义位):打印Invalid COLORREF并当黑色处理。
- 实现流程 :① 表面缺省为
psurfDefaultBitmap(全局默认位图,保证索引色/位深信息可用)→ ② 分类转换得到 RGB → ③EXLATEOBJ_vInitialize(&exlo, &gpalRGB, psurfDC->ppal, CLR_INVALID×3):构建"标准 RGB 调色板 → 表面调色板"的转换对象(第三个参数起是背景/前景色,用 CLR_INVALID 表示不用)→ ④XLATEOBJ_iXlate(&exlo.xlo, crColor)执行转换 → ⑤EXLATEOBJ_vCleanup释放 → 返回。 - 使用方式 :
NtGdiSetPixel(bitblt.c 1510 行)用它把 COLORREF 转为表面色后经IntPatBlt绘制;SetBrushOrg/文本等路径用EBRUSHOBJ内部的同机制。反向转换(表面色→RGB)在NtGdiSetPixel尾部用"表面调色板→gpalRGB"的反向 XLATEOBJ 完成。 - 注意事项 :
EXLATEOBJ_vInitialize与XLATEOBJ_iXlate属于 GDI 引擎(win32ss/gdi/eng/xlateobj.*),详见《分析_19》。gpalRGB定义于 palette.c(PALETTE gpalRGB, gpalBGR, ...),是 24 位 RGB 的全局调色板常量。
4. 机制专题
4.1 脏标记机制(Dirty Flags)与画刷重建
脏标记是 dcutil.c 所有"写属性"函数的统一出口。其生命周期分为四步:
① 写属性(dcutil.c)
IntGdiSetBkColor / IntGdiSetTextColor / IntSetDCBrushColor / IntSetDCPenColor
│ 置位
▼
② ulDirty_ |= DIRTY_XXX
│
├─ IntGdiSetTextColor ──► 立即调用 DC_vUpdateTextBrush/LineBrush/FillBrush(同步消费)
└─ 其余 ──► 等待下次绘制前(惰性消费)
│
▼
③ 消费(dcobjs.c)
DC_vUpdateFillBrush : hbrush 变化? 换 pbrFill;置脏则 EBRUSHOBJ_vUpdateFromDC;
DC_BRUSH 则 EBRUSHOBJ_vSetSolidRGBColor(crBrushClr);清 DIRTY_FILL|DC_BRUSH_DIRTY
DC_vUpdateLineBrush : 对称;DC_PEN 则设置 crPenClr;清 DIRTY_LINE|DC_PEN_DIRTY
DC_vUpdateTextBrush : 置脏则 EBRUSHOBJ_vUpdateFromDC(pbrDefaultBrush);
无条件 EBRUSHOBJ_vSetSolidRGBColor(crForegroundClr);清 DIRTY_TEXT
DC_vUpdateBackgroundBrush: 置脏则 EBRUSHOBJ_vUpdateFromDC(pbrDefaultBrush);
无条件 EBRUSHOBJ_vSetSolidRGBColor(crBackgroundClr);清 DIRTY_BACKGROUND
│
▼
④ EBRUSHOBJ(驱动接口)重建
EBRUSHOBJ_vUpdateFromDC(ntgdi/ebrushobj.c)→ 按需调用驱动 DrvRealizeBrush / 生成模式画刷
为什么文本色走"立即重建"而背景色走"惰性重建"? 观察源码:
IntGdiSetTextColor在锁内直接调用三个DC_vUpdate*------文本色在ExtTextOut路径中几乎每次都要用,且DC_vUpdateTextBrush本身是"置脏才重建 EBRUSHOBJ、无条件设纯色、清位"三步,代价极低,立即执行反而省去绘制路径的重复检查。IntGdiSetBkColor置位后不重建背景画刷,只通过"重选当前画刷"(NtGdiSelectBrush)顺带 重建填充画刷;背景画刷(eboBackground)的重建推迟到实际需要背景色的路径(如虚线、文本背景)首次使用DC_vUpdateBackgroundBrush时。IntSetDCBrushColor/IntSetDCPenColor则完全依赖惰性消费------它们连重选都不做,因为DC_vUpdateFillBrush/LineBrush的 DC_BRUSH/DC_PEN 分支会在下次绘制时把新颜色写入。
"cr* 与 ul* 双份颜色"的作用 :ul* 是无条件写入的(API 总能看到最新值),cr* 是"上次已生效值"。比较二者可消除重复置位 :连续两次 SetBkColor(同色) 只有第一次触发脏位,第二次直接短路。这是性能优化,也是脏标记机制的正确性基础------GetBkColor 读 ulBackgroundClr 永远返回用户设置值,而内部画刷只跟随 crBackgroundClr 变化。
关键位一览(置位者 → 消费者):
| 脏位 | 置位者(本文件) | 消费者(dcobjs.c) |
|---|---|---|
| DIRTY_FILL (0x1) | IntGdiSetBkColor、IntGdiSetTextColor、IntSetDCBrushColor | DC_vUpdateFillBrush |
| DIRTY_LINE (0x2) | IntGdiSetBkColor、IntGdiSetTextColor、IntSetDCPenColor | DC_vUpdateLineBrush |
| DIRTY_TEXT (0x4) | IntGdiSetTextColor | DC_vUpdateTextBrush |
| DIRTY_BACKGROUND (0x8) | IntGdiSetBkColor | DC_vUpdateBackgroundBrush |
4.2 LPtoDP/DPtoLP:坐标变换链
GreLPtoDP/GreDPtoLP 只是薄壳,真正的逻辑在 coord.h 的内联函数与 coord.c 的矩阵维护函数中。
三级变换模型:GDI 的坐标体系是"世界坐标 → 页面坐标 → 设备坐标":
世界坐标 (World) 页面坐标 (Page) 设备坐标 (Device)
(x,y) ──世界变换──► (x',y') ──页→设备变换──► (x'',y'')
SetWorldTransform 窗口原点/视口原点、
(GM_ADVANCED 才可用) 窗口范围/视口范围、MM_* 映射模式
三个矩阵缓存在 DC_ATTR/DCLEVEL 中:
mxWorldToPage(世界→页面):SetWorldTransform设置的 XFORM(仅在 GM_ADVANCED 下非恒等)。mxWorldToDevice(世界→设备):DC_vUpdateWorldToDevice把mxWorldToPage与"页→设备"矩阵合成。mxDeviceToWorld(设备→世界):DC_vUpdateDeviceToWorld对mxWorldToDevice求逆。
惰性更新协议(coord.h):
c
FORCEINLINE PMATRIX DC_pmxWorldToDevice(PDC pdc)
{
if (pdc->pdcattr->flXform & (PAGE_XLATE_CHANGED|PAGE_EXTENTS_CHANGED|WORLD_XFORM_CHANGED))
DC_vUpdateWorldToDevice(pdc); /* 任一相关位变化→重算合成矩阵 */
return &pdc->pdcattr->mxWorldToDevice;
}
FORCEINLINE PMATRIX DC_pmxDeviceToWorld(PDC pdc)
{
if (pdc->pdcattr->flXform & DEVICE_TO_WORLD_INVALID)
DC_vUpdateDeviceToWorld(pdc); /* 逆矩阵失效→重算 */
return &pdc->pdcattr->mxDeviceToWorld;
}
DC_vUpdateWorldToDevice(coord.c 182~200 行)的实现:用 DC_vGetPageToDevice 构造页→设备矩阵 mxPageToDevice,再 XFORMOBJ_iCombine(&xoWorldToDevice, &xoWorldToPage, &xoPageToDevice) 合成,最后清 WORLD_XFORM_CHANGED。DC_vUpdateDeviceToWorld(coord.c 204 行起)则用 XFORMOBJ_iInverse 求逆。
IntLPtoDP / IntDPtoLP(coord.h 179~197 行):
c
static inline BOOLEAN IntLPtoDP(DC* pdc, PPOINTL ppt, UINT count)
{
DC_vUpdateWorldToDevice(pdc); /* 保证矩阵最新 */
return INTERNAL_LPTODP(pdc, (LPPOINT)ppt, count);
}
static inline BOOLEAN IntDPtoLP(DC* pdc, PPOINTL ppt, UINT count)
{
DC_vUpdateDeviceToWorld(pdc); /* 保证逆矩阵最新 */
return INTERNAL_DPTOLP(pdc, (LPPOINT)ppt, count);
}
INTERNAL_APPLY_MATRIX(coord.h 10~42 行):核心矩阵乘法,用 FLOATOBJ(定点浮点,避免浮点环境切换)逐点计算:
c
/* x' = x * M11 + y * M21 + Dx */
/* y' = x * M12 + y * M22 + Dy */
FLOATOBJ_SetLong(&x, points[count].x);
FLOATOBJ_Mul(&x, &matrix->efM11); /* x*M11 */
tmp = matrix->efM21; FLOATOBJ_MulLong(&tmp, points[count].y); /* y*M21 */
FLOATOBJ_Add(&x, &tmp);
FLOATOBJ_Add(&x, &matrix->efDx); /* +Dx */
... 同样处理 y 分量 ...
FLOATOBJ_bConvertToLong(&x, &points[count].x); /* 定点→整数,溢出返回 FALSE */
MIN_COORD/MAX_COORD(INT_MIN/16、INT_MAX/16)是坐标空间的安全边界,防止 FLOATOBJ 中间结果溢出。
MM_ISOTROPIC 的特殊处理 (coord.h DC_pszlViewportExt):各向同性映射要求 X/Y 缩放一致,当 PAGE_EXTENTS_CHANGED 且 iMapMode == MM_ISOTROPIC 时,读取视口范围前调用 DC_vFixIsotropicMapping(pdc) 修正(按设备像素纵横比调整)。
触发矩阵失效的源头 (均在 coord.c):IntGdiSetMapMode(置 PAGE_XLATE_CHANGED|PAGE_EXTENTS_CHANGED|INVALIDATE_ATTRIBUTES|DEVICE_TO_PAGE_INVALID|WORLD_XFORM_CHANGED|DEVICE_TO_WORLD_INVALID)、GreSetViewportOrgEx(置 PAGE_XLATE_CHANGED|WORLD_XFORM_CHANGED|DEVICE_TO_WORLD_INVALID)、SetWindowExtEx(置 PAGE_EXTENTS_CHANGED|...)等。dcutil.c 中唯一直接触碰该机制的入口是 NtGdiGetAndSetDCDword 的 GdiGetSetMapMode case(IntGdiSetMapMode),以及 NtGdiGetBoundsRect/NtGdiSetBoundsRect 中通过 IntDPtoLP/IntLPtoDP 间接触发的矩阵更新。
4.3 SetPixel 颜色转换(EXLATEOBJ 双向)
任务中提到的 IntGdiSetPixel/IntGdiGetPixel 并不存在于 dcutil.c (源码核对确认)。SetPixel/GetPixel 的 NtGdi 入口位于 bitblt.c(NtGdiSetPixel 1463 行、NtGdiGetPixel 1551 行),它们正是 dcutil.c 中 TranslateCOLORREF 与 IntUpdateBoundsRect 的主要消费者:
NtGdiSetPixel 完整流程(bitblt.c 1463~1549 行):
SetPixel(hdc, x, y, crColor)
│
├─ DC_LockDc;无表面(空内存 DC/信息 DC)→ CLR_INVALID
│
├─ 边界累计:若 fs & (DC_ACCUM_APP|DC_ACCUM_WMGR)
│ RECTL_vSetRect(&rcDst, x, y, x+1, y+1)
│ IntLPtoDP(pdc, (LPPOINT)&rcDst, 2) ← coord.h 变换
│ rcDst ± ptlDCOrig
│ IntUpdateBoundsRect(pdc, &rcDst) ← dcutil.c
│
├─ iSolidColor = TranslateCOLORREF(pdc, crColor) ← dcutil.c(RGB/索引→表面色)
│
├─ pebo = &pdc->eboText (复用文本画刷:恒为纯色)
│ iOldColor = EBRUSHOBJ_iSetSolidColor(pebo, iSolidColor)
│ ulDirty = pdcattr->ulDirty_; 清 DIRTY_TEXT (临时借用,事后恢复)
│ bResult = IntPatBlt(pdc, x, y, 1, 1, PATCOPY, pebo) ← 1×1 填充
│ EBRUSHOBJ_iSetSolidColor(pebo, iOldColor); 恢复 ulDirty_
│
├─ 反向转换(表面色→RGB,返回给调用方)
│ EXLATEOBJ_vInitialize(&exlo, surf->ppal, &gpalRGB, ...)
│ crColor = XLATEOBJ_iXlate(&exlo.xlo, iSolidColor)
│
└─ 返回 bResult ? crColor : CLR_INVALID
关键技巧:借用 eboText 做纯色画刷 ------文本画刷是"恒为纯色、永远存在"的 EBRUSHOBJ,临时改色绘制 1×1 后立即恢复,避免了新建/销毁画刷的开销;同时用"保存-恢复"ulDirty_ 的方式保护 DIRTY_TEXT 位不被 IntPatBlt 内部的 DC_vUpdateTextBrush 消费。TranslateCOLORREF 在其中负责正方向 (COLORREF→表面格式),反向 XLATEOBJ 负责反方向(表面格式→RGB)。
NtGdiGetPixel 则是把 (x,y) 的 1×1 区域经 IntEngBitBlt(SRCCOPY)拷贝到临时 1×1 DIB 表面,再经反向 XLATEOBJ 得到 RGB 返回。
4.4 边界矩形(BoundsRect)机制
边界矩形系统由 dcutil.c 的四个函数 + 两个状态字段构成:
| 组件 | 类型 | 坐标 | 写入者 | 读取者 |
|---|---|---|---|---|
erclBoundsApp |
DC 字段(RECTL) | 设备坐标累计,返回时转逻辑 | IntUpdateBoundsRect(绘制路径)、NtGdiSetBoundsRect(APP 累计) |
NtGdiGetBoundsRect(APP 路径) |
erclBounds |
DC 字段(RECTL) | 设备坐标 | IntUpdateBoundsRect(绘制路径)、NtGdiSetBoundsRect(WMGR 累计) |
NtGdiGetBoundsRect(WMGR 路径) |
DC_ACCUM_APP |
fs 位 | --- | NtGdiSetBoundsRect(DCB_ENABLE/DISABLE) |
IntUpdateBoundsRect |
DC_ACCUM_WMGR |
fs 位 | --- | NtGdiSetBoundsRect(DCB_ENABLE/DISABLE) |
IntUpdateBoundsRect |
工作流程:
应用:SetBoundsRect(hdc, NULL, DCB_ENABLE) ← 开启应用累计
[ 执行一系列绘制:LineTo / BitBlt / SetPixel ... ]
└─ 每次绘制前:IntUpdateBoundsRect(pdc, 设备矩形) ← 自动累计
应用:GetBoundsRect(hdc, &rc, DCB_RESET) ← 查询 + 复位
├─ 边界与 Rao/Vis 包围盒相交(裁剪到可见区)
├─ IntDPtoLP 转逻辑坐标
├─ RTL 布局交换左右边界
└─ 返回 DCB_SET;若从未绘制返回 DCB_RESET + 全 0
设计要点:
- 累计发生在设备坐标 (绘制路径先
IntLPtoDP再累计),返回时转回逻辑坐标 ------这样边界对应用透明,应用可直接用于InvalidateRect。 - 空集表示法 :
left/top = INT_MAX、right/bottom = INT_MIN("倒置矩形"),RECTL_bIsEmptyRect判定left>=right || top>=bottom成立。复位时写这个特殊值,使下次RECTL_bUnionRect直接拷贝新矩形。 - WMGR 路径独立 :
DCB_WINDOWMGR的累计不经过坐标转换、不裁剪、返回设备坐标,供 win32k 内部(ntuser 绘制跟踪)使用,与应用路径完全隔离。 - Rao 区惰性重建的联动 :
NtGdiGetBoundsRect的 APP 路径若发现DC_DIRTY_RAO(由IntGdiSetHookFlags或窗口滚动置位),先CLIPPING_UpdateGCRegion重建可见区再求包围盒------保证边界裁剪基于最新可见状态。
4.5 RTL 布局(LAYOUT_RTL)处理汇总
dcutil.c 中有三处 RTL 相关逻辑,值得总结:
| 函数 | RTL 行为 | 目的 |
|---|---|---|
IntGdiSetTextAlign |
非 TA_CENTER 时 Mode ^= TA_RIGHT 翻转左右 |
文本对齐在 RTL 布局下左右镜像 |
NtGdiGetDCDword(GdiGetArcDirection)与 NtGdiGetAndSetDCDword(GdiGetSetArcDirection) |
方向值取反、设置方向时交换 CW/CCW 操作 | 弧线方向在镜像坐标系中语义反转 |
NtGdiGetBoundsRect(APP 路径) |
交换 left/right 后 right-- |
RTL 下边界矩形方向保持视觉一致性,独占右边界修正 |
dwLayout 由 SetLayout(coord.c/gdi32)设置;GreGetDCPoint、GreGetWindowExtEx 等 coord.c 函数也会读取 dwLayout(如窗口范围取负 X = -X),共同构成 RTL 镜像支持。
5. 调用链(mermaid)
5.1 属性设置 → 画刷重建
渲染错误: Mermaid 渲染失败: Parse error on line 4: ...y_: DIRTY_BACKGROUND|DIRTY_LINE|DIRTY_FI -----------------------^ Expecting 'SQE', 'TAGEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PIPE'
5.2 坐标换算
#mermaid-svg-UeYwODLEIthql0vv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UeYwODLEIthql0vv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UeYwODLEIthql0vv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UeYwODLEIthql0vv .error-icon{fill:#552222;}#mermaid-svg-UeYwODLEIthql0vv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UeYwODLEIthql0vv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UeYwODLEIthql0vv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UeYwODLEIthql0vv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UeYwODLEIthql0vv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UeYwODLEIthql0vv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UeYwODLEIthql0vv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UeYwODLEIthql0vv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UeYwODLEIthql0vv .marker.cross{stroke:#333333;}#mermaid-svg-UeYwODLEIthql0vv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UeYwODLEIthql0vv p{margin:0;}#mermaid-svg-UeYwODLEIthql0vv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UeYwODLEIthql0vv .cluster-label text{fill:#333;}#mermaid-svg-UeYwODLEIthql0vv .cluster-label span{color:#333;}#mermaid-svg-UeYwODLEIthql0vv .cluster-label span p{background-color:transparent;}#mermaid-svg-UeYwODLEIthql0vv .label text,#mermaid-svg-UeYwODLEIthql0vv span{fill:#333;color:#333;}#mermaid-svg-UeYwODLEIthql0vv .node rect,#mermaid-svg-UeYwODLEIthql0vv .node circle,#mermaid-svg-UeYwODLEIthql0vv .node ellipse,#mermaid-svg-UeYwODLEIthql0vv .node polygon,#mermaid-svg-UeYwODLEIthql0vv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UeYwODLEIthql0vv .rough-node .label text,#mermaid-svg-UeYwODLEIthql0vv .node .label text,#mermaid-svg-UeYwODLEIthql0vv .image-shape .label,#mermaid-svg-UeYwODLEIthql0vv .icon-shape .label{text-anchor:middle;}#mermaid-svg-UeYwODLEIthql0vv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UeYwODLEIthql0vv .rough-node .label,#mermaid-svg-UeYwODLEIthql0vv .node .label,#mermaid-svg-UeYwODLEIthql0vv .image-shape .label,#mermaid-svg-UeYwODLEIthql0vv .icon-shape .label{text-align:center;}#mermaid-svg-UeYwODLEIthql0vv .node.clickable{cursor:pointer;}#mermaid-svg-UeYwODLEIthql0vv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UeYwODLEIthql0vv .arrowheadPath{fill:#333333;}#mermaid-svg-UeYwODLEIthql0vv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UeYwODLEIthql0vv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UeYwODLEIthql0vv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UeYwODLEIthql0vv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UeYwODLEIthql0vv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UeYwODLEIthql0vv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UeYwODLEIthql0vv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UeYwODLEIthql0vv .cluster text{fill:#333;}#mermaid-svg-UeYwODLEIthql0vv .cluster span{color:#333;}#mermaid-svg-UeYwODLEIthql0vv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UeYwODLEIthql0vv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UeYwODLEIthql0vv rect.text{fill:none;stroke-width:0;}#mermaid-svg-UeYwODLEIthql0vv .icon-shape,#mermaid-svg-UeYwODLEIthql0vv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UeYwODLEIthql0vv .icon-shape p,#mermaid-svg-UeYwODLEIthql0vv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UeYwODLEIthql0vv .icon-shape .label rect,#mermaid-svg-UeYwODLEIthql0vv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UeYwODLEIthql0vv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UeYwODLEIthql0vv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UeYwODLEIthql0vv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} gdi32: LPtoDP
GreLPtoDP
dcutil.c
IntLPtoDP
coord.h inline
DC_vUpdateWorldToDevice
coord.c: XFORMOBJ_iCombine
INTERNAL_LPTODP
INTERNAL_APPLY_MATRIX
FLOATOBJ 矩阵乘
mxWorldToDevice
gdi32: DPtoLP
GreDPtoLP
dcutil.c
IntDPtoLP
coord.h inline
DC_vUpdateDeviceToWorld
coord.c: XFORMOBJ_iInverse
INTERNAL_DPTOLP
INTERNAL_APPLY_MATRIX
mxDeviceToWorld
5.3 边界矩形
#mermaid-svg-FFCvWbbr1BpFnXZC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FFCvWbbr1BpFnXZC .error-icon{fill:#552222;}#mermaid-svg-FFCvWbbr1BpFnXZC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FFCvWbbr1BpFnXZC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FFCvWbbr1BpFnXZC .marker.cross{stroke:#333333;}#mermaid-svg-FFCvWbbr1BpFnXZC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FFCvWbbr1BpFnXZC p{margin:0;}#mermaid-svg-FFCvWbbr1BpFnXZC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC .cluster-label text{fill:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC .cluster-label span{color:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC .cluster-label span p{background-color:transparent;}#mermaid-svg-FFCvWbbr1BpFnXZC .label text,#mermaid-svg-FFCvWbbr1BpFnXZC span{fill:#333;color:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC .node rect,#mermaid-svg-FFCvWbbr1BpFnXZC .node circle,#mermaid-svg-FFCvWbbr1BpFnXZC .node ellipse,#mermaid-svg-FFCvWbbr1BpFnXZC .node polygon,#mermaid-svg-FFCvWbbr1BpFnXZC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FFCvWbbr1BpFnXZC .rough-node .label text,#mermaid-svg-FFCvWbbr1BpFnXZC .node .label text,#mermaid-svg-FFCvWbbr1BpFnXZC .image-shape .label,#mermaid-svg-FFCvWbbr1BpFnXZC .icon-shape .label{text-anchor:middle;}#mermaid-svg-FFCvWbbr1BpFnXZC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FFCvWbbr1BpFnXZC .rough-node .label,#mermaid-svg-FFCvWbbr1BpFnXZC .node .label,#mermaid-svg-FFCvWbbr1BpFnXZC .image-shape .label,#mermaid-svg-FFCvWbbr1BpFnXZC .icon-shape .label{text-align:center;}#mermaid-svg-FFCvWbbr1BpFnXZC .node.clickable{cursor:pointer;}#mermaid-svg-FFCvWbbr1BpFnXZC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FFCvWbbr1BpFnXZC .arrowheadPath{fill:#333333;}#mermaid-svg-FFCvWbbr1BpFnXZC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FFCvWbbr1BpFnXZC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FFCvWbbr1BpFnXZC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FFCvWbbr1BpFnXZC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FFCvWbbr1BpFnXZC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FFCvWbbr1BpFnXZC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FFCvWbbr1BpFnXZC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FFCvWbbr1BpFnXZC .cluster text{fill:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC .cluster span{color:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FFCvWbbr1BpFnXZC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC rect.text{fill:none;stroke-width:0;}#mermaid-svg-FFCvWbbr1BpFnXZC .icon-shape,#mermaid-svg-FFCvWbbr1BpFnXZC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FFCvWbbr1BpFnXZC .icon-shape p,#mermaid-svg-FFCvWbbr1BpFnXZC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FFCvWbbr1BpFnXZC .icon-shape .label rect,#mermaid-svg-FFCvWbbr1BpFnXZC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FFCvWbbr1BpFnXZC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FFCvWbbr1BpFnXZC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FFCvWbbr1BpFnXZC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 查询
累计
设置
gdi32: SetBoundsRect
NtGdiSetBoundsRect
dcutil.c
DCB_ENABLE/DISABLE → fs 位
DC_ACCUM_APP / DC_ACCUM_WMGR
DCB_ACCUMULATE → IntLPtoDP + RECTL_bUnionRect
绘制: LineTo/SetPixel/BitBlt
IntUpdateBoundsRect
dcutil.c
RECTL_bUnionRect
rect.c
erclBoundsApp / erclBounds
gdi32: GetBoundsRect
NtGdiGetBoundsRect
dcutil.c
CLIPPING_UpdateGCRegion 若 DIRTY_RAO
与 Rao/Vis 包围盒相交
IntDPtoLP 转逻辑坐标
RTL 交换左右边界
DCB_RESET 复位累计
5.4 SetPixel(跨文件协作)
#mermaid-svg-s4ABg01a9cBlzSjR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s4ABg01a9cBlzSjR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s4ABg01a9cBlzSjR .error-icon{fill:#552222;}#mermaid-svg-s4ABg01a9cBlzSjR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s4ABg01a9cBlzSjR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s4ABg01a9cBlzSjR .marker.cross{stroke:#333333;}#mermaid-svg-s4ABg01a9cBlzSjR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s4ABg01a9cBlzSjR p{margin:0;}#mermaid-svg-s4ABg01a9cBlzSjR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-s4ABg01a9cBlzSjR .cluster-label text{fill:#333;}#mermaid-svg-s4ABg01a9cBlzSjR .cluster-label span{color:#333;}#mermaid-svg-s4ABg01a9cBlzSjR .cluster-label span p{background-color:transparent;}#mermaid-svg-s4ABg01a9cBlzSjR .label text,#mermaid-svg-s4ABg01a9cBlzSjR span{fill:#333;color:#333;}#mermaid-svg-s4ABg01a9cBlzSjR .node rect,#mermaid-svg-s4ABg01a9cBlzSjR .node circle,#mermaid-svg-s4ABg01a9cBlzSjR .node ellipse,#mermaid-svg-s4ABg01a9cBlzSjR .node polygon,#mermaid-svg-s4ABg01a9cBlzSjR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-s4ABg01a9cBlzSjR .rough-node .label text,#mermaid-svg-s4ABg01a9cBlzSjR .node .label text,#mermaid-svg-s4ABg01a9cBlzSjR .image-shape .label,#mermaid-svg-s4ABg01a9cBlzSjR .icon-shape .label{text-anchor:middle;}#mermaid-svg-s4ABg01a9cBlzSjR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-s4ABg01a9cBlzSjR .rough-node .label,#mermaid-svg-s4ABg01a9cBlzSjR .node .label,#mermaid-svg-s4ABg01a9cBlzSjR .image-shape .label,#mermaid-svg-s4ABg01a9cBlzSjR .icon-shape .label{text-align:center;}#mermaid-svg-s4ABg01a9cBlzSjR .node.clickable{cursor:pointer;}#mermaid-svg-s4ABg01a9cBlzSjR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-s4ABg01a9cBlzSjR .arrowheadPath{fill:#333333;}#mermaid-svg-s4ABg01a9cBlzSjR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-s4ABg01a9cBlzSjR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-s4ABg01a9cBlzSjR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s4ABg01a9cBlzSjR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-s4ABg01a9cBlzSjR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s4ABg01a9cBlzSjR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-s4ABg01a9cBlzSjR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-s4ABg01a9cBlzSjR .cluster text{fill:#333;}#mermaid-svg-s4ABg01a9cBlzSjR .cluster span{color:#333;}#mermaid-svg-s4ABg01a9cBlzSjR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-s4ABg01a9cBlzSjR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-s4ABg01a9cBlzSjR rect.text{fill:none;stroke-width:0;}#mermaid-svg-s4ABg01a9cBlzSjR .icon-shape,#mermaid-svg-s4ABg01a9cBlzSjR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s4ABg01a9cBlzSjR .icon-shape p,#mermaid-svg-s4ABg01a9cBlzSjR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-s4ABg01a9cBlzSjR .icon-shape .label rect,#mermaid-svg-s4ABg01a9cBlzSjR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s4ABg01a9cBlzSjR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-s4ABg01a9cBlzSjR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-s4ABg01a9cBlzSjR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
gdi32: SetPixel
NtGdiSetPixel
bitblt.c
fs & ACCUM?
IntLPtoDP + IntUpdateBoundsRect
dcutil.c
TranslateCOLORREF
dcutil.c
EXLATEOBJ_vInitialize
eng/xlateobj.c
XLATEOBJ_iXlate
gpalRGB → 表面调色板
借 eboText 改纯色
IntPatBlt 1×1 PATCOPY
bitblt.c
反向 XLATEOBJ
表面 → gpalRGB
返回新 RGB 或 CLR_INVALID
5.5 可见区失效通知(ntuser → ntgdi)
渲染错误: Mermaid 渲染失败: Parse error on line 3: ... B --> Cdc-\>fs \|= DC_DIRTY_RAO ----------------------^ Expecting 'SQE', 'TAGEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PIPE'
6. 与《分析_31》及分册关系
| 项 | 说明 |
|---|---|
| 《分析_31》4.2 表(dcutil.c 函数清单) | 本分析对其中各函数逐一展开 |
| 《分析_39》(dcobjs.c 画刷更新) | 脏标记的消费者:DC_vUpdateTextBrush/LineBrush/FillBrush/BackgroundBrush;本文件置位,对方清位 |
| 《分析_34》(coord.c/coord.h 坐标) | IntLPtoDP/IntDPtoLP、XFORM 矩阵惰性更新、IntGdiSetMapMode |
| 《分析_19》(EXLATEOBJ) | TranslateCOLORREF 与 NtGdiSetPixel 双向颜色转换的核心机制 |
| 《分析_17》(MIX/ROP) | jBkMode/jROP2 的下游消费者------背景模式决定 MIX 背景 ROP |
| 本文件(dcutil.c) | 属性读写、坐标入口、边界矩形、通用 DC 查询、颜色翻译 |
| cliprgn.c | CLIPPING_UpdateGCRegion------NtGdiGetBoundsRect 可见区重建的联动 |
| bitblt.c | NtGdiSetPixel/NtGdiGetPixel/IntPatBlt------TranslateCOLORREF/IntUpdateBoundsRect 的调用方 |
| ntuser/windc.c | IntGdiSetHookFlags 的唯一调用方(DCE 可见区管理) |
7. 源码索引
| 文件 | 关键内容 |
|---|---|
| dcutil.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcutil.c) | GreDPtoLP/GreLPtoDP/GreGetBkMode/GreGetBkColor/GreGetMapMode/GreGetTextColor/IntGdiSetBkColor/IntGdiSetBkMode/IntGdiSetTextAlign/IntGdiSetTextColor/IntSetDCBrushColor/IntSetDCPenColor/GreSetBrushOrg/GreSetStretchBltMode/GreGetGraphicsMode/DCU_SetDcUndeletable/IntSetDefaultRegion/NtGdiCancelDC/IntGdiSetHookFlags/NtGdiGetDCDword/NtGdiGetAndSetDCDword/IntUpdateBoundsRect/NtGdiGetBoundsRect/NtGdiSetBoundsRect/TranslateCOLORREF(共 25 个活动函数 + 1 个 #if 0 禁用的 IntIsPrimarySurface) |
| dc.h(file:///d:/reactos/win32ss/gdi/ntgdi/dc.h) | _DC/_DCLEVEL 结构、_DCFLAGS(DC_ACCUM_APP/WMGR、DC_DIRTY_RAO、DC_PERMANANT、DC_EPSPRINTINGESCAPE)、DC_LockDc/DC_UnlockDc、DC_vSetBrushOrigin、DC_vUpdate*Brush 声明 |
| coord.h(file:///d:/reactos/win32ss/gdi/ntgdi/coord.h) | INTERNAL_APPLY_MATRIX/INTERNAL_LPTODP/INTERNAL_DPTOLP/IntLPtoDP/IntDPtoLP、DC_pmxWorldToDevice/DC_pmxDeviceToWorld 惰性更新、MIN_COORD/MAX_COORD |
| coord.c(file:///d:/reactos/win32ss/gdi/ntgdi/coord.c) | IntGdiSetMapMode(826 行)、DC_vUpdateWorldToDevice/DC_vUpdateDeviceToWorld、GreSetViewportOrgEx 等映射模式/变换实现 |
| ntgdihdl.h(file:///d:/reactos/win32ss/include/ntgdihdl.h) | _DC_ATTR 结构(291~351 行)、脏标记(122~145 行)、Xform 标志(173~188 行) |
| dcobjs.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcobjs.c) | DC_vUpdateFillBrush/LineBrush/TextBrush/BackgroundBrush(16~138 行)、DC_vSetBrushOrigin(140~151 行)、NtGdiSelectBrush(255~280 行)、NtGdiSetBrushOrg(162~195 行) |
| rect.h(file:///d:/reactos/win32ss/gdi/ntgdi/rect.h) / rect.c(file:///d:/reactos/win32ss/gdi/ntgdi/rect.c) | RECTL_bIsEmptyRect/RECTL_bUnionRect/RECTL_vMakeWellOrdered 等矩形运算 |
| bitblt.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c) | NtGdiSetPixel(1463 行)/NtGdiGetPixel(1551 行)------TranslateCOLORREF/IntUpdateBoundsRect 的调用方 |
| xlateobj.h(file:///d:/reactos/win32ss/gdi/eng/xlateobj.h) | EXLATEOBJ_vInitialize/EXLATEOBJ_vCleanup/XLATEOBJ_iXlate |
| palette.h(file:///d:/reactos/win32ss/gdi/ntgdi/palette.h) | gpalRGB 等全局调色板声明(PALETTE gpalRGB, gpalBGR, ...) |
| dce.h(file:///d:/reactos/win32ss/user/ntuser/dce.h) | DCHF_INVALIDATEVISRGN(0x1)/DCHF_VALIDATEVISRGN(0x2) |
| windc.c(file:///d:/reactos/win32ss/user/ntuser/windc.c) | IntGdiSetHookFlags 的调用点(187、610、931、1158 行) |
函数覆盖统计 :本分析覆盖 dcutil.c 中全部 26 个函数(25 个活动函数 + 1 个被 #if 0 禁用的 IntIsPrimarySurface),以及与之直接协作的 coord.h/coord.c(坐标变换)、dcobjs.c(画刷重建)、rect.c(矩形运算)、bitblt.c(SetPixel/GetPixel)、cliprgn.c(可见区重建)、xlateobj(颜色转换)等关联实现。
关联模块: dcobjs.c(画刷更新,《分析_39》)、coord.c/coord.h(坐标变换,《分析_34》)、EXLATEOBJ(《分析_19》)、MIX/ROP(《分析_17》)、cliprgn.c(Rao/可见区)、bitblt.c(SetPixel/GetPixel)、ntuser/windc.c(DCE 可见区钩子)
本文档基于 ReactOS 源代码
win32ss/gdi/ntgdi/dcutil.c(963 行)及关联模块(dc.h、coord.h、dcobjs.c、rect.c、bitblt.c、ntgdihdl.h 等)逐函数分析(2026 年 8 月)。文中行号为源码当前版本行号,函数行为描述以源码为准。