ReactOS 图形系统分析(39):DC 对象 — dcobjs.c

ReactOS 图形系统分析(39):DC 对象 --- dcobjs.c

1. 概述

dcobjs.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcobjs.c) 是 ntgdi(内核模式 GDI)中负责 DC(Device Context,设备描述表)对象选择与画刷更新 的核心文件。它实现了用户态 GDI 与内核 DC 对象之间的桥接层,包含以下三类职责:

  1. 对象选择(SelectObject 家族) :位图(NtGdiSelectBitmap)、画刷(NtGdiSelectBrush)、画笔(NtGdiSelectPen)、字体(NtGdiSelectFont)、裁剪路径(NtGdiSelectClipPath)。
  2. DC 状态查询(GetCurrentObject 家族)NtGdiGetDCObject(当前选中对象)、NtGdiGetRandomRgn(裁剪相关区域)、NtGdiEnumObjects(对象枚举)。
  3. 画刷脏标记更新(内部基础设施)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)保存用户可感知 的句柄与颜色值------hbrushhpencrForegroundClrcrBackgroundClrcrBrushClrcrPenClr 等。用户调用 SelectObjectSetTextColor 只会修改这一层。
  • 物化层 :引擎(eng/engbrush.ceng/line.cdib/ 等)绘制时不能直接用逻辑句柄,它需要已经针对目标表面格式(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 _DCdc.h(file:///d:/reactos/win32ss/gdi/ntgdi/dc.h) 第 95-137 行)是 DC 对象的完整描述,第一个成员必须是 BASEOBJECT(GDI 句柄表对象的公共头,gdiobj.h 中定义,包含 hHmgrpUserpNext 等),不能移动位置:

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;

关键点:

  • pdcattrdcattr 是"指针 + 备份"关系: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;

DCLEVELDC_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 的核心工作就是把这两层对齐:比较 hbrushpbrFill->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_DIRTYDC_vUpdateLineBrush 清除 DIRTY_LINE|DC_PEN_DIRTYNtGdiGetDCObject 在读取前正是用 (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 + BRUSHBODYBRUSHBODY 布局取自冯远(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_ShareLockBrushPEN_ShareLockPenbrush.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(引擎填充画刷)。

实现流程(四步):

  1. 句柄对齐 :若 pdcattr->hbrush != pdc->dclevel.pbrFill->BaseObject.hHmgr,说明用户换过画刷。尝试 BRUSH_ShareLockBrush(pdcattr->hbrush) 锁定新画刷:
    • 成功:BRUSH_ShareUnlockBrush 解锁旧画刷(减少引用计数),pbrFill = pbrFill新,置 DIRTY_FILL
    • 失败:句柄无效,回滚 pdcattr->hbrush = pbrFill->BaseObject.hHmgr(恢复旧句柄,防止脏句柄继续流传)。
  2. 实体重建 :若 ulDirty_ & DIRTY_FILL,调用 EBRUSHOBJ_vUpdateFromDC(&pdc->eboFill, pdc->dclevel.pbrFill, pdc)(内部 = 清理 + 用 DC 上下文重新初始化,见第 4 节)。
  3. 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,所以可以安全调用纯色设置路径。
  4. 清脏pdcattr->ulDirty_ &= ~(DIRTY_FILL | DC_BRUSH_DIRTY)

使用方式 :所有填充绘制入口(如 IntGdiFillRgnIntGdiFillPathIntStretchBlt 的内部填充)在动手前调用;NtGdiSelectBrushIntGdiSetTextColorIntGdiSetBkColorIntSetDCBrushColorGdiSelectPalette 也会显式调用。

注意事项

  • 画刷句柄的"脏"是靠 pdcattr->hbrushpbrFill->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

实现流程:与填充版本完全对称------

  1. pdcattr->hpen != pdc->dclevel.pbrLine->BaseObject.hHmgrPEN_ShareLockPen 锁定新画笔,成功则解锁旧画笔并更新 pbrLine、置 DIRTY_LINE;失败则回滚 hpen
  2. DIRTY_LINEEBRUSHOBJ_vUpdateFromDC(&pdc->eboLine, pdc->dclevel.pbrLine, pdc)
  3. hpen == StockObjects[DC_PEN]EBRUSHOBJ_vSetSolidRGBColor(&pdc->eboLine, pdcattr->crPenClr)(DC_PEN 由 IntCreateStockPen 创建,见 stockobj.c 第 265 行,BR_IS_OLDSTYLEPEN|BR_IS_SOLID)。
  4. 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_BACKGROUNDEBRUSHOBJ_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 原点),并可选返回旧原点。

实现流程

  1. 调用内部函数 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.ptlBrushOriginptlFillOrigin
    • DC_UnlockDc
  2. 若成功且调用方要求旧原点(pptOut != NULL),进入 _SEH2_TRYProbeForWrite(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)。

实现流程

  1. DC_LockDc(hDC),失败返回 NULL;

  2. PALETTE_ShareLockPalette(hpal) 校验句柄,失败解锁 DC 返回 NULL;

  3. 兼容性检查(带 FIXME 注释,注明不应在 PDEV 未锁定时解引用 pSurface):

    c 复制代码
    if ((!pdc->dclevel.pSurface) ||
        (BitsPerFormat(...) <= 8 && (ppal->flFlags & PAL_INDEXED)) ||
        (BitsPerFormat(...) > 8))

    即:无表面、或 8bpp 以下且为索引调色板、或高于 8bpp------满足其一即可选择;

  4. 记录 oldPal = pdc->dclevel.hpal,更新 dclevel.hpal = hpalDC_vSelectPalette(pdc, ppal)(dc.h 内联函数:解锁旧调色板、引用新调色板);

  5. 置脏ulDirty_ |= DIRTY_FILL | DIRTY_LINE | DIRTY_BACKGROUND | DIRTY_TEXT------调色板变化影响所有画刷的颜色翻译;

  6. 内存 DC 分支(IntGdiRealizePalette 被注释掉,注明"This didn't work anyway");

  7. 解锁调色板与 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!SelectObjectNtGdiSelectBrush

3.9 NtGdiSelectPen(dcobjs.c 第 282-310 行)

签名HPEN APIENTRY NtGdiSelectPen(IN HDC hDC, IN HPEN hPen)

作用SelectObject(hdc, hpen) 的画笔分支。与 NtGdiSelectBrush 完全同构:保存旧 hpen → 写入新 hpenDC_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 中最复杂的选择函数。

实现流程

  1. 参数检查:hdc == NULL || hbmp == NULL 返回 NULL;DC_LockDc(hdc)
  2. 类型检查pdc->dctype != DCTYPE_MEMORY 直接失败(位图只能选入内存 DC)。
  3. 保存旧位图:psurfOld = pdc->dclevel.pSurfacehbmpOld = psurfOld ? psurfOld->BaseObject.hHmgr : StockObjects[DEFAULT_BITMAP]
  4. 幂等短路hbmp == hbmpOld 直接返回(重复选择同一张位图无副作用)。
  5. 分派:
    • hbmp == StockObjects[DEFAULT_BITMAP](1×1 默认位图):psurfNew = NULLdclevel.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 脏位。
  6. 提交:pdc->dclevel.pSurface = psurfNew
  7. 清理旧位图:若 psurfOld 非空,psurfOld->hdc = NULL(解除归属),SURFACE_ShareUnlockSurface(psurfOld)
  8. 置脏ulDirty_ |= DIRTY_FILL | DIRTY_LINE(表面格式变了,画刷颜色翻译可能失效);pdc->fs |= DC_DIRTY_RAO(RAO 区域需要重算)。
  9. 更新系统区域REGION_SetRectRgn(pdc->prgnVis, 0, 0, sizl.cx, sizl.cy)------可见区域重置为整个位图大小。
  10. 解锁 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)与现有裁剪区域合并。

实现流程

  1. DC_LockDc(hDC)(失败 ERROR_INVALID_PARAMETER);PATH_LockPath(pdc->dclevel.hPath) 锁定当前路径。
  2. 路径必须闭合pPath->state != PATH_Closed 时报 ERROR_CAN_NOT_COMPLETE 并退出。PATH_Closedpath.h(file:///d:/reactos/win32ss/gdi/ntgdi/path.h) 第 20 行的路径状态枚举成员。
  3. IntSysCreateRectpRgn(0, 0, 0, 0) 创建空目标区域(失败报 ERROR_NOT_ENOUGH_MEMORY)。
  4. PATH_FlattenPath(pPath) 把路径中的贝塞尔曲线展平成折线,得到 pNewPath
  5. PATH_PathToRegion(pNewPath, pdcattr->jFillMode, RgnPath)------按 DC 的填充模式(ALTERNATE/WINDING)把展平路径栅格化为区域。
  6. 临时路径用完即删:PATH_UnlockPath + PATH_Delete(pNewPath->BaseObject.hHmgr)
  7. 若转换成功:success = IntGdiExtSelectClipRgn(pdc, RgnPath, Mode) != ERROR(与现有裁剪区域按 Mode 合并)。
  8. 清理:REGION_Delete(RgnPath)PATH_UnlockPath(pPath)销毁当前路径 PATH_Delete(pdc->dclevel.hPath)flPath &= ~DCPATH_ACTIVEhPath = 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"表明这是旧字体引擎的遗留路径。

实现流程

  1. TextIntRealizeFont(hlfntNew, NULL) 预实现字体------若失败(字体无效),返回 NULL。TextIntRealizeFont 是字体引擎(text.c)的"确保字体已实现"入口。
  2. hlfntOld = pdc->dclevel.plfnt->BaseObject.hHmgr 取当前字体句柄。
  3. hlfntNew != hlfntOld
    • LFONT_ShareLockFont(hlfntNew) 锁定新字体实体;成功则 LFONT_ShareUnlockFont 解锁旧实体,dclevel.plfnt = plfntNewpdcattr->hlfntNew = hlfntNew
    • 置脏:ulDirty_ |= DIRTY_CHARSET(字符集/字体需要重建),ulDirty_ &= ~SLOW_WIDTHS(清除慢速宽度标记------新字体先用快速宽度路径);
    • 失败则回滚 pdcattr->hlfntNew = hlfntOld 并返回 NULL。
  4. 若字体相同,直接返回旧句柄(不重新锁定)。

注意事项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_LockDcDC_hSelectFont(pdc, hfont)DC_UnlockDc → 返回旧字体。

3.15 NtGdiGetDCObject(dcobjs.c 第 627-690 行)

签名HANDLE APIENTRY NtGdiGetDCObject(HDC hDC, INT ObjectType)

作用GetCurrentObject(hdc, type) 的系统调用------返回 DC 当前选中的指定类型对象句柄。

实现流程

  1. 注释注明(源自 Wine):GetCurrentObject 对空对象不设置 SetLastError。

  2. if (!hDC) return NULL;DC_LockDc(hDC)

  3. 读前刷新 (保证返回的是最新选中对象):

    c 复制代码
    if (pdcattr->ulDirty_ & (DIRTY_FILL | DC_BRUSH_DIRTY)) DC_vUpdateFillBrush(pdc);
    if (pdcattr->ulDirty_ & (DIRTY_LINE | DC_PEN_DIRTY))   DC_vUpdateLineBrush(pdc);
  4. 按类型分发:

    • GDI_OBJECT_TYPE_EXTPEN / GDI_OBJECT_TYPE_PENpdcattr->hpen
    • GDI_OBJECT_TYPE_BRUSHpdcattr->hbrush
    • GDI_OBJECT_TYPE_PALETTEpdc->dclevel.hpal
    • GDI_OBJECT_TYPE_FONTpdcattr->hlfntNew
    • GDI_OBJECT_TYPE_BITMAPpSurface ? pSurface->BaseObject.hHmgr : StockObjects[DEFAULT_BITMAP]
    • GDI_OBJECT_TYPE_COLORSPACEDPRINT1("FIXME ... not supported yet!"),返回 NULL;
    • default → NULL + EngSetLastError(ERROR_INVALID_PARAMETER)
  5. 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

实现流程

  1. DC_LockDc(hdc),失败 ERROR_INVALID_HANDLE 返回 -1。
  2. 按 iCode 选源区域:
    • CLIPRGNdclevel.prgnClip(API 裁剪区域);
    • METARGNdclevel.prgnMeta(元区域);
    • APIRGN → 若 pdc->fs & DC_DIRTY_RAOCLIPPING_UpdateGCRemain(pdc)------即 CLIPPING_UpdateGCRegion(pdc) 重算区域;然后按 prgnAPIprgnClipprgnMeta 的优先级取源;
    • SYSRGNpdc->prgnVis(系统/可见区域);
    • default 不做处理(prgnSrc 保持 NULL)。
  3. 源非空时:REGION_LockRgn(hrgnDest) 锁定目标;IntGdiCombineRgn(prgnDest, prgnSrc, 0, RGN_COPY) 拷贝(失败返回 -1)。
  4. SYSRGN 特例REGION_bOffsetRgn(prgnDest, pdc->ptlDCOrig.x, pdc->ptlDCOrig.y)------系统区域是设备坐标,需按 DC 原点偏移到客户坐标(附 FIXME 注释:因为区域已被修改,严格说应先拷贝再偏移)。
  5. 返回:有源区域且拷贝成功 → 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_RAONtGdiSelectBitmapIntGdiCleanDC 等置位。

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) 先重建再读

触发重建的三个典型时机:

  1. 选择/设置后立即重建NtGdiSelectBrush/NtGdiSelectPen/IntGdiSetTextColor 等入口在修改属性后同步调用 对应的 Update 函数。这样 GetCurrentObjectGetObject 及接下来的第一次绘制都看到一致状态。
  2. 绘制前按需重建 :填充/线条/文本绘制入口在取 pdc->eboFill 等对象前调用 DC_vUpdateFillBrush 等。由于多数情况下脏位已被清除(上一次绘制已重建过),函数退化为"比较句柄 + 清脏",开销极小。
  3. 查询前防御性重建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 行):
    • 记录 pbrushflattrs = pbrush->flAttrspsurfTrg = pdc->dclevel.pSurface(内存 DC 未选位图时为 psurfDefaultBitmap);
    • 引用 psurfTrg->ppal 作为 ppalSurf,引用 ppalDC(DC 调色板,缺省 gppalDefault);
    • 按属性分派:BR_IS_NULL → iSolidColor=0;BR_IS_SOLIDEBRUSHOBJ_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 翻译为目标格式的索引。
  • EBRUSHOBJ_bRealizeBrush(第 331-435 行):模式画刷的真正实现------选择 DrvRealizeBrush(调用驱动)或 EngRealizeBrush(第 230-286 行:EngCreateBitmap + EngCopyBits 把图案按目标格式复制到新位图),处理阴影画刷(ppdev->ahsurf[iHatch])与 DIB_PAL_COLORS 调色板修复(FixupDIBBrushPalette)。

4.4 一个典型场景的时序

以"SetTextColorTextOut"为例:

复制代码
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 旧位图句柄
字体 NtGdiSelectFontDC_hSelectFont TextIntRealizeFont 预实现 dclevel.plfnt/hlfntNew + DIRTY_CHARSET 旧字体句柄
路径→裁剪 NtGdiSelectClipPath 路径必须闭合(PATH_Closed PATH_FlattenPath + PATH_PathToRegion + IntGdiExtSelectClipRgn;销毁路径 成功/失败

三类通用模式:

  1. 句柄-实体同步模式 (画刷/画笔/字体):pdcattr 保存句柄,dclevel 保存被引用计数的实体;切换时先锁定新实体(*_ShareLock*),成功才解锁旧实体并提交,失败则回滚句柄。原子性由 DC 锁保证。
  2. 独占归属模式 (位图):psurf->hdc 记录归属 DC,用 InterlockedCompareExchangePointer 原子抢占,保证位图同一时刻只属于一个 DC。
  3. 消耗模式(路径):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_ATTRGdiPoolAllocate),把内核态 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_ShareLockBrushPEN_ShareLockPenLFONT_ShareLockFontSURFACE_ShareLockSurface------所有对象引用计数操作的来源
dclife.c DC 的创建/销毁(DC_vInitDcDC_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

  1. NtGdiEnumObjects 是 UNIMPLEMENTED 桩------EnumObjects API 在 ReactOS 上尚不可用。
  2. NtGdiGetDCObject 的 OBJ_COLORSPACE 分支打印 FIXME 并返回 NULL(参考代码被注释:dc->dclevel.pColorSpace.BaseObject.hHmgr)。
  3. GdiSelectPaletteForceBackground 参数未使用(FIXME: Mark the palette as a fore\\background pal);兼容性检查处注明不应在 PDEV 未锁定时解引用 pSurface。
  4. NtGdiGetRandomRgn 的 SYSRGN 偏移逻辑有 FIXME:应先拷贝源区域再偏移,避免就地修改后的语义偏差。
  5. 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.cwin32ss/gdi/ntgdi/dc.hwin32ss/gdi/ntgdi/dcattr.cwin32ss/gdi/ntgdi/dcstate.cwin32ss/gdi/ntgdi/dcutil.cwin32ss/gdi/eng/engbrush.cwin32ss/include/ntgdihdl.h 分析(2026 年 8 月)


相关推荐
caimouse1 小时前
ReactOS 图形系统分析(38):区域子系统 — region.c
c语言
嵌入式阿蔡1 小时前
STM32时钟树详解:配错一个全乱套
c语言·stm32·单片机·嵌入式硬件
MarkHD1 小时前
Stable Diffusion入门第19-20天:保存与分享你的工作流——第一阶段收官,从“能用”到“可复用”
java·开发语言·stable diffusion
云水初2 小时前
【agent篇】RAG 知识库构建避坑指南
开发语言·python·学习·agent·rag
2019一路前行2 小时前
Python 函数、循环语句
开发语言·python
caimouse2 小时前
ReactOS 图形系统分析(37):路径子系统 — path.c
c语言·reactos
CarIise2 小时前
C语言基础课程笔记:硬件原理、数据类型、结构体与格式化输入输出详解
c语言·开发语言·笔记
leoZ2312 小时前
Vue3 还原一个企业级后台-14-项目总结
开发语言·人工智能·后端·opencv·计算机视觉·数据挖掘·rust
GeekZHR3 小时前
C语言指针2:数组名、二级指针、指针数组,一次把“指针和数组“讲透
c语言·数据结构·算法·指针
zlinear数据采集卡3 小时前
数据采集卡从入门到精通(7):流水线型ADC——级级接力,高速与高精的平衡术
开发语言·arm开发·嵌入式硬件·fpga开发·c#