ReactOS 图形系统分析(39):DC 对象 --- dcobjs.c
1. 概述
dcobjs.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcobjs.c) 是 ntgdi(内核模式 GDI)中负责 DC(Device Context,设备描述表)对象选择与画刷更新 的核心文件。它实现了用户态 GDI 与内核 DC 对象之间的桥接层,包含以下三类职责:
- 对象选择(SelectObject 家族) :位图(
NtGdiSelectBitmap)、画刷(NtGdiSelectBrush)、画笔(NtGdiSelectPen)、字体(NtGdiSelectFont)、裁剪路径(NtGdiSelectClipPath)。 - DC 状态查询(GetCurrentObject 家族) :
NtGdiGetDCObject(当前选中对象)、NtGdiGetRandomRgn(裁剪相关区域)、NtGdiEnumObjects(对象枚举)。 - 画刷脏标记更新(内部基础设施) :
DC_vUpdateFillBrush/DC_vUpdateLineBrush/DC_vUpdateTextBrush/DC_vUpdateBackgroundBrush四个内部函数,把"逻辑画刷句柄 + DC 属性"物化为引擎可用的EBRUSHOBJ对象,供绘制路径直接使用。
此外文件还包含 DC_vSetBrushOrigin(画刷原点设置)与 GdiSelectPalette(调色板选择)两个入口。
1.1 架构总览(ASCII 图)
用户态 (user32.dll / gdi32full.dll)
│ SelectObject(hdc, hObj) / GetCurrentObject / EnumObjects / SetBrushOrgEx ...
│ GetCurrentObject / SelectClipPath / SelectObject(hbmp/hbr/hpen/hfont)
▼
系统调用层 (win32k.sys)
│ NtGdiSelectBitmap NtGdiSelectBrush NtGdiSelectPen NtGdiSelectFont
│ NtGdiSelectClipPath NtGdiGetDCObject NtGdiGetRandomRgn NtGdiEnumObjects
│ NtGdiSetBrushOrg
▼
dcobjs.c ──────────────────────────────────────────────────────────────┐
│ 选择入口:LockDc → 更新 pdcattr(句柄字段)→ DC_vUpdate*Brush → UnlockDc
│ 查询入口:LockDc → 按类型读取 pdcattr / dclevel → UnlockDc
│ 内部更新:DC_vUpdateFillBrush / Line / Text / Background(脏标记驱动)
▼
DCLEVEL(dclevel) DC_ATTR(pdcattr) DC(主结构)
│ pbrFill / pbrLine hbrush / hpen eboFill / eboLine
│ plfnt / hpal / pSurface crBrushClr / crPenClr eboText / eboBackground
│ prgnClip / prgnMeta crForegroundClr prgnVis / prgnAPI / prgnRao
│ ptlBrushOrigin crBackgroundClr ptlDCOrig / ptlFillOrigin
│ ulDirty_(脏标记) co(XCLIPOBJ)
▼
EBRUSHOBJ(engine brush object)
│ EBRUSHOBJ_vUpdateFromDC → EBRUSHOBJ_vCleanup + EBRUSHOBJ_vInitFromDC
│ EBRUSHOBJ_vSetSolidRGBColor(纯色画刷的 XLATEOBJ 颜色转换)
│ EBRUSHOBJ_bRealizeBrush → DrvRealizeBrush / EngRealizeBrush
▼
绘制路径(bitblt.c / line.c / text.c / fill.c ...)
IntGdiFillRgn / IntEngLineTo / IntTextOut ...
1.2 设计动机
DC 是 GDI 的"状态容器"。Windows/ReactOS 的设计把 DC 的逻辑状态与引擎绘制所需的具体格式解耦:
- 逻辑层 :
DC_ATTR(pdcattr)保存用户可感知 的句柄与颜色值------hbrush、hpen、crForegroundClr、crBackgroundClr、crBrushClr、crPenClr等。用户调用SelectObject或SetTextColor只会修改这一层。 - 物化层 :引擎(
eng/engbrush.c、eng/line.c、dib/等)绘制时不能直接用逻辑句柄,它需要已经针对目标表面格式(bpp、调色板)翻译好的 画刷(纯色索引或模式位图)。这就是EBRUSHOBJ(Engine BRUSH OBJect)。 - 脏标记 :把逻辑状态翻译成引擎对象是有代价的(调色板查找、颜色翻译、位图实现 Realize)。如果每次绘制都重做一遍,性能不可接受。因此 ReactOS 采用惰性(lazy)重建 策略:
SelectObject/SetTextColor等操作只设置ulDirty_中的脏位,真正的重建推迟到DC_vUpdate*Brush被调用时(绘制入口在动手前都会先调用它)。
于是文件呈现出清晰的"写时置脏、读时重建 "模式:NtGdiSelectBrush 只改 pdcattr->hbrush 并立即调用一次 DC_vUpdateFillBrush(保证 GetCurrentObject 等查询路径看到一致状态),而更频繁的绘制路径则靠脏标记按需重建。
2. 核心数据结构
2.1 DC 主结构(dc.h)
typedef struct _DC(dc.h(file:///d:/reactos/win32ss/gdi/ntgdi/dc.h) 第 95-137 行)是 DC 对象的完整描述,第一个成员必须是 BASEOBJECT(GDI 句柄表对象的公共头,gdiobj.h 中定义,包含 hHmgr、pUser、pNext 等),不能移动位置:
c
typedef struct _DC
{
BASEOBJECT BaseObject; /* 句柄表对象头(必须为第一成员) */
DHPDEV dhpdev; /* 设备句柄(PDEVOBJ.hPDev) */
DCTYPE dctype; /* DCTYPE_DIRECT / DCTYPE_MEMORY / DCTYPE_INFO */
INT fs; /* DCFLAGS 标志(DC_DIRTY_RAO 等) */
PPDEVOBJ ppdev; /* 物理设备对象(PDEV)指针 */
PVOID hsem; /* PERESOURCE(DC 自旋锁/信号量) */
FLONG flGraphicsCaps; /* 图形能力标志 */
FLONG flGraphicsCaps2;
PDC_ATTR pdcattr; /* 指向 DC_ATTR(用户属性,可能在 GDI 池中) */
DCLEVEL dclevel; /* DC 内部级别状态(句柄对象的已选实体) */
DC_ATTR dcattr; /* 内核模式 DC_ATTR 备份(pdcattr 常指向它) */
HDC hdcNext; /* DC 链表 */
HDC hdcPrev;
RECTL erclClip; /* 裁剪矩形 */
POINTL ptlDCOrig; /* DC 原点(屏幕坐标偏移) */
RECTL erclWindow; /* 窗口矩形 */
RECTL erclBounds; /* WMGR 边界累积矩形 */
RECTL erclBoundsApp; /* APP 边界累积矩形 */
PREGION prgnAPI; /* API 区域(元区域 ∩ 裁剪区域) */
PREGION prgnVis; /* 可见区域(必须非空) */
PREGION prgnRao; /* Rao 区域(API 区域 ∩ 系统区域) */
POINTL ptlFillOrigin; /* 填充原点(画刷原点 + DC 原点) */
EBRUSHOBJ eboFill; /* 填充画刷引擎对象 */
EBRUSHOBJ eboLine; /* 线条画刷引擎对象(画笔) */
EBRUSHOBJ eboText; /* 文本画刷引擎对象 */
EBRUSHOBJ eboBackground; /* 背景画刷引擎对象 */
HFONT hlfntCur; /* 当前字体句柄 */
FLONG flSimulationFlags;
LONG lEscapement;
PVOID prfnt; /* RFONT* */
XCLIPOBJ co; /* CLIPOBJ 裁剪对象 */
...
} DC;
关键点:
pdcattr与dcattr是"指针 + 备份"关系:DC 创建时pdcattr = &pdc->dcattr(见 dcattr.c);当 DC 归属某个进程时,DC_bAllocDcAttr会从该进程的 GDI 属性池 分配真正的DC_ATTR并把内容拷贝过去(见第 6 节)。这样不同进程的 DC 属性可以分别驻留在各自的池中,随进程回收。- 四个
EBRUSHOBJ(eboFill/eboLine/eboText/eboBackground)直接内嵌在 DC 结构里,而不是指针------这是性能考量:避免绘制热路径上的额外解引用。 prgnVis注释"must never be 0":可见区域是所有绘制最终都要经过的区域。
2.2 DCLEVEL(dc.h 第 49-89 行)
DCLEVEL 保存 DC 的"已选择实体"(被引用计数保护的对象指针),与 DC_ATTR 中保存的"句柄值"对应:
c
typedef struct _DCLEVEL
{
HPALETTE hpal; /* 调色板句柄 */
struct _PALETTE *ppal; /* 调色板对象指针(引用计数) */
PVOID pColorSpace; /* COLORSPACE* */
LONG lIcmMode;
LONG lSaveDepth; /* SaveDC 深度 */
DWORD unk1_00000000;
HGDIOBJ hdcSave; /* SaveDC 保存的 DC 句柄(链式) */
POINTL ptlBrushOrigin; /* 画刷原点(相对 DC 原点) */
PBRUSH pbrFill; /* 填充画刷(BRUSH*,引用计数) */
PBRUSH pbrLine; /* 线条画刷/画笔(BRUSH*,引用计数) */
struct _LFONT * plfnt; /* 字体对象(TEXTOBJ*) */
HGDIOBJ hPath; /* 当前路径句柄(HPATH) */
FLONG flPath; /* 路径标志(DCPATH_ACTIVE 等) */
LINEATTRS laPath; /* 路径的线属性 */
PREGION prgnClip; /* 裁剪区域(API 设置) */
PREGION prgnMeta; /* 元区域 */
COLORADJUSTMENT ca;
FLONG flFontState; /* 字体状态(DC_DIRTYFONT_XFORM 等) */
UNIVERSAL_FONT_ID ufi; /* 当前字体 UFI */
UNIVERSAL_FONT_ID ufiLoc[4]; /* 本地字体列表 */
...
PSURFACE pSurface; /* 当前表面(内存 DC 的位图) */
SIZE sizl; /* 当前表面尺寸 */
} DCLEVEL;
DCLEVEL 与 DC_ATTR 的分工是理解 dcobjs.c 的关键:
| 字段(DC_ATTR) | 对应实体(DCLEVEL) | 说明 |
|---|---|---|
hbrush |
pbrFill |
逻辑填充画刷句柄 vs 已锁定实体 |
hpen |
pbrLine |
逻辑画笔句柄 vs 已锁定实体(画笔用 BRUSH 结构表示) |
hlfntNew |
plfnt |
逻辑字体句柄 vs LFONT 实体 |
hpal(在 dclevel) |
ppal |
调色板句柄 vs 实体 |
| --- | pSurface / sizl |
内存 DC 的位图表面与尺寸 |
DC_vUpdateFillBrush 的核心工作就是把这两层对齐:比较 hbrush 与 pbrFill->BaseObject.hHmgr,不一致则锁定新画刷、解锁旧画刷。
2.3 DC_ATTR(ntgdihdl.h 第 291-351 行)
c
typedef struct _DC_ATTR
{
PVOID pvLDC; /* 本地 DC 扩展(打印等) */
ULONG ulDirty_; /* 脏标记位(见 2.4 表) */
HANDLE hbrush; /* 当前填充画刷句柄 */
HANDLE hpen; /* 当前画笔句柄 */
COLORREF crBackgroundClr; /* 背景色(已转换) */
ULONG ulBackgroundClr; /* 背景色(原始 COLORREF) */
COLORREF crForegroundClr; /* 前景色(已转换) */
ULONG ulForegroundClr; /* 前景色(原始 COLORREF) */
COLORREF crBrushClr; /* DC 画刷颜色(SetDCBrushColor) */
ULONG ulBrushClr;
COLORREF crPenClr; /* DC 画笔颜色(SetDCPenColor) */
ULONG ulPenClr;
DWORD iCS_CP;
INT iGraphicsMode;
BYTE jROP2; /* 光栅操作(R2_COPYPEN 等) */
BYTE jBkMode; /* 背景模式(OPAQUE/TRANSPARENT) */
BYTE jFillMode; /* 填充模式(ALTERNATE/WINDING) */
BYTE jStretchBltMode;
POINTL ptlCurrent; /* 当前位置(逻辑) */
POINTL ptfxCurrent; /* 当前位置(物理) */
LONG lBkMode;
LONG lFillMode;
LONG lStretchBltMode;
FLONG flFontMapper;
LONG lIcmMode;
HANDLE hcmXform;
HCOLORSPACE hColorSpace;
FLONG flIcmFlags;
INT IcmBrushColor;
INT IcmPenColor;
PVOID pvLIcm;
FLONG flTextAlign;
LONG lTextAlign;
LONG lTextExtra;
LONG lRelAbs;
LONG lBreakExtra;
LONG cBreak;
HANDLE hlfntNew; /* 当前字体句柄(选中字体) */
MATRIX mxWorldToDevice; /* 世界→设备变换 */
MATRIX mxDeviceToWorld;
MATRIX mxWorldToPage;
FLOATOBJ efM11PtoD; /* 页面→设备变换分量 */
FLOATOBJ efM22PtoD;
FLOATOBJ efDxPtoD;
FLOATOBJ efDyPtoD;
INT iMapMode; /* 映射模式(MM_TEXT 等) */
DWORD dwLayout;
LONG lWindowOrgx;
POINTL ptlWindowOrg;
SIZEL szlWindowExt;
POINTL ptlViewportOrg;
SIZEL szlViewportExt;
FLONG flXform;
SIZEL szlVirtualDevicePixel;
SIZEL szlVirtualDeviceMm;
SIZEL szlVirtualDeviceSize;
POINTL ptlBrushOrigin; /* 画刷原点(属性副本) */
RGN_ATTR VisRectRegion; /* 可见矩形区域属性 */
} DC_ATTR;
注意颜色字段成对出现:crForegroundClr(转换后的颜色)与 ulForegroundClr(原始 COLORREF)。dcutil.c 中的 IntGdiSetTextColor 只在实际颜色变化时才置脏并更新 cr 副本,避免无谓的 EBRUSHOBJ 重建。
2.4 ulDirty_ 脏标记位表
定义于 ntgdihdl.h(file:///d:/reactos/win32ss/include/ntgdihdl.h) 第 122-145 行:
| 位 | 宏名 | 含义 |
|---|---|---|
| 0x00000001 | DIRTY_FILL |
填充画刷(eboFill)需要重建 |
| 0x00000002 | DIRTY_LINE |
线条画刷(eboLine)需要重建 |
| 0x00000004 | DIRTY_TEXT |
文本画刷(eboText)需要重建 |
| 0x00000008 | DIRTY_BACKGROUND |
背景画刷(eboBackground)需要重建 |
| 0x00000010 | DIRTY_CHARSET |
字符集/字体需要重建 |
| 0x00000020 | SLOW_WIDTHS |
慢速宽度表(字体) |
| 0x00000040 | DC_CACHED_TM_VALID |
缓存的 TEXTMETRIC 有效 |
| 0x00000080 | DISPLAY_DC |
显示 DC |
| 0x00000100 | DIRTY_PTLCURRENT |
当前位置(逻辑)脏 |
| 0x00000200 | DIRTY_PTFXCURRENT |
当前位置(物理)脏 |
| 0x00000400 | DIRTY_STYLESTATE |
线型状态脏 |
| 0x00000800 | DC_PLAYMETAFILE |
元文件回放模式 |
| 0x00001000 | DC_BRUSH_DIRTY |
DC 画刷颜色(crBrushClr)变化 |
| 0x00002000 | DC_PEN_DIRTY |
DC 画笔颜色(crPenClr)变化 |
| 0x00004000 | DC_DIBSECTION |
当前位图是 DIBSECTION |
| 0x00008000 | DC_LAST_CLIPRGN_VALID |
上次裁剪区域有效 |
| 0x00010000 | DC_PRIMARY_DISPLAY |
主显示 DC |
| 0x00020000 | DC_ICM_NOT_CALIBRATED |
ICM 未校准 |
| 0x00040000 | DC_ICM_BRUSH_DIRTY |
ICM 画刷脏 |
| 0x00080000 | DC_ICM_PEN_DIRTY |
ICM 画笔脏 |
| 0x00100000 | DC_ICM_NOT_SET |
ICM 未设置 |
| 0x00200000 | DC_MODE_DIRTY |
模式脏 |
| 0x00400000 | DC_FONTTEXT_DIRTY |
字体文本脏 |
dcobjs.c 中直接使用的前 8 个标记(DIRTY_FILL/LINE/TEXT/BACKGROUND/CHARSET、DC_BRUSH_DIRTY/DC_PEN_DIRTY、DC_DIBSECTION)形成两个"重建通道":
- 画刷通道 :
DC_vUpdateFillBrush清除DIRTY_FILL|DC_BRUSH_DIRTY,DC_vUpdateLineBrush清除DIRTY_LINE|DC_PEN_DIRTY。NtGdiGetDCObject在读取前正是用(DIRTY_FILL|DC_BRUSH_DIRTY)与(DIRTY_LINE|DC_PEN_DIRTY)判断是否需要先更新。 - 注意
DIRTY_TEXT/DIRTY_BACKGROUND没有对应的"颜色脏位",因为文本/背景画刷的颜色总是无条件 通过EBRUSHOBJ_vSetSolidRGBColor设置(见函数分析)。
2.5 EBRUSHOBJ 结构(brush.h 第 69-95 行)
c
typedef struct _EBRUSHOBJ
{
BRUSHOBJ BrushObject; /* 引擎 BRUSHOBJ 头(iSolidColor、pvRbrush、flColorType) */
COLORREF crRealize; /* 实现时的 RGB 颜色 */
ULONG ulRGBColor; /* RGB 颜色副本 */
PVOID pengbrush; /* GDI 实现的模式位图(SURFACE*) */
ULONG ulSurfPalTime; /* 表面调色板时间戳 */
ULONG ulDCPalTime; /* DC 调色板时间戳 */
COLORREF crCurrentText; /* 当前前景色 */
COLORREF crCurrentBack; /* 当前背景色 */
COLORADJUSTMENT *pca;
SURFACE * psurfTrg; /* 目标表面 */
struct _PALETTE *ppalSurf; /* 表面调色板(引用计数) */
struct _PALETTE *ppalDC; /* DC 调色板(引用计数) */
struct _PALETTE *ppalDIB; /* DIB_PAL_COLORS 修复调色板 */
PBRUSH pbrush; /* 逻辑画刷(BRUSH*) */
FLONG flattrs; /* 画刷属性(BR_IS_SOLID 等) */
DWORD ulUnique;
SURFOBJ *psoMask; /* 阴影/掩码位图 */
} EBRUSHOBJ;
flattrs 取自 pbrush->flAttrs,其取值见 brush.h 第 97-115 行:
c
#define BR_NEED_FG_CLR 0x00000001 /* 需要前景色 */
#define BR_NEED_BK_CLR 0x00000002 /* 需要背景色 */
#define BR_DITHER_OK 0x00000004 /* 允许抖动 */
#define BR_IS_SOLID 0x00000010 /* 纯色画刷 */
#define BR_IS_HATCH 0x00000020 /* 阴影画刷 */
#define BR_IS_BITMAP 0x00000040 /* DDB 模式画刷 */
#define BR_IS_DIB 0x00000080 /* DIB 模式画刷 */
#define BR_IS_NULL 0x00000100 /* 空画刷(NULL_BRUSH) */
#define BR_IS_GLOBAL 0x00000200 /* 全局对象(库存对象) */
#define BR_IS_PEN 0x00000400 /* 画笔 */
#define BR_IS_OLDSTYLEPEN 0x00000800 /* 几何画笔 */
#define BR_IS_DIBPALCOLORS 0x00001000 /* DIB_PAL_COLORS */
#define BR_IS_DIBPALINDICES 0x00002000 /* DIB_PAL_INDICES */
#define BR_IS_DEFAULTSTYLE 0x00004000
#define BR_IS_MASKING 0x00008000 /* 模式位图用作透明掩码 */
#define BR_IS_INSIDEFRAME 0x00010000 /* 内框画笔 */
#define BR_CACHED_ENGINE 0x00040000
#define BR_CACHED_IS_SOLID 0x80000000
2.6 BRUSH / BRUSHBODY(brush.h 第 14-59 行)
画刷与画笔在 ntgdi 内部共用 BRUSH 结构:BRUSH = BASEOBJECT + BRUSHBODY。BRUSHBODY 布局取自冯远(Feng Yuan)《Windows Graphics Programming》一书,注释明确警告不要随意改动:
c
typedef struct _BRUSHBODY
{
ULONG iHatch; /* 阴影样式索引(非画刷样式!) */
HBITMAP hbmPattern; /* 模式位图 */
HBITMAP hbmClient;
ULONG flAttrs; /* BR_IS_* 属性位 */
ULONG ulBrushUnique;
BRUSH_ATTR *pBrushAttr; /* 当前激活的画刷属性指针 */
BRUSH_ATTR BrushAttr; /* 全局画刷的内部属性(lbColor 等) */
POINT ptOrigin;
ULONG bCacheGrabbed;
COLORREF crBack;
COLORREF crFore;
ULONG ulPalTime;
ULONG ulSurfTime;
PVOID pvRBrush; /* 驱动实现的画刷 */
HDEV hdev;
/* 以下成员仅画笔(PEN)使用 */
LONG lWidth; /* 线宽 */
FLOATOBJ eWidth;
ULONG ulPenStyle; /* PS_SOLID 等 */
DWORD *pStyle; /* 自定义线型数组 */
ULONG dwStyleCount;
BYTE jJoin; /* 几何宽线的连接样式 */
BYTE jEndCap; /* 端帽样式 */
INT iBrushStyle;
ULONG ulStyleSize;
LIST_ENTRY ListHead;
} BRUSHBODY;
这就是 dcobjs.c 中 pbrFill/pbrLine 的实际类型:BRUSH_ShareLockBrush 与 PEN_ShareLockPen(brush.h(file:///d:/reactos/win32ss/gdi/ntgdi/brush.h) 第 117-118 行)都是基于 GDIOBJ_ShareLockObj 的宏,只是对象类型不同(GDI_OBJECT_TYPE_BRUSH / GDI_OBJECT_TYPE_PEN)。
3. 函数逐一分析
3.1 DC_vUpdateFillBrush(dcobjs.c 第 14-58 行)
签名 :VOID FASTCALL DC_vUpdateFillBrush(PDC pdc)
作用 :把 pdcattr->hbrush(逻辑填充画刷句柄)与 pdcattr->crBrushClr(DC 画刷颜色)物化到 pdc->eboFill(引擎填充画刷)。
实现流程(四步):
- 句柄对齐 :若
pdcattr->hbrush != pdc->dclevel.pbrFill->BaseObject.hHmgr,说明用户换过画刷。尝试BRUSH_ShareLockBrush(pdcattr->hbrush)锁定新画刷:- 成功:
BRUSH_ShareUnlockBrush解锁旧画刷(减少引用计数),pbrFill = pbrFill新,置DIRTY_FILL; - 失败:句柄无效,回滚
pdcattr->hbrush = pbrFill->BaseObject.hHmgr(恢复旧句柄,防止脏句柄继续流传)。
- 成功:
- 实体重建 :若
ulDirty_ & DIRTY_FILL,调用EBRUSHOBJ_vUpdateFromDC(&pdc->eboFill, pdc->dclevel.pbrFill, pdc)(内部 = 清理 + 用 DC 上下文重新初始化,见第 4 节)。 - DC 画刷颜色 :若
hbrush == StockObjects[DC_BRUSH](即选择了"DC 画刷"这个库存对象,其颜色由SetDCBrushColor控制),调用EBRUSHOBJ_vSetSolidRGBColor(&pdc->eboFill, pdcattr->crBrushClr)更新纯色。DC_BRUSH 在 stockobj.c(file:///d:/reactos/win32ss/gdi/ntgdi/stockobj.c) 第 256 行创建(白色纯色画刷),它本身是BR_IS_SOLID,所以可以安全调用纯色设置路径。 - 清脏 :
pdcattr->ulDirty_ &= ~(DIRTY_FILL | DC_BRUSH_DIRTY)。
使用方式 :所有填充绘制入口(如 IntGdiFillRgn、IntGdiFillPath、IntStretchBlt 的内部填充)在动手前调用;NtGdiSelectBrush、IntGdiSetTextColor、IntGdiSetBkColor、IntSetDCBrushColor、GdiSelectPalette 也会显式调用。
注意事项:
- 画刷句柄的"脏"是靠
pdcattr->hbrush与pbrFill->BaseObject.hHmgr的直接比较 检测的,并不需要额外的脏位------这是本函数与DC_vUpdateTextBrush的显著差异(后者画刷固定为pbrDefaultBrush,永远不变)。 DC_BRUSH_DIRTY只在IntSetDCBrushColor中置位(见 dcutil.c 第 220-224 行);即便画刷不是 DC_BRUSH,DC_vUpdateFillBrush也会把它清除,防止"颜色改了但画刷不是 DC_BRUSH"时残留脏位造成误导。
3.2 DC_vUpdateLineBrush(dcobjs.c 第 60-104 行)
签名 :VOID FASTCALL DC_vUpdateLineBrush(PDC pdc)
作用 :把 pdcattr->hpen(逻辑画笔句柄)与 pdcattr->crPenClr(DC 画笔颜色)物化到 pdc->eboLine。
实现流程:与填充版本完全对称------
- 若
pdcattr->hpen != pdc->dclevel.pbrLine->BaseObject.hHmgr:PEN_ShareLockPen锁定新画笔,成功则解锁旧画笔并更新pbrLine、置DIRTY_LINE;失败则回滚hpen。 - 若
DIRTY_LINE:EBRUSHOBJ_vUpdateFromDC(&pdc->eboLine, pdc->dclevel.pbrLine, pdc)。 - 若
hpen == StockObjects[DC_PEN]:EBRUSHOBJ_vSetSolidRGBColor(&pdc->eboLine, pdcattr->crPenClr)(DC_PEN 由IntCreateStockPen创建,见 stockobj.c 第 265 行,BR_IS_OLDSTYLEPEN|BR_IS_SOLID)。 - 清
DIRTY_LINE | DC_PEN_DIRTY。
注意事项 :画笔在 ntgdi 中用 BRUSH 结构表示(见 2.6),PEN_ShareLockPen 本质上与 BRUSH_ShareLockBrush 同构,只是句柄类型校验不同。宽笔(几何画笔)的路径由 line.c 在绘制时另行处理,本函数只保证 eboLine 的纯色/模式正确。
3.3 DC_vUpdateTextBrush(dcobjs.c 第 106-122 行)
签名 :VOID FASTCALL DC_vUpdateTextBrush(PDC pdc)
作用 :维护文本画刷 pdc->eboText。文本画刷的画刷实体永远是系统默认画刷 pbrDefaultBrush(全局,由 dclife.c 第 59 行 BRUSH_ShareLockBrush(StockObjects[BLACK_BRUSH]) 初始化),但它承载文本前景色。
实现流程:
c
/* Timo : The text brush should never be changed.
* Jérôme : Yeah, but its palette must be updated anyway! */
if(pdcattr->ulDirty_ & DIRTY_TEXT)
EBRUSHOBJ_vUpdateFromDC(&pdc->eboText, pbrDefaultBrush, pdc);
/* Update the eboText's solid color */
EBRUSHOBJ_vSetSolidRGBColor(&pdc->eboText, pdcattr->crForegroundClr);
/* Clear flag */
pdcattr->ulDirty_ &= ~DIRTY_TEXT;
注意事项:
- 函数开头的注释是一段开发者对话:Timo 认为文本画刷"永远不应该变",Jérôme 指出"但它的调色板必须更新"------即
EBRUSHOBJ_vUpdateFromDC负责重建(含调色板引用),而EBRUSHOBJ_vSetSolidRGBColor是无条件执行 的,因为文本颜色随SetTextColor频繁变化,每次绘制前都必须同步。 - 与填充/线条版本不同,这里没有"句柄比较 + 锁定/解锁"的逻辑:因为画刷固定是
pbrDefaultBrush,不存在切换问题。 IntGdiSetTextColor(dcutil.c 第 196-198 行)在置DIRTY_TEXT|DIRTY_LINE|DIRTY_FILL后立即调用本函数和另外两个 Update 函数。
3.4 DC_vUpdateBackgroundBrush(dcobjs.c 第 124-138 行)
签名 :VOID FASTCALL DC_vUpdateBackgroundBrush(PDC pdc)
作用 :维护背景画刷 pdc->eboBackground,同样使用 pbrDefaultBrush 作为实体,颜色取 pdcattr->crBackgroundClr。
实现流程 :与 DC_vUpdateTextBrush 结构完全一致------DIRTY_BACKGROUND 时 EBRUSHOBJ_vUpdateFromDC(&pdc->eboBackground, pbrDefaultBrush, pdc),然后无条件 EBRUSHOBJ_vSetSolidRGBColor(&pdc->eboBackground, pdcattr->crBackgroundClr),最后清 DIRTY_BACKGROUND。
用途 :背景画刷服务于 ETO_OPAQUE 文本背景、ROP 操作的背景填充(如 PatBlt 的背景色、TextOut 的不透明背景)等场景。IntGdiSetBkColor(dcutil.c 第 112-116 行)在背景色变化时置 DIRTY_BACKGROUND|DIRTY_LINE|DIRTY_FILL 三个脏位,然后重新 NtGdiSelectBrush 一次来刷新填充画刷。
3.5 DC_vSetBrushOrigin(dcobjs.c 第 140-151 行)
签名 :VOID NTAPI DC_vSetBrushOrigin(PDC pdc, LONG x, LONG y)
作用:设置画刷原点,并同步计算"填充原点"。
实现流程:
c
pdc->dclevel.ptlBrushOrigin.x = x;
pdc->dclevel.ptlBrushOrigin.y = y;
pdc->ptlFillOrigin.x = x + pdc->ptlDCOrig.x;
pdc->ptlFillOrigin.y = y + pdc->ptlDCOrig.y;
ptlDCOrig 是 DC 原点(窗口原点相对屏幕的偏移),因此 ptlFillOrigin 是设备坐标 下的画刷原点------模式画刷平铺时,引擎用 ptlFillOrigin 决定图案的对齐位置。
注意事项:
- 本函数不更新
pdcattr->ptlBrushOrigin!属性层副本由调用者负责(GreSetBrushOrg在调用前先写pdcattr->ptlBrushOrigin,见 dcutil.c 第 249-250 行)。因此本函数只能通过 GreSetBrushOrg 间接调用。 - 内存 DC 的
ptlDCOrig通常为 (0,0),此时填充原点 == 画刷原点。
3.6 NtGdiSetBrushOrg(dcobjs.c 第 153-195 行)
签名 (__kernel_entry):
c
BOOL APIENTRY NtGdiSetBrushOrg(HDC hdc, INT x, INT y, _Out_opt_ LPPOINT pptOut)
作用 :SetBrushOrgEx 的系统调用入口。把画刷原点设为 (x, y)(相对 DC 原点),并可选返回旧原点。
实现流程:
- 调用内部函数
GreSetBrushOrg(hdc, x, y, &ptOut)(dcutil.c 第 230-256 行):DC_LockDc,失败则EngSetLastError(ERROR_INVALID_HANDLE)返回 FALSE;- 若
pptOut != NULL,先把旧原点pdc->pdcattr->ptlBrushOrigin写入*pptOut; - 设置
pdcattr->ptlBrushOrigin.x/y = x/y; - 调用
DC_vSetBrushOrigin(pdc, x, y)同步dclevel.ptlBrushOrigin与ptlFillOrigin; DC_UnlockDc。
- 若成功且调用方要求旧原点(
pptOut != NULL),进入_SEH2_TRY:ProbeForWrite(pptOut, sizeof(POINT), 1)探测并拷贝ptOut;异常则return FALSE。
使用方式 :user32 的 SetBrushOrgEx 最终落到此入口。模式画刷在窗口滚动、多窗口平铺对齐场景会用到(保证相邻窗口的图案连续)。
注意事项 :GreSetBrushOrg 在 DC 未锁定时就写 *pptOut 属于轻量读;真正的用户缓冲写入由 NtGdiSetBrushOrg 用 SEH 保护,遵循内核入口的"Probe + 捕获"规范。
3.7 GdiSelectPalette(dcobjs.c 第 197-250 行)
签名 :HPALETTE NTAPI GdiSelectPalette(HDC hDC, HPALETTE hpal, BOOL ForceBackground)
作用 :选择逻辑调色板。注意它是 NTAPI(非 __kernel_entry),由 win32k 内部或 gdi32 的映射表调用(对应 SelectPalette)。
实现流程:
-
DC_LockDc(hDC),失败返回 NULL; -
PALETTE_ShareLockPalette(hpal)校验句柄,失败解锁 DC 返回 NULL; -
兼容性检查(带 FIXME 注释,注明不应在 PDEV 未锁定时解引用 pSurface):
cif ((!pdc->dclevel.pSurface) || (BitsPerFormat(...) <= 8 && (ppal->flFlags & PAL_INDEXED)) || (BitsPerFormat(...) > 8))即:无表面、或 8bpp 以下且为索引调色板、或高于 8bpp------满足其一即可选择;
-
记录
oldPal = pdc->dclevel.hpal,更新dclevel.hpal = hpal,DC_vSelectPalette(pdc, ppal)(dc.h 内联函数:解锁旧调色板、引用新调色板); -
置脏 :
ulDirty_ |= DIRTY_FILL | DIRTY_LINE | DIRTY_BACKGROUND | DIRTY_TEXT------调色板变化影响所有画刷的颜色翻译; -
内存 DC 分支(
IntGdiRealizePalette被注释掉,注明"This didn't work anyway"); -
解锁调色板与 DC,返回旧调色板句柄。
3.8 NtGdiSelectBrush(dcobjs.c 第 252-280 行)
签名 :HBRUSH APIENTRY NtGdiSelectBrush(IN HDC hDC, IN HBRUSH hBrush)
作用 :SelectObject(hdc, hbr) 的画刷分支。
实现流程:
c
if (hDC == NULL || hBrush == NULL) return NULL;
pDC = DC_LockDc(hDC);
if (!pDC) return NULL;
/* Simply return the user mode value, without checking */
hOrgBrush = pDC->pdcattr->hbrush;
pDC->pdcattr->hbrush = hBrush;
DC_vUpdateFillBrush(pDC);
DC_UnlockDc(pDC);
return hOrgBrush;
要点:
- "Simply return the user mode value, without checking"------直接返回旧的用户态句柄值,不做有效性验证 ;句柄的校验延迟到
DC_vUpdateFillBrush中的BRUSH_ShareLockBrush(无效句柄会在那里被检测并回滚)。 - 更新
pdcattr->hbrush后立即 调用DC_vUpdateFillBrush,保证 DC 的pbrFill/eboFill与句柄同步(这样后续GetCurrentObject或任何绘制都不需要额外判断)。 - 调用链:
SelectObject(hbr)→gdi32!SelectObject→NtGdiSelectBrush。
3.9 NtGdiSelectPen(dcobjs.c 第 282-310 行)
签名 :HPEN APIENTRY NtGdiSelectPen(IN HDC hDC, IN HPEN hPen)
作用 :SelectObject(hdc, hpen) 的画笔分支。与 NtGdiSelectBrush 完全同构:保存旧 hpen → 写入新 hpen → DC_vUpdateLineBrush(pDC) → 返回旧句柄。
注意事项 :ExtCreatePen(几何画笔)与 CreatePen(普通画笔)在 ntgdi 中都是 BRUSH 对象,这里不做区分;宽笔的几何路径在绘制阶段由 line.c 根据 pbrLine->lWidth/eWidth 处理。
3.10 DC_bIsBitmapCompatible(dcobjs.c 第 312-336 行)
签名 :BOOL NTAPI DC_bIsBitmapCompatible(PDC pdc, PSURFACE psurf)
作用 :判断位图能否选入该 DC(SelectObject(hdc, hbmp) 只允许内存 DC,且格式必须兼容)。
实现流程(逐步短路):
c
if (!(psurf->flags & API_BITMAP)) return FALSE; /* 必须是 API 位图 */
if (psurf->hSecure != NULL) return TRUE; /* DIB section 永远兼容 */
if (psurf->SurfObj.hdev == (HDEV)pdc->ppdev) return TRUE; /* 同 PDEV */
cBitsPixel = gajBitsPerFormat[psurf->SurfObj.iBitmapFormat];
if ((cBitsPixel == 1) || (cBitsPixel == pdc->ppdev->gdiinfo.cBitsPixel))
return TRUE; /* 1bpp 或与屏幕同深 */
return FALSE;
要点:
hSecure != NULL表示这是 DIB section(CreateDIBSection创建),其内存由用户映射,与任何 DC 的格式都兼容(位深由创建参数决定,Blit 时按需转换)。- "同 PDEV":兼容位图(
CreateCompatibleBitmap)的SurfObj.hdev指向创建它的显示设备,与 DC 的ppdev相同即可直接使用。 - 1bpp 位图(
CreateBitmap单色)是特例,任何 DC 都接受------因为它本质上就是 DDB 且 Blt 时按 ROP 解释。 gajBitsPerFormat是"格式→每像素位数"的全局查找表。
3.11 NtGdiSelectBitmap(dcobjs.c 第 338-479 行)
签名 :HBITMAP APIENTRY NtGdiSelectBitmap(IN HDC hdc, IN HBITMAP hbmp)
作用 :SelectObject(hdc, hbmp) 的位图分支(内存 DC 专用)。这是 dcobjs.c 中最复杂的选择函数。
实现流程:
- 参数检查:
hdc == NULL || hbmp == NULL返回 NULL;DC_LockDc(hdc)。 - 类型检查 :
pdc->dctype != DCTYPE_MEMORY直接失败(位图只能选入内存 DC)。 - 保存旧位图:
psurfOld = pdc->dclevel.pSurface;hbmpOld = psurfOld ? psurfOld->BaseObject.hHmgr : StockObjects[DEFAULT_BITMAP]。 - 幂等短路 :
hbmp == hbmpOld直接返回(重复选择同一张位图无副作用)。 - 分派:
- 若
hbmp == StockObjects[DEFAULT_BITMAP](1×1 默认位图):psurfNew = NULL,dclevel.sizl = {1, 1}; - 否则:
SURFACE_ShareLockSurface(hbmp)引用新位图(失败返回 NULL);DC_bIsBitmapCompatible(pdc, psurfNew)校验,不兼容则解锁返回 NULL;hdcOld = InterlockedCompareExchangePointer(&psurfNew->hdc, hdc, 0)------原子抢占 位图的hdc归属字段;若hdcOld != NULL说明位图已选入别的 DC,ASSERT(hdcOld != hdc)并失败返回 NULL(这是"同一张位图不能同时选入两个 DC"的强制保证);dclevel.sizl = psurfNew->SurfObj.sizlBitmap复制尺寸;- 根据
psurfNew->hSecure置/清DC_DIBSECTION脏位。
- 若
- 提交:
pdc->dclevel.pSurface = psurfNew。 - 清理旧位图:若
psurfOld非空,psurfOld->hdc = NULL(解除归属),SURFACE_ShareUnlockSurface(psurfOld)。 - 置脏 :
ulDirty_ |= DIRTY_FILL | DIRTY_LINE(表面格式变了,画刷颜色翻译可能失效);pdc->fs |= DC_DIRTY_RAO(RAO 区域需要重算)。 - 更新系统区域 :
REGION_SetRectRgn(pdc->prgnVis, 0, 0, sizl.cx, sizl.cy)------可见区域重置为整个位图大小。 - 解锁 DC,返回
hbmpOld。
注意事项:
- 位图对象被选入 DC 期间,其引用计数由 DC 持有(
SURFACE_ShareLockSurface),因此用户DeleteObject(hbmp)只会延迟到取消选择后才真正释放------这是 GDI 对象"选中即锁定"语义在内核的实现。 psurfNew->hdc字段是"该位图当前所属 DC"的标记,InterlockedCompareExchangePointer保证并发安全(多线程 DC 场景)。- 返回的旧句柄在"原本没有位图"时是
DEFAULT_BITMAP句柄,与 Windows 行为一致(GetCurrentObject(hdc, OBJ_BITMAP)也返回 DEFAULT_BITMAP)。
3.12 NtGdiSelectClipPath(dcobjs.c 第 482-546 行)
签名 :BOOL APIENTRY NtGdiSelectClipPath(HDC hDC, int Mode)
作用 :SelectClipPath 系统调用------把当前路径转换为裁剪区域,并按 Mode(RGN_AND / RGN_COPY / RGN_DIFF / RGN_OR / RGN_XOR)与现有裁剪区域合并。
实现流程:
DC_LockDc(hDC)(失败ERROR_INVALID_PARAMETER);PATH_LockPath(pdc->dclevel.hPath)锁定当前路径。- 路径必须闭合 :
pPath->state != PATH_Closed时报ERROR_CAN_NOT_COMPLETE并退出。PATH_Closed是 path.h(file:///d:/reactos/win32ss/gdi/ntgdi/path.h) 第 20 行的路径状态枚举成员。 IntSysCreateRectpRgn(0, 0, 0, 0)创建空目标区域(失败报ERROR_NOT_ENOUGH_MEMORY)。PATH_FlattenPath(pPath)把路径中的贝塞尔曲线展平成折线,得到pNewPath。PATH_PathToRegion(pNewPath, pdcattr->jFillMode, RgnPath)------按 DC 的填充模式(ALTERNATE/WINDING)把展平路径栅格化为区域。- 临时路径用完即删:
PATH_UnlockPath+PATH_Delete(pNewPath->BaseObject.hHmgr)。 - 若转换成功:
success = IntGdiExtSelectClipRgn(pdc, RgnPath, Mode) != ERROR(与现有裁剪区域按 Mode 合并)。 - 清理:
REGION_Delete(RgnPath);PATH_UnlockPath(pPath);销毁当前路径PATH_Delete(pdc->dclevel.hPath);flPath &= ~DCPATH_ACTIVE;hPath = NULL(SelectClipPath 之后路径被消耗,与 Windows 语义一致)。
注意事项 :jFillMode 是 DC 属性中的填充模式(ALTERNATE=1 / WINDING=2),决定自交路径的奇偶/非零环绕填充规则。整个函数在 DC 锁内完成,路径对象的锁定贯穿始终。
3.13 DC_hSelectFont(dcobjs.c 第 548-593 行)
签名 :HFONT NTAPI DC_hSelectFont(_In_ PDC pdc, _In_ HFONT hlfntNew)
作用:内部字体选择。注释"Legacy crap that will die with font engine rewrite"表明这是旧字体引擎的遗留路径。
实现流程:
TextIntRealizeFont(hlfntNew, NULL)预实现字体------若失败(字体无效),返回 NULL。TextIntRealizeFont是字体引擎(text.c)的"确保字体已实现"入口。hlfntOld = pdc->dclevel.plfnt->BaseObject.hHmgr取当前字体句柄。- 若
hlfntNew != hlfntOld:LFONT_ShareLockFont(hlfntNew)锁定新字体实体;成功则LFONT_ShareUnlockFont解锁旧实体,dclevel.plfnt = plfntNew,pdcattr->hlfntNew = hlfntNew;- 置脏:
ulDirty_ |= DIRTY_CHARSET(字符集/字体需要重建),ulDirty_ &= ~SLOW_WIDTHS(清除慢速宽度标记------新字体先用快速宽度路径); - 失败则回滚
pdcattr->hlfntNew = hlfntOld并返回 NULL。
- 若字体相同,直接返回旧句柄(不重新锁定)。
注意事项 :pdcattr->hlfntNew 是"逻辑选中字体",dclevel.plfnt 是"实体"。二者必须同步------这正是 NtGdiGetDCObject 的 FONT 分支返回 pdcattr->hlfntNew 的原因。DC_LockDc 的内联断言(dc.h 第 229-230 行)要求 dclevel.plfnt 永远非空且类型为字体。
3.14 NtGdiSelectFont(dcobjs.c 第 595-625 行)
签名 :HFONT APIENTRY NtGdiSelectFont(_In_ HDC hdc, _In_ HFONT hfont)
作用 :SelectObject(hdc, hfont) 的字体分支,DC_hSelectFont 的薄包装:NULL 参数检查 → DC_LockDc → DC_hSelectFont(pdc, hfont) → DC_UnlockDc → 返回旧字体。
3.15 NtGdiGetDCObject(dcobjs.c 第 627-690 行)
签名 :HANDLE APIENTRY NtGdiGetDCObject(HDC hDC, INT ObjectType)
作用 :GetCurrentObject(hdc, type) 的系统调用------返回 DC 当前选中的指定类型对象句柄。
实现流程:
-
注释注明(源自 Wine):
GetCurrentObject对空对象不设置 SetLastError。 -
if (!hDC) return NULL;→DC_LockDc(hDC)。 -
读前刷新 (保证返回的是最新选中对象):
cif (pdcattr->ulDirty_ & (DIRTY_FILL | DC_BRUSH_DIRTY)) DC_vUpdateFillBrush(pdc); if (pdcattr->ulDirty_ & (DIRTY_LINE | DC_PEN_DIRTY)) DC_vUpdateLineBrush(pdc); -
按类型分发:
GDI_OBJECT_TYPE_EXTPEN/GDI_OBJECT_TYPE_PEN→pdcattr->hpen;GDI_OBJECT_TYPE_BRUSH→pdcattr->hbrush;GDI_OBJECT_TYPE_PALETTE→pdc->dclevel.hpal;GDI_OBJECT_TYPE_FONT→pdcattr->hlfntNew;GDI_OBJECT_TYPE_BITMAP→pSurface ? pSurface->BaseObject.hHmgr : StockObjects[DEFAULT_BITMAP];GDI_OBJECT_TYPE_COLORSPACE→DPRINT1("FIXME ... not supported yet!"),返回 NULL;- default → NULL +
EngSetLastError(ERROR_INVALID_PARAMETER)。
-
DC_UnlockDc返回句柄。
注意事项 :步骤 3 的"读前刷新"很重要------因为 NtGdiSelectBrush 之外的路径(如 IntGdiSetBkColor 触发 NtGdiSelectBrush 重选)可能只设置了脏位而没更新实体,这里确保句柄与实体一致。注意调色板/字体/位图分支读取的是 dclevel/pdcattr 中保存的句柄值 ,而画笔/画刷读 pdcattr。
3.16 NtGdiGetRandomRgn(dcobjs.c 第 692-780 行)
签名 :INT APIENTRY NtGdiGetRandomRgn(HDC hdc, HRGN hrgnDest, INT iCode)
作用 :GetRandomRgn 系统调用------把 DC 的某个内部区域复制到调用方提供的区域句柄。iCode 可取 CLIPRGN / METARGN / APIRGN / SYSRGN。
实现流程:
DC_LockDc(hdc),失败ERROR_INVALID_HANDLE返回 -1。- 按 iCode 选源区域:
CLIPRGN→dclevel.prgnClip(API 裁剪区域);METARGN→dclevel.prgnMeta(元区域);APIRGN→ 若pdc->fs & DC_DIRTY_RAO先CLIPPING_UpdateGCRemain(pdc)------即CLIPPING_UpdateGCRegion(pdc)重算区域;然后按prgnAPI→prgnClip→prgnMeta的优先级取源;SYSRGN→pdc->prgnVis(系统/可见区域);- default 不做处理(prgnSrc 保持 NULL)。
- 源非空时:
REGION_LockRgn(hrgnDest)锁定目标;IntGdiCombineRgn(prgnDest, prgnSrc, 0, RGN_COPY)拷贝(失败返回 -1)。 - SYSRGN 特例 :
REGION_bOffsetRgn(prgnDest, pdc->ptlDCOrig.x, pdc->ptlDCOrig.y)------系统区域是设备坐标,需按 DC 原点偏移到客户坐标(附 FIXME 注释:因为区域已被修改,严格说应先拷贝再偏移)。 - 返回:有源区域且拷贝成功 → 1;源为空 → 0;失败 → -1。
要点:函数头的大段注释(第 692-706 行)澄清了区域语义,明确纠正了 CodeProject 上一篇裁剪指南的错误:
The intersection of the clip with the meta region is not Rao it's API!
Rao = API & Vis(Rao 区域 = API 区域 ∩ 系统区域)
API = Meta ∩ Clip(API 区域 = 元区域 ∩ 裁剪区域)
注意事项 :APIRGN 分支在 prgnAPI 为 NULL 时回退到 prgnClip/prgnMeta------因为尚未触发区域计算时这两个区域就是 API 区域的近似。DC_DIRTY_RAO 由 NtGdiSelectBitmap、IntGdiCleanDC 等置位。
3.17 NtGdiEnumObjects(dcobjs.c 第 782-792 行)
签名 :ULONG APIENTRY NtGdiEnumObjects(IN HDC hdc, IN INT iObjectType, IN ULONG cjBuf, OUT OPTIONAL PVOID pvBuf)
作用 :EnumObjects 系统调用------枚举 DC 中选中的对象(Win16 遗留 API)。当前实现是占位桩:
c
UNIMPLEMENTED;
return 0;
UNIMPLEMENTED 宏会在调试构建输出 WARNING: ... is UNIMPLEMENTED! 日志。调用方(gdi32 的 EnumObjects)收到 0 表示"枚举结束/失败",属于已知未实现功能。
4. 画刷更新机制详解
4.1 脏标记如何触发重建
整个机制的骨架是"置脏者 "与"重建者"两个角色:
置脏者(写 pdcattr->ulDirty_)
│
├─ NtGdiSelectBrush / NtGdiSelectPen → 写 hbrush / hpen + 立即 DC_vUpdate*Brush
├─ IntGdiSetTextColor (dcutil.c) → DIRTY_TEXT|DIRTY_LINE|DIRTY_FILL + 立即重建
├─ IntGdiSetBkColor (dcutil.c) → DIRTY_BACKGROUND|DIRTY_LINE|DIRTY_FILL + 重选画刷
├─ IntSetDCBrushColor (dcutil.c) → DIRTY_FILL | DC_BRUSH_DIRTY
├─ IntSetDCPenColor (dcutil.c) → DIRTY_LINE | DC_PEN_DIRTY
├─ GdiSelectPalette → DIRTY_FILL|LINE|BACKGROUND|TEXT
├─ NtGdiSelectBitmap → DIRTY_FILL | DIRTY_LINE + fs |= DC_DIRTY_RAO
├─ DC_hSelectFont → DIRTY_CHARSET,清 SLOW_WIDTHS
└─ DC_vCopyState (dcstate.c) → 全部画刷脏位 + 字体 + ICM 位
│
▼
重建者(读 ulDirty_)
├─ DC_vUpdateFillBrush 清除 DIRTY_FILL|DC_BRUSH_DIRTY
├─ DC_vUpdateLineBrush 清除 DIRTY_LINE|DC_PEN_DIRTY
├─ DC_vUpdateTextBrush 清除 DIRTY_TEXT
├─ DC_vUpdateBackgroundBrush 清除 DIRTY_BACKGROUND
└─ NtGdiGetDCObject 按 (DIRTY_FILL|DC_BRUSH_DIRTY) / (DIRTY_LINE|DC_PEN_DIRTY) 先重建再读
触发重建的三个典型时机:
- 选择/设置后立即重建 :
NtGdiSelectBrush/NtGdiSelectPen/IntGdiSetTextColor等入口在修改属性后同步调用 对应的 Update 函数。这样GetCurrentObject、GetObject及接下来的第一次绘制都看到一致状态。 - 绘制前按需重建 :填充/线条/文本绘制入口在取
pdc->eboFill等对象前调用DC_vUpdateFillBrush等。由于多数情况下脏位已被清除(上一次绘制已重建过),函数退化为"比较句柄 + 清脏",开销极小。 - 查询前防御性重建 :
NtGdiGetDCObject在读取句柄前检查脏位------防止某些只置脏而未重建的路径(如IntGdiSetBkColor通过重选画刷间接触发)暴露过期实体。
4.2 四个引擎画刷的用途对照
| 引擎对象 | 逻辑来源 | 颜色来源 | 使用方 |
|---|---|---|---|
eboFill |
pbrFill(=hbrush 对应实体) |
若为 DC_BRUSH 用 crBrushClr;否则画刷自带颜色 |
区域/路径填充、PatBlt、位图 ROP 的图案 |
eboLine |
pbrLine(=hpen 对应实体) |
若为 DC_PEN 用 crPenClr;否则画笔颜色 |
线/弧/边框绘制(line.c、arc.c、frame 类) |
eboText |
恒为 pbrDefaultBrush |
恒为 crForegroundClr(文本前景色) |
字形渲染(IntTextOut 等),按 ROP 写入 |
eboBackground |
恒为 pbrDefaultBrush |
恒为 crBackgroundClr(背景色) |
ETO_OPAQUE 背景、透明/不透明背景填充 |
注意 eboText 与 eboBackground 不使用 hbrush/hpen 的实体------它们永远是默认画刷实体 + 动态纯色,因为文本色/背景色变化频率远高于"选中新画刷"。
4.3 EBRUSHOBJ 的生命周期(eng/engbrush.c)
EBRUSHOBJ_vUpdateFromDC(engbrush.c 第 213-225 行)只是两行:
c
EBRUSHOBJ_vCleanup(pebo); /* 释放旧实现(pengbrush/pvRbrush/掩码/调色板引用) */
EBRUSHOBJ_vInitFromDC(pebo, pbrush, pdc); /* 用 DC 的当前表面/颜色/调色板重新初始化 */
EBRUSHOBJ_vCleanup(第 172-211 行):依次释放 GDI 实现位图(SURFACE_ShareUnlockSurface(pengbrush))、驱动实现(EngFreeMem(pvRbrush))、掩码位图(psoMask)、三个调色板引用(ppalSurf/ppalDC/ppalDIB)。EBRUSHOBJ_vInitFromDC(第 109-117 行)→EBRUSHOBJ_vInit(第 49-107 行):- 记录
pbrush、flattrs = pbrush->flAttrs、psurfTrg = pdc->dclevel.pSurface(内存 DC 未选位图时为psurfDefaultBitmap); - 引用
psurfTrg->ppal作为ppalSurf,引用ppalDC(DC 调色板,缺省gppalDefault); - 按属性分派:
BR_IS_NULL→ iSolidColor=0;BR_IS_SOLID→EBRUSHOBJ_vSetSolidRGBColor(pebo, pbrush->BrushAttr.lbColor);模式画刷 → iSolidColor=0xFFFFFFFF(占位,"未实现"),阴影画刷把crCurrentText置为画刷前景色。
- 记录
EBRUSHOBJ_vSetSolidRGBColor(第 119-170 行):- 单色表面(PAL_MONOCHROME):按背景色求最近索引 iBackIndex,颜色等于背景则取 iBackIndex,否则取 iForeIndex=iBackIndex^1,并置
BR_NEED_BK_CLR; - 一般表面:
EXLATEOBJ_vInitialize(&gpalRGB → ppalSurf)+XLATEOBJ_iXlate把 RGB 翻译为目标格式的索引。
- 单色表面(PAL_MONOCHROME):按背景色求最近索引 iBackIndex,颜色等于背景则取 iBackIndex,否则取 iForeIndex=iBackIndex^1,并置
EBRUSHOBJ_bRealizeBrush(第 331-435 行):模式画刷的真正实现------选择DrvRealizeBrush(调用驱动)或EngRealizeBrush(第 230-286 行:EngCreateBitmap+EngCopyBits把图案按目标格式复制到新位图),处理阴影画刷(ppdev->ahsurf[iHatch])与 DIB_PAL_COLORS 调色板修复(FixupDIBBrushPalette)。
4.4 一个典型场景的时序
以"SetTextColor 后 TextOut"为例:
SetTextColor(hdc, RED)
└─ IntGdiSetTextColor:crForegroundClr=RED;置 DIRTY_TEXT|DIRTY_LINE|DIRTY_FILL
└─ DC_vUpdateTextBrush:DIRTY_TEXT → 重建 eboText(pbrDefaultBrush + 新调色板引用)
→ vSetSolidRGBColor(eboText, RED)(翻译到表面格式)
└─ DC_vUpdateLineBrush / DC_vUpdateFillBrush:同步刷新(清脏)
TextOut(hdc, ...)
└─ IntTextOut:绘制前调用 DC_vUpdateTextBrush(脏位已清,近乎无操作)
└─ 用 eboText.BrushObject 参与 ROP 写像素
可见"立即重建"与"按需重建"互为冗余:前者保证一致性,后者保证性能(热路径上不重复重建)。
5. SelectObject 各类型流程汇总
用户态 SelectObject(hdc, hObj) 按句柄类型分发到不同的 NtGdi* 入口,这里汇总五种对象的完整流程:
| 对象类型 | 入口 | 关键校验 | 更新动作 | 返回 |
|---|---|---|---|---|
| 画刷 | NtGdiSelectBrush |
仅 NULL 检查 | pdcattr->hbrush + DC_vUpdateFillBrush |
旧画刷句柄 |
| 画笔 | NtGdiSelectPen |
仅 NULL 检查 | pdcattr->hpen + DC_vUpdateLineBrush |
旧画笔句柄 |
| 位图 | NtGdiSelectBitmap |
必须内存 DC;格式兼容(DC_bIsBitmapCompatible);未被其他 DC 选中 |
dclevel.pSurface/sizl + 置脏 + 重建 prgnVis |
旧位图句柄 |
| 字体 | NtGdiSelectFont → DC_hSelectFont |
TextIntRealizeFont 预实现 |
dclevel.plfnt/hlfntNew + DIRTY_CHARSET |
旧字体句柄 |
| 路径→裁剪 | NtGdiSelectClipPath |
路径必须闭合(PATH_Closed) |
PATH_FlattenPath + PATH_PathToRegion + IntGdiExtSelectClipRgn;销毁路径 |
成功/失败 |
三类通用模式:
- 句柄-实体同步模式 (画刷/画笔/字体):
pdcattr保存句柄,dclevel保存被引用计数的实体;切换时先锁定新实体(*_ShareLock*),成功才解锁旧实体并提交,失败则回滚句柄。原子性由 DC 锁保证。 - 独占归属模式 (位图):
psurf->hdc记录归属 DC,用InterlockedCompareExchangePointer原子抢占,保证位图同一时刻只属于一个 DC。 - 消耗模式(路径):SelectClipPath 把路径"消费"掉(转换为区域后删除路径),与 Windows 语义一致------再次 SelectClipPath 前必须先 BeginPath。
6. 调用链(mermaid)
6.1 SelectObject 画刷/画笔
EBRUSHOBJ (engbrush.c) DC (dcobjs.c) NtGdiSelectBrush/Pen user32/gdi32full 应用 (gdi32) EBRUSHOBJ (engbrush.c) DC (dcobjs.c) NtGdiSelectBrush/Pen user32/gdi32full 应用 (gdi32) #mermaid-svg-AXGppRFyTiNyjdLd{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-AXGppRFyTiNyjdLd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AXGppRFyTiNyjdLd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AXGppRFyTiNyjdLd .error-icon{fill:#552222;}#mermaid-svg-AXGppRFyTiNyjdLd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AXGppRFyTiNyjdLd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AXGppRFyTiNyjdLd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AXGppRFyTiNyjdLd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AXGppRFyTiNyjdLd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AXGppRFyTiNyjdLd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AXGppRFyTiNyjdLd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AXGppRFyTiNyjdLd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AXGppRFyTiNyjdLd .marker.cross{stroke:#333333;}#mermaid-svg-AXGppRFyTiNyjdLd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AXGppRFyTiNyjdLd p{margin:0;}#mermaid-svg-AXGppRFyTiNyjdLd .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AXGppRFyTiNyjdLd text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-AXGppRFyTiNyjdLd .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AXGppRFyTiNyjdLd .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-AXGppRFyTiNyjdLd .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-AXGppRFyTiNyjdLd .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-AXGppRFyTiNyjdLd #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-AXGppRFyTiNyjdLd .sequenceNumber{fill:white;}#mermaid-svg-AXGppRFyTiNyjdLd #sequencenumber{fill:#333;}#mermaid-svg-AXGppRFyTiNyjdLd #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-AXGppRFyTiNyjdLd .messageText{fill:#333;stroke:none;}#mermaid-svg-AXGppRFyTiNyjdLd .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AXGppRFyTiNyjdLd .labelText,#mermaid-svg-AXGppRFyTiNyjdLd .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-AXGppRFyTiNyjdLd .loopText,#mermaid-svg-AXGppRFyTiNyjdLd .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-AXGppRFyTiNyjdLd .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AXGppRFyTiNyjdLd .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-AXGppRFyTiNyjdLd .noteText,#mermaid-svg-AXGppRFyTiNyjdLd .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-AXGppRFyTiNyjdLd .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AXGppRFyTiNyjdLd .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AXGppRFyTiNyjdLd .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AXGppRFyTiNyjdLd .actorPopupMenu{position:absolute;}#mermaid-svg-AXGppRFyTiNyjdLd .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-AXGppRFyTiNyjdLd .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AXGppRFyTiNyjdLd .actor-man circle,#mermaid-svg-AXGppRFyTiNyjdLd line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-AXGppRFyTiNyjdLd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SelectObject(hdc, hbr/hpen)NtGdiSelectBrush(hDC, hBrush) / NtGdiSelectPenDC_LockDc(hDC)保存旧句柄,写入 pdcattr->>hbrush/hpenDC_vUpdateFillBrush/DC_vUpdateLineBrush句柄变化? BRUSH_ShareLockBrush/PEN_ShareLockPenEBRUSHOBJ_vUpdateFromDC(ebo, pbr, pdc)EBRUSHOBJ_vCleanup + EBRUSHOBJ_vInitFromDC(DC_BRUSH/DC_PEN) EBRUSHOBJ_vSetSolidRGBColor(crBrushClr/crPenClr)DC_UnlockDc返回旧句柄
6.2 SelectObject 位图(内存 DC)
渲染错误: Mermaid 渲染失败: Parse error on line 8: ...ctype==DCTYPE_MEMORY K->>D: 幂等检查 hbm -----------------------^ Expecting '()', 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'SOLID_ARROW_TOP', 'SOLID_ARROW_BOTTOM', 'STICK_ARROW_TOP', 'STICK_ARROW_BOTTOM', 'SOLID_ARROW_TOP_DOTTED', 'SOLID_ARROW_BOTTOM_DOTTED', 'STICK_ARROW_TOP_DOTTED', 'STICK_ARROW_BOTTOM_DOTTED', 'SOLID_ARROW_TOP_REVERSE', 'SOLID_ARROW_BOTTOM_REVERSE', 'STICK_ARROW_TOP_REVERSE', 'STICK_ARROW_BOTTOM_REVERSE', 'SOLID_ARROW_TOP_REVERSE_DOTTED', 'SOLID_ARROW_BOTTOM_REVERSE_DOTTED', 'STICK_ARROW_TOP_REVERSE_DOTTED', 'STICK_ARROW_BOTTOM_REVERSE_DOTTED', 'BIDIRECTIONAL_SOLID_ARROW', 'DOTTED_ARROW', 'BIDIRECTIONAL_DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT', 'DOTTED_POINT', got 'NEWLINE'
6.3 查询路径(GetCurrentObject / GetRandomRgn)
DC NtGdiGetDCObject / NtGdiGetRandomRgn 应用 DC NtGdiGetDCObject / NtGdiGetRandomRgn 应用 #mermaid-svg-xKedGxxqd71o3yiS{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-xKedGxxqd71o3yiS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xKedGxxqd71o3yiS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xKedGxxqd71o3yiS .error-icon{fill:#552222;}#mermaid-svg-xKedGxxqd71o3yiS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xKedGxxqd71o3yiS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xKedGxxqd71o3yiS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xKedGxxqd71o3yiS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xKedGxxqd71o3yiS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xKedGxxqd71o3yiS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xKedGxxqd71o3yiS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xKedGxxqd71o3yiS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xKedGxxqd71o3yiS .marker.cross{stroke:#333333;}#mermaid-svg-xKedGxxqd71o3yiS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xKedGxxqd71o3yiS p{margin:0;}#mermaid-svg-xKedGxxqd71o3yiS .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-xKedGxxqd71o3yiS text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-xKedGxxqd71o3yiS .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-xKedGxxqd71o3yiS .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-xKedGxxqd71o3yiS .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-xKedGxxqd71o3yiS .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-xKedGxxqd71o3yiS #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-xKedGxxqd71o3yiS .sequenceNumber{fill:white;}#mermaid-svg-xKedGxxqd71o3yiS #sequencenumber{fill:#333;}#mermaid-svg-xKedGxxqd71o3yiS #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-xKedGxxqd71o3yiS .messageText{fill:#333;stroke:none;}#mermaid-svg-xKedGxxqd71o3yiS .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-xKedGxxqd71o3yiS .labelText,#mermaid-svg-xKedGxxqd71o3yiS .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-xKedGxxqd71o3yiS .loopText,#mermaid-svg-xKedGxxqd71o3yiS .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-xKedGxxqd71o3yiS .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-xKedGxxqd71o3yiS .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-xKedGxxqd71o3yiS .noteText,#mermaid-svg-xKedGxxqd71o3yiS .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-xKedGxxqd71o3yiS .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-xKedGxxqd71o3yiS .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-xKedGxxqd71o3yiS .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-xKedGxxqd71o3yiS .actorPopupMenu{position:absolute;}#mermaid-svg-xKedGxxqd71o3yiS .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-xKedGxxqd71o3yiS .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-xKedGxxqd71o3yiS .actor-man circle,#mermaid-svg-xKedGxxqd71o3yiS line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-xKedGxxqd71o3yiS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} altGetDCObjectGetRandomRgn GetCurrentObject(hdc, OBJ_BRUSH) / GetRandomRgn(hdc, hrgn, iCode)DC_LockDc若脏则 DC_vUpdateFillBrush/LineBrush返回 pdcattr->>hbrush/hpen/hlfntNew 或 dclevel.hpal/pSurface 句柄按 iCode 选 prgnClip/prgnMeta/prgnAPI/prgnVisIntGdiCombineRgn(目标, 源, RGN_COPY) (+SYSRGN 偏移)1(成功)/0(空)/-1(失败)
6.4 字体选择与裁剪路径
渲染错误: Mermaid 渲染失败: Parse error on line 13: ...ath); 校验 PATH_Closed K->>P: PATH_Fla -----------------------^ Expecting '()', 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'SOLID_ARROW_TOP', 'SOLID_ARROW_BOTTOM', 'STICK_ARROW_TOP', 'STICK_ARROW_BOTTOM', 'SOLID_ARROW_TOP_DOTTED', 'SOLID_ARROW_BOTTOM_DOTTED', 'STICK_ARROW_TOP_DOTTED', 'STICK_ARROW_BOTTOM_DOTTED', 'SOLID_ARROW_TOP_REVERSE', 'SOLID_ARROW_BOTTOM_REVERSE', 'STICK_ARROW_TOP_REVERSE', 'STICK_ARROW_BOTTOM_REVERSE', 'SOLID_ARROW_TOP_REVERSE_DOTTED', 'SOLID_ARROW_BOTTOM_REVERSE_DOTTED', 'STICK_ARROW_TOP_REVERSE_DOTTED', 'STICK_ARROW_BOTTOM_REVERSE_DOTTED', 'BIDIRECTIONAL_SOLID_ARROW', 'DOTTED_ARROW', 'BIDIRECTIONAL_DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT', 'DOTTED_POINT', got 'NEWLINE'
7. 与 dcstate / dcattr / engbrush 的关系
7.1 dcstate.c(SaveDC/RestoreDC 与状态拷贝)
dcstate.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcstate.c) 的核心是 DC_vCopyState(第 14-68 行),它与 dcobjs.c 的画刷体系深度耦合:
- 整块拷贝属性 :
*pdcDst->pdcattr = *pdcSrc->pdcattr(第 21 行)------把源 DC 的全部属性(含句柄与颜色)复制给目标; - 全部置脏 :第 27 行一次性置
DIRTY_FILL|DIRTY_LINE|DIRTY_TEXT|DIRTY_BACKGROUND|DIRTY_CHARSET|DC_ICM_NOT_CALIBRATED|DC_ICM_NOT_SET------因为拷贝后的 EBRUSHOBJ 是目标 DC 的旧实体,必须全部重建; - 实体重新引用 :第 42-49 行用
DC_vSelectFillBrush/DC_vSelectLineBrush/DC_vSelectPalette(dc.h 内联函数:先解锁旧实体、再引用新实体)同步pbrFill/pbrLine/ppal,字体用LFONT_ShareUnlockFont+GDIOBJ_vReferenceObjectByPointer; IntGdiCleanDC(第 71-117 行)在清理后主动调用四个 Update 函数 (第 86-89 行),注释解释了原因:EBRUSHOBJ 引用了属于旧进程的调色板,清理后 DC 可能被别的进程复用(如GetDC(0)),必须立即重建;NtGdiSaveDC(第 255-325 行)分配一个新的 DC(DC_AllocDcWithHandle+DC_vInitDc)作为保存层,DC_vCopyState把当前状态拷贝进去,并以hdcSave链成栈;DC_vRestoreDC/NtGdiRestoreDC(第 136-252 行)按保存层号回滚,同样走DC_vCopyState,内存 DC 额外恢复表面DC_vSelectSurface(pdc, pdcSave->dclevel.pSurface)(dc.h 第 246-259 行内联函数)。
7.2 dcattr.c(DC_ATTR 的属性池分配)
dcattr.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcattr.c) 只有两个函数,负责 pdcattr 指向的真实属性存储:
DC_bAllocDcAttr(第 29-55 行):从当前进程的 GDI 属性池ppi->pPoolDcAttr分配一块DC_ATTR(GdiPoolAllocate),把内核态pdc->dcattr拷贝过去,并通过GDIOBJ_vSetObjectAttr挂到句柄表条目。DC 被某个进程"拥有"时调用,保证属性的进程隔离与统一回收。DC_vFreeDcAttr(第 57-77 行):若pdcattr != &pdc->dcattr(说明是池分配),归还池并复位为内嵌备份。
文件头定义的 GDI_DC_ATTR_FREELIST/GDI_DC_ATTR_ENTRY(第 16-26 行)实现批量为 8 个 DC_ATTR 预分配 的缓存(GDIDCATTRFREE),减少频繁分配的开销。dcobjs.c 的四个 Update 函数访问的 pdc->pdcattr 既可能是内嵌副本也可能是池分配------这正是它们总用指针 pdcattr 而非结构值的原因。
7.3 engbrush.c(EBRUSHOBJ 的实现层)
engbrush.c(file:///d:/reactos/win32ss/gdi/eng/engbrush.c) 提供 dcobjs.c 调用的全部 EBRUSHOBJ 操作(见 4.3 节)。二者分工:
- dcobjs.c 负责"何时重建"(策略):句柄对齐、脏标记判定、DC_BRUSH/DC_PEN 颜色注入;
- engbrush.c 负责"如何重建" (机制):调色板引用管理、颜色翻译(
EXLATEOBJ)、模式位图实现(EngRealizeBrush/DrvRealizeBrush)、DIB 调色板修复(FixupDIBBrushPalette)。
InitBrushImpl(第 27-47 行)在初始化时为 6 种阴影样式(HS_DDI_MAX)各创建一个 8×8 的 1bpp 图案位图(gahsurfHatch),阴影画刷的实现直接复用这些位图。
7.4 与其他模块的边界
| 模块 | 与 dcobjs.c 的关系 |
|---|---|
| gdiobj.c / gdi.h | DC_LockDc/DC_UnlockDc(dc.h 内联,基于 GDIOBJ_LockObject)、BRUSH_ShareLockBrush、PEN_ShareLockPen、LFONT_ShareLockFont、SURFACE_ShareLockSurface------所有对象引用计数操作的来源 |
| dclife.c | DC 的创建/销毁(DC_vInitDc、DC_vCleanup):初始化四个 EBRUSHOBJ(dclife.c 第 290-293 行)、默认画刷/画笔/字体/颜色(第 271-282 行)、清理时 EBRUSHOBJ_vCleanup ×4(第 374-377 行) |
| stockobj.c | StockObjects[] 全局数组:DC_BRUSH(第 256 行)、DC_PEN(第 265 行)、DEFAULT_BITMAP(第 269 行)、DEFAULT_PALETTE(第 273 行) |
| clipping.c | IntGdiExtSelectClipRgn(SelectClipPath 的合并目标)、CLIPPING_UpdateGCRegion(GetRandomRgn 的 APIRGN 重算) |
| path.c | PATH_LockPath/PATH_FlattenPath/PATH_PathToRegion/PATH_Delete(SelectClipPath 的路径处理) |
| text.c | TextIntRealizeFont(DC_hSelectFont 的字体预实现) |
| line.c | 读取 pbrLine/eboLine 做实际线条绘制 |
| font 引擎 | LFONT_ShareLockFont/LFONT_ShareUnlockFont(字体实体引用) |
8. 源码索引
| 文件 | 关键内容 | 行号 |
|---|---|---|
| dcobjs.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcobjs.c) | DC_vUpdateFillBrush / DC_vUpdateLineBrush / DC_vUpdateTextBrush / DC_vUpdateBackgroundBrush / DC_vSetBrushOrigin / NtGdiSetBrushOrg / GdiSelectPalette / NtGdiSelectBrush / NtGdiSelectPen / DC_bIsBitmapCompatible / NtGdiSelectBitmap / NtGdiSelectClipPath / DC_hSelectFont / NtGdiSelectFont / NtGdiGetDCObject / NtGdiGetRandomRgn / NtGdiEnumObjects | 14-792 |
| dc.h(file:///d:/reactos/win32ss/gdi/ntgdi/dc.h) | DC / DCLEVEL / DCTYPE / DCFLAGS 定义;DC_LockDc / DC_UnlockDc / DC_vSelectSurface / DC_vSelectFillBrush / DC_vSelectLineBrush / DC_vSelectPalette 内联函数 | 17-295 |
| ntgdihdl.h(file:///d:/reactos/win32ss/include/ntgdihdl.h) | DC_ATTR 结构、ulDirty_ 脏标记位定义、GDI 对象类型常量 | 122-351 |
| brush.h(file:///d:/reactos/win32ss/gdi/ntgdi/brush.h) | BRUSH / BRUSHBODY / EBRUSHOBJ 结构、BR_IS_* 属性、BRUSH_ShareLockBrush / BRUSH_ShareUnlockBrush | 14-192 |
| engbrush.c(file:///d:/reactos/win32ss/gdi/eng/engbrush.c) | EBRUSHOBJ_vInit / vInitFromDC / vUpdateFromDC / vSetSolidRGBColor / vCleanup / bRealizeBrush / EngRealizeBrush / BRUSHOBJ_pvAllocRbrush 等 | 27-523 |
| dcattr.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcattr.c) | DC_bAllocDcAttr / DC_vFreeDcAttr(GDI 属性池) | 14-78 |
| dcstate.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcstate.c) | DC_vCopyState / IntGdiCleanDC / NtGdiSaveDC / NtGdiRestoreDC / DC_vRestoreDC | 14-325 |
| dcutil.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcutil.c) | GreSetBrushOrg / IntGdiSetBkColor / IntGdiSetTextColor / IntSetDCBrushColor / IntSetDCPenColor / TranslateCOLORREF / IntSetDefaultRegion | 34-962 |
| dclife.c(file:///d:/reactos/win32ss/gdi/ntgdi/dclife.c) | DC_vInitDc(默认画刷/颜色/EBRUSHOBJ 初始化)、DC_vCleanup、DC_vSetOwner | 250-440 |
| stockobj.c(file:///d:/reactos/win32ss/gdi/ntgdi/stockobj.c) | StockObjects\[\]:DC_BRUSH / DC_PEN / DEFAULT_BITMAP / DEFAULT_PALETTE 等库存对象创建 | 100-273 |
| path.h(file:///d:/reactos/win32ss/gdi/ntgdi/path.h) | PATH_LockPath / PATH_FlattenPath / PATH_PathToRegion / PATH_Delete / PATH_Closed | 20-102 |
9. 已知问题与 TODO
NtGdiEnumObjects是 UNIMPLEMENTED 桩------EnumObjectsAPI 在 ReactOS 上尚不可用。NtGdiGetDCObject的 OBJ_COLORSPACE 分支打印FIXME并返回 NULL(参考代码被注释:dc->dclevel.pColorSpace.BaseObject.hHmgr)。GdiSelectPalette的ForceBackground参数未使用(FIXME: Mark the palette as a fore\\background pal);兼容性检查处注明不应在 PDEV 未锁定时解引用 pSurface。NtGdiGetRandomRgn的 SYSRGN 偏移逻辑有 FIXME:应先拷贝源区域再偏移,避免就地修改后的语义偏差。DC_hSelectFont的注释"Legacy crap that will die with font engine rewrite"表明字体选择依赖旧字体引擎的TextIntRealizeFont,待字体引擎重写后移除。
10. 结语
dcobjs.c 虽然只有约 800 行,却是 ntgdi 中"DC 状态一致性"的中枢:
- 句柄层(DC_ATTR)与实体层(DCLEVEL) 的双层结构让逻辑选择与物理锁定解耦,配合引用计数实现了"选中即锁定、删除延迟释放"的 GDI 语义;
- 脏标记(ulDirty_)驱动的惰性 EBRUSHOBJ 重建 避免了每次绘制都重新翻译颜色的开销,是性能与正确性之间的精妙平衡;
- 四个 Update 函数 各自独立但模式统一(句柄对齐 → 实体重建 → 颜色注入 → 清脏),是理解
SelectObject全家族与绘制引擎接口的钥匙。
下一篇可以沿着"脏标记的下游"继续:分析绘制入口(fill.c / line.c / text.c)如何消费 eboFill/eboLine/eboText,或沿"状态来源"回溯 dcstate.c 的 SaveDC/RestoreDC 完整生命周期。
本文档基于 ReactOS 源代码
win32ss/gdi/ntgdi/dcobjs.c、win32ss/gdi/ntgdi/dc.h、win32ss/gdi/ntgdi/dcattr.c、win32ss/gdi/ntgdi/dcstate.c、win32ss/gdi/ntgdi/dcutil.c、win32ss/gdi/eng/engbrush.c、win32ss/include/ntgdihdl.h分析(2026 年 8 月)