ReactOS 图形系统分析(46):DC 工具 — dcutil.c

ReactOS 图形系统分析(46):DC 工具 --- dcutil.c

1. 概述

1.1 文件定位

dcutil.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcutil.c)(约 963 行)位于 win32ss/gdi/ntgdi/ 目录,是 ntgdi 内核模式 GDI 中 DC(Device Context)工具函数集 。它与 dcobjs.c(DC 的创建/销毁/对象选择)分工互补:

文件 职责
dcobjs.c DC 生命周期(创建、删除、清理)、对象选择(SelectBrush/SelectPen/SelectBitmap/SelectFont/SelectClipPath)、画刷更新(DC_vUpdate*Brush)
dcutil.c DC 属性读写 (BkColor/BkMode/TextColor/TextAlign/MapMode/StretchBltMode/GraphicsMode/BrushOrg)、坐标换算 (LPtoDP/DPtoLP)、边界矩形 (Get/SetBoundsRect)、特殊 DC 查询(GetDCDword/GetAndSetDCDword)、杂项(CancelDC/HookFlags/SetDcUndeletable/SetDefaultRegion/TranslateCOLORREF)
coord.c 映射模式/变换(SetMapMode/SetViewportOrg/SetWindowExt 等,XFORM 矩阵构建)
bitblt.c 位块传输/填充(IntPatBlt 等)、SetPixel/GetPixel 的 NtGdi 入口

dcutil.c 中的函数大致分为四类:

  1. 6 个 NtGdi* 系统调用入口NtGdiCancelDCNtGdiGetDCDwordNtGdiGetAndSetDCDwordNtGdiGetBoundsRectNtGdiSetBoundsRect------这些函数从用户态(gdi32.dll)经系统调用进入,带有 SEH 异常保护,直接访问进程地址空间参数。
  2. Gre* 内部 API (约 9 个):GreDPtoLPGreLPtoDPGreGetBkModeGreGetBkColorGreGetMapModeGreGetTextColorGreSetBrushOrgGreSetStretchBltModeGreGetGraphicsMode------Gre 前缀表明它们同时可被 win32k 内部其它模块调用(win32ss 约定:Gre 前缀 = 可在内核内跨模块调用的 GDI 服务),也可被 gdi32.dll 的用户模式模拟层调用。
  3. IntGdi*/IntSet*/Int* 内部工具IntGdiSetBkColorIntGdiSetBkModeIntGdiSetTextAlignIntGdiSetTextColorIntSetDCBrushColorIntSetDCPenColorIntSetDefaultRegionIntUpdateBoundsRectIntGdiSetHookFlagsDCU_SetDcUndeletableTranslateCOLORREF------这些只在 win32k 内核内部使用,被其它 ntgdi/ntuser 模块直接调用。
  4. 辅助/禁用代码IntIsPrimarySurface(被 #if 0 禁用)。

1.2 架构总览(ASCII 图)

复制代码
用户态 (gdi32.dll)
   SetBkColor / SetBkMode / SetTextColor / SetTextAlign / SetBrushOrg
   SetStretchBltMode / SetGraphicsMode / GetBkColor / GetMapMode ...
   GetBoundsRect / SetBoundsRect / GetDCBrushColor / GetDCPenColor
        │  (系统调用: NtGdiSetBkColor, NtGdiSetTextColor, ...)
        ▼
win32k.sys ── ntgdi ── dcutil.c
   ├─ 属性读写组
   │    ├─ IntGdiSetBkColor      ──► 脏标记 DIRTY_BACKGROUND|DIRTY_LINE|DIRTY_FILL
   │    ├─ IntGdiSetBkMode       ──► jBkMode/lBkMode(MIX 背景 ROP 输入,见《分析_17》)
   │    ├─ IntGdiSetTextColor    ──► DIRTY_TEXT|DIRTY_LINE|DIRTY_FILL
   │    │                              └─► DC_vUpdateTextBrush/LineBrush/FillBrush(dcobjs.c)
   │    ├─ IntGdiSetTextAlign    ──► flTextAlign(RTL 翻转 TA_RIGHT↔TA_LEFT)
   │    ├─ IntSetDCBrushColor    ──► crBrushClr + DIRTY_FILL(DC_BRUSH 画刷颜色)
   │    ├─ IntSetDCPenColor      ──► crPenClr + DIRTY_LINE(DC_PEN 画笔颜色)
   │    ├─ GreSetBrushOrg        ──► DC_vSetBrushOrigin(ptlBrushOrigin/ptlFillOrigin)
   │    ├─ GreSetStretchBltMode  ──► lStretchBltMode/jStretchBltMode
   │    └─ Gre*Get* 系列          ──► 无锁竞争的直接字段读取
   ├─ 坐标换算组
   │    ├─ GreLPtoDP / GreDPtoLP ──► IntLPtoDP / IntDPtoLP(coord.h)
   │    │                              └─► INTERNAL_APPLY_MATRIX(FLOATOBJ 矩阵乘法)
   │    └─ NtGdiGetDCDword case GdiGetMapMode  ──► IntGdiSetMapMode(coord.c)
   ├─ 边界矩形组(BoundsRect)
   │    ├─ NtGdiGetBoundsRect   ──► erclBoundsApp / erclBounds
   │    ├─ NtGdiSetBoundsRect   ──► DC_ACCUM_APP / DC_ACCUM_WMGR
   │    └─ IntUpdateBoundsRect  ──► RECTL_bUnionRect 累计(被 bitblt/fillshap 等调用)
   ├─ 像素颜色转换
   │    └─ TranslateCOLORREF    ──► EXLATEOBJ_vInitialize(&exlo, &gpalRGB, surf->ppal, ...)
   │                                 └─► XLATEOBJ_iXlate(见《分析_19》)
   ├─ 杂项
   │    ├─ NtGdiCancelDC(UNIMPLEMENTED stub)
   │    ├─ IntGdiSetHookFlags(DCE 可见区失效通知,被 ntuser/windc.c 调用)
   │    ├─ DCU_SetDcUndeletable(DC_PERMANANT,防意外删除)
   │    └─ IntSetDefaultRegion(初始化可见区 prgnVis)
   └─ NtGdiGetDCDword / NtGdiGetAndSetDCDword(通用 DWORD 属性读写,驱动兼容用)

输出端(被本文件触发的重建):
   dcobjs.c: DC_vUpdateTextBrush / DC_vUpdateLineBrush / DC_vUpdateFillBrush
            └─► EBRUSHOBJ_vUpdateFromDC → 驱动画刷(见《分析_39》)

1.3 设计动机

DC 是 GDI 一切绘制操作的状态容器。dcutil.c 存在的根本动机有四点:

  1. 属性与绘制的解耦(惰性重建) :DC 属性(颜色、模式、对齐等)被高频修改,而画刷/画笔的实际"物化"(EBRUSHOBJ、驱动级画刷)代价高昂。dcutil.c 采用"写属性 → 只置脏标记 → 下次绘制前重建画刷 "的惰性策略,避免每次 Set* 都触发昂贵的画刷重建。脏标记机制(pdcattr->ulDirty_)是整个模块的核心设计(详见第 4 节)。
  2. 统一锁定协议 :所有函数都以 DC_LockDc(hdc) 开头、DC_UnlockDc(dc) 结尾。DC 是共享 GDI 对象(可在多线程/多进程间共享),必须经 GDI 句柄表加锁才能安全访问;所有失败路径统一 EngSetLastError(ERROR_INVALID_HANDLE) 并返回 CLR_INVALID/FALSE/GDI_ERROR
  3. 内核态与用户态的边界安全 :NtGdi* 入口对用户传入的指针一律用 _SEH2_TRY/ProbeForWrite/ProbeForRead 保护,防止越权访问内核地址;返回前通过 SetLastNtError 传播 NTSTATUS。
  4. 坐标变换的集中实现:LPtoDP/DPtoLP 是 GDI 坐标体系(逻辑坐标↔设备坐标)的唯一换算入口,内部依赖 XFORM 矩阵(FLOATOBJ 定点浮点),并支持 MM_ISOTROPIC 自动修正、RTL 布局镜像等特殊语义。

2. 核心数据结构

2.1 DC 对象(dc.h,struct _DC

DC 是 GDI 句柄表中的一种对象(GDILoObjType_LO_DC_TYPE / GDILoObjType_LO_ALTDC_TYPE)。dc.h 中定义的 _DC 结构(95~137 行)关键字段:

c 复制代码
typedef struct _DC
{
  BASEOBJECT  BaseObject;     /* GDI 对象公共头(句柄表管理) */
  DHPDEV      dhpdev;         /* 设备 PDEVOBJ.hPDev,驱动句柄 */
  DCTYPE      dctype;         /* DCTYPE_DIRECT / DCTYPE_MEMORY / DCTYPE_INFO */
  INT         fs;             /* DCFLAGS 位标志(见 2.3) */
  PPDEVOBJ    ppdev;          /* 物理设备对象(PDEV) */
  PVOID       hsem;           /* 每-DC 锁(PERESOURCE) */
  FLONG       flGraphicsCaps;
  FLONG       flGraphicsCaps2;
  _Notnull_ PDC_ATTR    pdcattr;  /* ┐ 属性指针:指向本对象内嵌的 dcattr 成员 */
  DCLEVEL     dclevel;            /* │ DCLEVEL 层(对象选择、字体、矩阵缓存) */
  DC_ATTR     dcattr;             /* ┘ DC_ATTR 内嵌实例(pdcattr 指向它) */
  HDC         hdcNext, hdcPrev;   /* DC 链表(同一进程的 DC 集合) */
  RECTL       erclClip;           /* 裁剪矩形 */
  POINTL      ptlDCOrig;          /* DC 原点(设备坐标,窗口滚动等引起) */
  RECTL       erclWindow;         /* 窗口矩形(设备坐标) */
  RECTL       erclBounds;         /* 边界矩形(WMGR 累计,设备坐标) */
  RECTL       erclBoundsApp;      /* 边界矩形(APP 累计,逻辑坐标) */
  PREGION     prgnAPI;            /* API 区域 = Meta∩Clip */
  _Notnull_ PREGION     prgnVis;  /* 可见区域(系统区域,永不为 0) */
  PREGION     prgnRao;            /* Rao 区域 = API∩Vis */
  POINTL      ptlFillOrigin;      /* 填充原点(画刷原点 + DC 原点) */
  EBRUSHOBJ   eboFill;            /* 填充画刷对象(驱动接口包装) */
  EBRUSHOBJ   eboLine;            /* 线条画笔对象 */
  EBRUSHOBJ   eboText;            /* 文本画刷对象(恒为纯色) */
  EBRUSHOBJ   eboBackground;      /* 背景画刷对象(恒为纯色) */
  ...
  ULONG       ulCopyCount;        /* 打印副本数(GdiGetSetCopyCount) */
} DC;

注意 pdcattrdcattr 的关系:DC 结构内嵌了一个 DC_ATTR 实例 dcattr,而 pdcattrDC_bAllocDcAttr(dcobjs.c)中通常指向它。pdcattr 被设计为指针是为了将来支持"局部 DC"(LDC)扩展。

2.2 DC_ATTR(ntgdihdl.h,291~351 行)

_DC_ATTR 是 DC 属性的核心容器,全部属性字段按功能分组:

c 复制代码
typedef struct _DC_ATTR
{
    PVOID pvLDC;               /* 指向 LDC(局部 DC,打印/EMF 用),一般 NULL */
    ULONG ulDirty_;            /* 脏标记(见 2.4) */
    HANDLE hbrush;             /* 已选画刷句柄 */
    HANDLE hpen;               /* 已选画笔句柄 */

    /* ---- 颜色对(两套:cr* 为"当前生效色",ul* 为"API 设置色") ---- */
    COLORREF crBackgroundClr;  /* 生效背景色(画刷重建依据) */
    ULONG    ulBackgroundClr;  /* API 背景色(GetBkColor 返回值) */
    COLORREF crForegroundClr;  /* 生效前景/文本色 */
    ULONG    ulForegroundClr;  /* API 文本色(GetTextColor 返回值) */
    COLORREF crBrushClr;       /* 生效 DC 画刷色(SetDCBrushColor) */
    ULONG    ulBrushClr;       /* API DC 画刷色 */
    COLORREF crPenClr;         /* 生效 DC 画笔色(SetDCPenColor) */
    ULONG    ulPenClr;         /* API DC 画笔色 */

    DWORD iCS_CP;              /* 颜色空间代码页 */
    INT   iGraphicsMode;       /* GM_COMPATIBLE / GM_ADVANCED */

    /* ---- 字节型"简化字段"(j 前缀):绘制热路径用,与 LONG 型并存 ---- */
    BYTE jROP2;                /* 光栅操作(R2_*,见《分析_17》) */
    BYTE jBkMode;              /* 背景模式 OPAQUE/TRANSPARENT */
    BYTE jFillMode;            /* 填充模式 ALTERNATE/WINDING */
    BYTE jStretchBltMode;      /* 拉伸模式(规范化后) */

    POINTL ptlCurrent;         /* 当前画笔位置(MoveToEx/LineTo) */
    POINTL ptfxCurrent;        /* 当前点的定点表示(28.4 定点) */

    /* ---- LONG 型"完整字段":API 语义层用 ---- */
    LONG lBkMode;
    LONG lFillMode;
    LONG lStretchBltMode;
    FLONG flFontMapper;        /* 字体映射标志(GdiGetSetMapperFlagsInternal) */
    LONG lIcmMode;
    HANDLE hcmXform;  HCOLORSPACE hColorSpace;  FLONG flIcmFlags;
    INT IcmBrushColor;  INT IcmPenColor;  PVOID pvLIcm;   /* ICM 相关 */

    FLONG flTextAlign;         /* 生效文本对齐(TA_MASK 内,含 RTL 修正) */
    LONG  lTextAlign;          /* API 文本对齐 */
    LONG  lTextExtra;          /* 字符间距(SetTextCharacterExtra) */
    LONG  lRelAbs;             /* 相对/绝对移动标志 */
    LONG  lBreakExtra;         /* 断字间距 */
    LONG  cBreak;              /* 断字字符数 */
    HANDLE hlfntNew;           /* 当前选中字体句柄 */

    /* ---- 坐标变换矩阵 ---- */
    MATRIX mxWorldToDevice;    /* 世界→设备(最终合成矩阵) */
    MATRIX mxDeviceToWorld;    /* 设备→世界(mxWorldToDevice 的逆) */
    MATRIX mxWorldToPage;      /* 世界→页面(SetWorldTransform 结果) */
    FLOATOBJ efM11PtoD, efM22PtoD, efDxPtoD, efDyPtoD; /* 页→设备缩放/平移缓存 */

    INT   iMapMode;            /* MM_TEXT/MM_ISOTROPIC/...(见 coord.c) */
    DWORD dwLayout;            /* LAYOUT_LTR / LAYOUT_RTL / LAYOUT_BITMAPORIENTATIONPRESERVED */
    LONG  lWindowOrgx;         /* 窗口原点(32 位兼容字段) */
    POINTL ptlWindowOrg;       /* 窗口原点(逻辑) */
    SIZEL  szlWindowExt;       /* 窗口范围 */
    POINTL ptlViewportOrg;     /* 视口原点(设备) */
    SIZEL  szlViewportExt;     /* 视口范围 */
    FLONG  flXform;            /* Xform 状态位(见 2.5) */
    SIZEL  szlVirtualDevicePixel; /* 虚拟设备像素尺寸(用于度量映射) */
    SIZEL  szlVirtualDeviceMm;    /* 虚拟设备毫米尺寸 */
    SIZEL  szlVirtualDeviceSize;  /* 虚拟设备总尺寸 */
    POINTL ptlBrushOrigin;     /* 画刷原点(相对 DC 原点) */
    RGN_ATTR VisRectRegion;    /* 可见区矩形属性缓存 */
} DC_ATTR, *PDC_ATTR;

关键设计点:cr*/ul* 双份颜色ul* 是 API 看到的颜色(GetBkColor/GetTextColor 返回它),cr* 是"已生效"的颜色。IntGdiSetBkColor 只有在新颜色与 crBackgroundClr 不同时才置脏标记并更新 cr*;相同则跳过(避免无谓重建)。这与"惰性重建"动机一致。

关键设计点:BYTE 简化字段(j 前缀) 。绘制热路径(如 line.c、fillshap.c 构造 MIX)只读取 jBkMode/jROP2/jFillMode/jStretchBltMode 这类单字节字段以提高缓存友好度;API 语义层(如 IntGdiSetBkMode 同时写 jBkModelBkMode)保持 LONG 型完整字段。GreSetStretchBltMode 的写法最典型:lStretchBltMode 无条件保存 API 值(不校验),jStretchBltMode 则保存规范化后的值(越界强制 WHITEONBLACK)。

2.3 DC 状态标志(dc.h,_DCFLAGS,17~37 行)

dc->fs 位标志(本文件大量用到):

标志 用途(在本文件中的体现)
DC_DISPLAY 0x0001 显示 DC
DC_DIRECT 0x0002 直接 DC
DC_CANCELED 0x0004 已取消(NtGdiCancelDC 本应设置,但当前是 stub)
DC_PERMANANT 0x0008 不可删除(DCU_SetDcUndeletable 设置)
DC_DIRTY_RAO 0x0010 Rao 区域失效(IntGdiSetHookFlags 设置;NtGdiGetBoundsRect/NtGdiGetRandomRgn 据此调用 CLIPPING_UpdateGCRegion
DC_ACCUM_WMGR 0x0020 窗口管理器累计边界(NtGdiSetBoundsRect 的 DCB_WINDOWMGR 路径)
DC_ACCUM_APP 0x0040 应用程序累计边界(NtGdiSetBoundsRect 默认路径)
DC_RESET 0x0080 重置累计边界
DC_EPSPRINTINGESCAPE 0x0200 EPS 打印转义(NtGdiGetAndSetDCDword 的 GdiGetSetEPSPrintingEscape 读写并清除)
DC_TEMPINFODC 0x0400 临时信息 DC
DC_FULLSCREEN 0x0800 全屏
DC_PREPARED 0x8000 调试用(仅 DBG 构建,ASSERT_DC_PREPARED

2.4 脏标记(ntgdihdl.h,122~145 行,ulDirty_

pdcattr->ulDirty_ 是惰性重建机制的中枢,本文件相关位:

名称 本文件中的置位/清除者
0x00000001 DIRTY_FILL IntGdiSetBkColor(连带)、IntGdiSetTextColorIntSetDCBrushColor;清除于 DC_vUpdateFillBrush
0x00000002 DIRTY_LINE IntGdiSetBkColorIntGdiSetTextColorIntSetDCPenColor;清除于 DC_vUpdateLineBrush
0x00000004 DIRTY_TEXT IntGdiSetTextColor;清除于 DC_vUpdateTextBrush
0x00000008 DIRTY_BACKGROUND IntGdiSetBkColor;清除于 DC_vUpdateBackgroundBrush
0x00000010 DIRTY_CHARSET DC_hSelectFont(dcobjs.c)置位
0x00000100 DIRTY_PTLCURRENT 当前点位置变化(coord.c)
0x00001000 DC_BRUSH_DIRTY DC_BRUSH 画刷颜色变化
0x00002000 DC_PEN_DIRTY DC_PEN 画笔颜色变化
0x00004000 DC_DIBSECTION 已选 DIB 段位图(dcobjs.c NtGdiSelectBitmap)
0x00200000 DC_MODE_DIRTY 映射模式变化

清除与重建的对称性 :置位在 dcutil.c,清除在 dcobjs.c 的四个 DC_vUpdate* 函数中。任何一个 EBRUSHOBJ 只要对应脏位被置位,下次绘制前(IntEng* 系列 / DC_* 准备函数)就会调用对应 DC_vUpdate* 完成重建并清位。

2.5 Xform 状态标志(ntgdihdl.h,173~188 行,flXform

坐标变换的惰性更新依赖这些位(详见第 4.2 节):

标志 含义
METAFILE_TO_WORLD_IDENTITY 0x1 元文件→世界为恒等
WORLD_TO_PAGE_IDENTITY 0x2 世界→页面恒等
DEVICE_TO_PAGE_INVALID 0x8 设备→页面矩阵失效
DEVICE_TO_WORLD_INVALID 0x10 设备→世界矩阵失效(DC_pmxDeviceToWorld 检查)
WORLD_TRANSFORM_SET 0x20 已设置世界变换
POSITIVE_Y_IS_UP 0x40 Y 轴向上(MM_* 映射模式)
INVALIDATE_ATTRIBUTES 0x80 属性失效
PTOD_EFM11_NEGATIVE / PTOD_EFM22_NEGATIVE 0x100 / 0x200 页→设备缩放为负
ISO_OR_ANISO_MAP_MODE 0x400 各向同性/异性映射
PAGE_TO_DEVICE_IDENTITY 0x800 页→设备恒等
PAGE_TO_DEVICE_SCALE_IDENTITY 0x1000 页→设备仅缩放恒等
PAGE_XLATE_CHANGED 0x2000 页平移(原点)变化
PAGE_EXTENTS_CHANGED 0x4000 页范围变化(DC_pszlViewportExt 检查,MM_ISOTROPIC 时触发修正)
WORLD_XFORM_CHANGED 0x8000 世界变换变化(DC_pmxWorldToDevice 检查)

3. 函数逐一展开

3.1 坐标换算组:GreLPtoDP / GreDPtoLP

GreLPtoDP(20~32 行)
c 复制代码
BOOL FASTCALL GreLPtoDP(HDC hdc, LPPOINT lpPoints, INT nCount)
{
   PDC dc;
   if (!(dc = DC_LockDc(hdc)))          /* ① 锁定 DC(失败置 ERROR_INVALID_HANDLE) */
   {
      EngSetLastError(ERROR_INVALID_HANDLE);
      return FALSE;
   }
   IntLPtoDP(dc, lpPoints, nCount);     /* ② 核心换算(coord.h 内联函数) */
   DC_UnlockDc(dc);                     /* ③ 解锁 */
   return TRUE;
}
  • 签名BOOL GreLPtoDP(HDC, LPPOINT, INT)lpPoints 是逻辑坐标数组(原地修改),nCount 为点数。
  • 作用 :将一组逻辑坐标 点变换为设备坐标 。设备坐标 = 逻辑坐标经过"世界→页面→设备"三级矩阵变换(mxWorldToDevice)后的结果。注意:如果当前 DC 是 GM_COMPATIBLE 且没有设置世界变换,则等价于"窗口/视口原点平移 + 范围缩放"。
  • 实现流程 :① DC_LockDc(GDI 句柄表锁 + 类型/字体断言)→ ② IntLPtoDP(见 4.2 节,内部先 DC_vUpdateWorldToDevice 保证矩阵最新,再 INTERNAL_APPLY_MATRIX 用 FLOATOBJ 逐点计算)→ ③ 解锁。
  • 使用方式 :gdi32.dll 的 LPtoDPobjects/dc.c)最终经系统调用到达此函数;win32k 内部 NtGdiGetBoundsRect(本文件 746 行)也用它把边界矩形从设备坐标转回逻辑坐标。NtGdiSetBoundsRect(855 行)则反向用 IntLPtoDP
  • 注意事项 :失败只返回 FALSE 而不修改点数组;点数 nCount 为负数时调用方需自行保证(实际循环用 while (count--),负数会导致死循环------调用方均传正数)。IntLPtoDP 返回 BOOLEAN,但 GreLPtoDP 忽略其失败结果(FLOATOBJ 溢出时点坐标可能不可靠)。
GreDPtoLP(6~18 行)
c 复制代码
BOOL FASTCALL GreDPtoLP(HDC hdc, LPPOINT lpPoints, INT nCount)
{
   PDC dc;
   if (!(dc = DC_LockDc(hdc))) { EngSetLastError(ERROR_INVALID_HANDLE); return FALSE; }
   IntDPtoLP(dc, lpPoints, nCount);
   DC_UnlockDc(dc);
   return TRUE;
}
  • 作用GreLPtoDP 的逆变换------设备坐标 → 逻辑坐标。内部 IntDPtoLPDC_vUpdateDeviceToWorld(若 DEVICE_TO_WORLD_INVALID,对 mxWorldToDevice 求逆得到 mxDeviceToWorld),再 INTERNAL_APPLY_MATRIX(&mxDeviceToWorld, ...)
  • 使用方式 :gdi32.dll 的 DPtoLP;本文件 NtGdiGetBoundsRect(746 行)把 APP 边界矩形转回逻辑坐标后返回给用户。
  • 注意 :若矩阵不可逆(如缩放为 0),XFORMOBJ_iInverse 会失败,此时 mxDeviceToWorld 保持旧值,结果不可靠------这是已知的简化。

3.2 属性查询组:GreGet*

四个 GreGet 函数结构完全一致:锁定 → 读字段 → 解锁 → 返回。它们都返回 CLR_INVALID(0xFFFFFFFF)作为错误码(虽然 BkMode/MapMode 语义上应返回 GDI_ERROR,ReactOS 统一用 CLR_INVALID,与 Wine 兼容)。

GreGetBkMode(34~47 行)
  • 读取 dc->pdcattr->lBkMode(LONG 完整字段,而非 jBkMode),返回 OPAQUE(1)/TRANSPARENT(2)。
  • 对应 gdi32.dll GetBkMode
GreGetBkColor(49~62 行)
  • 读取 dc->pdcattr->ulBackgroundClr(API 背景色,注意不是 crBackgroundClr)。
  • 返回 COLORREF;对应 gdi32.dll GetBkColor。之所以返回 ulBackgroundClr 而非 crBackgroundClr,是因为 cr* 是内部生效色(可能经调色板换算),而 API 语义要求返回用户上次设置的颜色。
GreGetMapMode(64~77 行)
  • 读取 dc->pdcattr->iMapMode,返回 MM_TEXT(1)~MM_TWIPS(8) 等。
  • 对应 gdi32.dll GetMapMode
GreGetTextColor(79~92 行)
  • 读取 dc->pdcattr->ulForegroundClr,返回 COLORREF。
  • 对应 gdi32.dll GetTextColor

3.3 属性设置组:IntGdiSet* / IntSet*

IntGdiSetBkColor(94~121 行)
c 复制代码
COLORREF FASTCALL IntGdiSetBkColor(HDC hDC, COLORREF color)
{
    PDC dc; PDC_ATTR pdcattr; HBRUSH hBrush;
    if (!(dc = DC_LockDc(hDC))) { EngSetLastError(ERROR_INVALID_HANDLE); return CLR_INVALID; }
    pdcattr = dc->pdcattr;

    oldColor = pdcattr->ulBackgroundClr;
    pdcattr->ulBackgroundClr = color;

    if (pdcattr->crBackgroundClr != color)
    {
        pdcattr->ulDirty_ |= (DIRTY_BACKGROUND|DIRTY_LINE|DIRTY_FILL); // Clear Flag if set.
        pdcattr->crBackgroundClr = color;
    }
    hBrush = pdcattr->hbrush;
    DC_UnlockDc(dc);
    NtGdiSelectBrush(hDC, hBrush);   /* 解锁后重选画刷,触发重建 */
    return oldColor;
}
  • 签名COLORREF IntGdiSetBkColor(HDC, COLORREF),返回背景色。
  • 作用:设置背景色。背景色用于:① 文本/图形中"背景"像素的填充色(配合 BkMode=OPAQUE);② 虚线样式中的空隙色。
  • 实现流程 :① 保存旧 API 值 ulBackgroundClr 并写入新值 → ② 比较 cr 与 ul :若生效色不同,置三个脏位(背景/线条/填充画刷全部失效)并同步 crBackgroundClr → ③ 取出当前画刷句柄,解锁后调用 NtGdiSelectBrush(hDC, hBrush) 重选同一个画刷 ------这是关键技巧:SelectBrush 内部(dcobjs.c 255~280 行)会调用 DC_vUpdateFillBrush,从而立即消费 DIRTY_FILL 位重建填充画刷(使其携带新背景色)→ ④ 返回旧值。
  • 使用方式 :gdi32.dll SetBkColor 入口;win32k 内部其它模块也可直接调用。
  • 注意事项ulDirty_ 注释 "Clear Flag if set" 表明这三位置位后由 DC_vUpdateBackgroundBrush/LineBrush/FillBrush 消费。注意此处未调用 DC_vUpdateBackgroundBrush 立即重建 ------背景画刷的重建被推迟到下次真正使用背景时(NtGdiGetBoundsRect 之外的绘制路径),这与 IntGdiSetTextColor 的"立即重建"策略不同(见下)。
IntGdiSetBkMode(123~141 行)
c 复制代码
INT FASTCALL IntGdiSetBkMode(HDC hDC, INT Mode)
{
    ...
    oldMode = pdcattr->lBkMode;
    pdcattr->jBkMode = Mode;    /* 热路径简化字段 */
    pdcattr->lBkMode = Mode;    /* API 完整字段 */
    ...
    return oldMode;
}
  • 签名INT IntGdiSetBkMode(HDC, INT),返回旧模式。
  • 作用:设置背景模式 OPAQUE(1)/TRANSPARENT(2)。BkMode 决定绘制时背景像素如何处理:OPAQUE 用背景色填充背景,TRANSPARENT 保持背景不变。
  • 实现流程 :同步写 jBkModelBkMode 两个字段,不设置任何脏标记 ------因为 BkMode 不改变画刷本身,只改变绘制时 MIX(ROP)的背景部分构造(见《分析_17》),MIX 是每次绘制时按 jBkMode 现算的。
  • 注意事项 :返回值类型声明为 COLORREF oldMode(实为 INT),错误时返回 CLR_INVALID,与 Windows 的 GDI_ERROR(-1) 兼容。
IntGdiSetTextAlign(143~168 行)
c 复制代码
UINT FASTCALL IntGdiSetTextAlign(HDC hDC, UINT Mode)
{
    ...
    prevAlign = pdcattr->lTextAlign;
    pdcattr->lTextAlign = Mode;
    if (pdcattr->dwLayout & LAYOUT_RTL)
    {
        if ((Mode & TA_CENTER) != TA_CENTER) Mode ^= TA_RIGHT;   /* RTL 镜像 */
    }
    pdcattr->flTextAlign = Mode & TA_MASK;
    ...
    return prevAlign;
}
  • 签名UINT IntGdiSetTextAlign(HDC, UINT),返回旧对齐。
  • 作用 :设置文本对齐(TA_LEFT/TA_RIGHT/TA_CENTER × TA_TOP/TA_BOTTOM/TA_BASELINE,外加 TA_UPDATECP 等)。flTextAlign 是绘制时(IntExtTextOutW)计算文本起点的依据。
  • 实现流程 :① 保存旧 lTextAlign → ② 写 lTextAlign = Mode → ③ RTL 修正 :若 DC 布局为 LAYOUT_RTL 且非 TA_CENTER,则将 Mode 的 TA_LEFT↔TA_RIGHT 翻转(异或 TA_RIGHT:TA_LEFT(0)TA_RIGHT=TA_RIGHT,TA_RIGHT(2)TA_RIGHT=0)→ ④ flTextAlign = Mode & TA_MASK(TA_MASK 过滤掉 TA_UPDATECP/TA_NOUPDATECP 等非对齐位)→ ⑤ 返回旧值。
  • 注意事项lTextAlign 保存用户原始值(含 TA_UPDATECP),flTextAlign 保存 RTL 修正后的对齐位(不含更新标志)。此处不更新 ptlCurrent 相关脏位------TA_UPDATECP 的语义由文本输出函数自行处理。
IntGdiSetTextColor(170~203 行)
c 复制代码
COLORREF FASTCALL IntGdiSetTextColor(HDC hDC, COLORREF color)
{
    ...
    crOldColor = (COLORREF) pdcattr->ulForegroundClr;
    pdcattr->ulForegroundClr = (ULONG)color;

    if (pdcattr->crForegroundClr != color)
    {
        pdcattr->ulDirty_ |= (DIRTY_TEXT|DIRTY_LINE|DIRTY_FILL);
        pdcattr->crForegroundClr = color;
    }

    DC_vUpdateTextBrush(pdc);   /* 立即重建文本画刷 */
    DC_vUpdateLineBrush(pdc);   /* 立即重建线条画刷 */
    DC_vUpdateFillBrush(pdc);   /* 立即重建填充画刷 */
    ...
    return crOldColor;
}
  • 签名COLORREF IntGdiSetTextColor(HDC, COLORREF),返回旧文本色。
  • 作用:设置文本前景色。文本色同时影响:文本绘制(eboText)、直线绘制(SetBkMode 之前用文本色做线条色,DC_PEN 未选时)、填充(DC_BRUSH 未选时)。
  • 实现流程 :① 保存旧值并写新 ulForegroundClr → ② 若生效色 crForegroundClr 变化,置 DIRTY_TEXT|DIRTY_LINE|DIRTY_FILL 并同步 → ③ 立即 调用三个 DC_vUpdate* 重建画刷(与 IntGdiSetBkColor 的延迟策略不同------文本色使用频率极高,立即重建可避免每次文本输出前都检查脏位)→ ④ 返回旧值。
  • 关键点 :三个 DC_vUpdate*(dcobjs.c)内部:置脏才重建 EBRUSHOBJ(EBRUSHOBJ_vUpdateFromDC),随后无条件 EBRUSHOBJ_vSetSolidRGBColor(&eboText, crForegroundClr) 设置纯色,最后清除脏位。由于 DC_PEN/DC_BRUSH 的处理(dcobjs.c 5054、96100 行)只在 hbrush == StockObjects[DC_BRUSH]/hpen == StockObjects[DC_PEN] 时才把 crBrushClr/crPenClr 写入画刷,因此文本色对线条/填充画刷的影响是条件性的。
IntSetDCBrushColor(205~228 行)
c 复制代码
COLORREF FASTCALL IntSetDCBrushColor(HDC hdc, COLORREF crColor)
{
   ...
   OldColor = (COLORREF) dc->pdcattr->ulBrushClr;
   dc->pdcattr->ulBrushClr = (ULONG) crColor;
   if ( dc->pdcattr->crBrushClr != crColor )
   {
      dc->pdcattr->ulDirty_ |= DIRTY_FILL;
      dc->pdcattr->crBrushClr = crColor;
   }
   ...
   return OldColor;
}
  • 签名COLORREF IntSetDCBrushColor(HDC, COLORREF),返回旧色。
  • 作用 :设置 DC 画刷色SetDCBrushColor,Win2000+ 引入)。当 DC 中选中 StockObjects[DC_BRUSH] 时,填充使用的颜色由此值决定。返回旧颜色。
  • 实现流程 :同 BkColor 模式:ul 先行 → cr 比较 → 置 DIRTY_FILL → 返回旧值。不立即重建DC_vUpdateFillBrushhbrush == DC_BRUSH 分支会在下次绘制时把 crBrushClr 写入 eboFill)。
  • 注意 :本函数不调用 NtGdiSelectBrush 重选,比 IntGdiSetBkColor 少一步------因为 DC_BRUSH 颜色变化只需置 DIRTY_FILL,NtGdiGetDCObject/绘制路径会处理。
IntSetDCPenColor(258~279 行)
  • 签名COLORREF IntSetDCPenColor(HDC, COLORREF),返回旧色。与 IntSetDCBrushColor 完全对称,置 DIRTY_LINE
  • 唯一不同 :锁定失败时返回 ERROR_INVALID_PARAMETER 而非 ERROR_INVALID_HANDLE(源码如此,可视为笔误,但不影响功能)。
  • 对应 SetDCPenColor;当 DC 中选中 StockObjects[DC_PEN] 时,线条颜色由此值决定。

3.4 GreSetBrushOrg(230~256 行)

c 复制代码
BOOL FASTCALL GreSetBrushOrg(HDC hdc, INT x, INT y, LPPOINT pptOut)
{
    PDC pdc = DC_LockDc(hdc);
    if (pdc == NULL) { EngSetLastError(ERROR_INVALID_HANDLE); return FALSE; }

    if (pptOut != NULL)
        *pptOut = pdc->pdcattr->ptlBrushOrigin;   /* 旧画刷原点 */

    pdc->pdcattr->ptlBrushOrigin.x = x;           /* 新原点(相对 DC 原点) */
    pdc->pdcattr->ptlBrushOrigin.y = y;

    DC_vSetBrushOrigin(pdc, x, y);                /* 同步 dclevel 与填充原点 */

    DC_UnlockDc(pdc);
    return TRUE;
}
  • 签名BOOL GreSetBrushOrg(HDC, INT x, INT y, LPPOINT pptOut)pptOut 可选,输出原点。

  • 作用 :设置画刷原点(SetBrushOrgEx 的底层)。画刷原点决定图案画刷(Pattern Brush)铺贴时相对 DC 的起始对齐位置。

  • 实现流程 :① 锁定 → ② 可选输出旧 ptlBrushOrigin → ③ 更新 pdcattr->ptlBrushOrigin → ④ 调用 DC_vSetBrushOrigin(dcobjs.c 140~151 行):

    c 复制代码
    pdc->dclevel.ptlBrushOrigin.x = x;  pdc->dclevel.ptlBrushOrigin.y = y;
    pdc->ptlFillOrigin.x = x + pdc->ptlDCOrig.x;   /* 填充原点 = 画刷原点 + DC 原点 */
    pdc->ptlFillOrigin.y = y + pdc->ptlDCOrig.y;

    ptlFillOrigin 是最终传给驱动/填充引擎的物理原点(考虑了窗口滚动导致的 DC 原点偏移)。

  • 使用方式 :gdi32.dll SetBrushOrgExNtGdiSetBrushOrg(dcobjs.c 162~195 行,SEH 保护输出指针)→ GreSetBrushOrg

  • 注意事项 :不改 flXform、不置脏位------画刷原点不参与 EBRUSHOBJ 重建,只在绘制时作为填充原点使用。

3.5 GreSetStretchBltMode(281~303 行)

c 复制代码
int FASTCALL GreSetStretchBltMode(HDC hDC, int iStretchMode)
{
    ...
    oSMode = pdcattr->lStretchBltMode;
    pdcattr->lStretchBltMode = iStretchMode;         /* 原样保存 */

    // Wine returns an error here. We set the default.
    if ((iStretchMode <= 0) || (iStretchMode > MAXSTRETCHBLTMODE)) iStretchMode = WHITEONBLACK;
    pdcattr->jStretchBltMode = iStretchMode;         /* 规范化后保存 */
    ...
    return oSMode;
}
  • 签名int GreSetStretchBltMode(HDC, int),返回旧模式。
  • 作用 :设置拉伸位图模式:BLACKONWHITE(1)/WHITEONBLACK(2)/COLORONCOLOR(3)/HALFTONE(4)。影响 StretchBlt/StretchDIBits 缩放时的像素取舍算法。
  • 实现流程lStretchBltMode 无条件保存 API 原始值(即使非法);jStretchBltMode 保存规范化值------非法(≤0 或 > MAXSTRETCHBLTMODE)时强制 WHITEONBLACK。源码注释明确说明 Wine 此处返回错误,ReactOS 选择"设默认值"的宽容策略。
  • 注意MAXSTRETCHBLTMODE 未在本文件定义(来自 win32k.h/ntgdi 头),值为 4(HALFTONE)。对应 gdi32.dll SetStretchBltMode

3.6 GreGetGraphicsMode(305~318 行)

  • 读取 dc->pdcattr->iGraphicsMode,返回 GM_COMPATIBLE(1)/GM_ADVANCED(2)。
  • 对应 gdi32.dll GetGraphicsMode。图形模式决定是否启用世界变换(SetWorldTransform 需 GM_ADVANCED)以及文本布局细节。

3.7 DCU_SetDcUndeletable(320~334 行)

c 复制代码
VOID FASTCALL DCU_SetDcUndeletable(HDC hDC)
{
    ...
    dc->fs |= DC_PERMANANT;   /* 标记为不可删除 */
    ...
}
  • 作用 :将 DC 标记为"永久"(DC_PERMANANT),防止 DeleteDC 误删(如被窗口 DC(DCE)借用的 DC)。返回 VOID,失败时仅设置错误码。
  • 使用方式 :win32k 内部在创建某些共享/持久 DC 时调用。DeleteDC 路径(IntGdiDeleteDC/NtGdiDeleteDC)会检查此位。

3.8 IntSetDefaultRegion(348~410 行)

c 复制代码
BOOL FASTCALL IntSetDefaultRegion(PDC pdc)
{
    IntGdiReleaseRaoRgn(pdc);                    /* 释放旧 Rao 区域 */

    rclWnd.left = 0; rclWnd.top = 0;
    rclWnd.right = pdc->dclevel.sizl.cx;         /* 表面尺寸 */
    rclWnd.bottom = pdc->dclevel.sizl.cy;
    rclClip = rclWnd;

    if (pdc->ppdev->flFlags & PDEV_META_DEVICE)  /* 元设备:叠加 PDEV 原点 */
    {
        pSurface = pdc->dclevel.pSurface;
        if (pSurface && pSurface->flags & PDEV_SURFACE)
        {
            rclClip.left   += pdc->ppdev->ptlOrigion.x;  /* 注意源码拼写 ptlOrigion */
            ...
        }
    }

    prgn = pdc->prgnVis;
    if (prgn && prgn != prgnDefault)
        REGION_SetRectRgn(prgn, rclClip.left, rclClip.top, rclClip.right, rclClip.bottom);
    else
    {
        prgn = IntSysCreateRectpRgn(...);        /* 新建系统矩形区域 */
        pdc->prgnVis = prgn;
    }

    if (prgn)
    {
        pdc->ptlDCOrig.x = 0;  pdc->ptlDCOrig.y = 0;     /* DC 原点复位 */
        pdc->erclWindow = rclWnd;
        pdc->erclClip = rclClip;
        pdc->ptlFillOrigin = pdc->dcattr.ptlBrushOrigin; /* 填充原点 = 画刷原点 */
        return TRUE;
    }
    pdc->prgnVis = prgnDefault;                  /* 失败回退:默认系统区域 */
    return FALSE;
}
  • 签名BOOL IntSetDefaultRegion(PDC),DC 已锁定状态下调用(不自行加锁)。
  • 作用 :为 DC 建立"默认可见区"------将 prgnVis 设置为覆盖整个表面(dclevel.sizl)的矩形区域,并复位 DC 原点、窗口/裁剪矩形、填充原点。用于 DC 初始化(DC_vInitDc)和恢复默认状态(如 SaveDC/RestoreDC 或清空裁剪后)。
  • 实现流程 :① IntGdiReleaseRaoRgn 释放旧的 prgnRao → ② 构造表面矩形 → ③ 元设备特例 :若 PDEV 是元文件设备且表面是 PDEV 表面,裁剪矩形叠加 ppdev->ptlOrigion(源码拼写如此,实为 ptlOrigin 的笔误)→ ④ 复用/新建 prgnVis → ⑤ 成功则复位 ptlDCOrigerclWindowerclClipptlFillOrigin → 返回。
  • 注意事项ptlDCOrig 复位为 0 意味着窗口滚动等造成的 DC 原点偏移被清除;ptlFillOrigin 直接从 dcattr.ptlBrushOrigin 拷贝(因为此刻 DC 原点为 0,DC_vSetBrushOrigin 的公式 x + ptlDCOrig.x 退化为 x)。

3.9 NtGdiCancelDC(413~418 行)

c 复制代码
BOOL APIENTRY NtGdiCancelDC(HDC hDC)
{
    UNIMPLEMENTED;      /* 打印 WARNING: ... is UNIMPLEMENTED!(见 AGENTS.md) */
    return FALSE;
}
  • 作用 :Windows 的 CancelDC 语义是取消指定 DC 中所有未完成的绘制操作(常用于取消长打印作业中的某个 DC)。ReactOS 尚未实现,是纯 stub。
  • 注意事项 :调用方(gdi32.dll CancelDC)会收到 FALSE 并设置相应错误。这是本文件唯一未实现的 NtGdi 入口。

3.10 IntGdiSetHookFlags(421~451 行)

c 复制代码
WORD APIENTRY IntGdiSetHookFlags(HDC hDC, WORD Flags)
{
    ...
    wRet = dc->fs & DC_DIRTY_RAO;   // FIXME: Wrong flag!
    DPRINT("DC %p, Flags %04x\n", hDC, Flags);

    if (Flags & DCHF_INVALIDATEVISRGN)
        dc->fs |= DC_DIRTY_RAO;                 /* 可见区需更新 */
    else if (Flags & DCHF_VALIDATEVISRGN || 0 == Flags)
    {
        //dc->fs &= ~DC_DIRTY_RAO;               /* 被注释:不清除 */
    }
    ...
    return wRet;
}
  • 签名WORD IntGdiSetHookFlags(HDC, WORD Flags),返回旧标志(当前实现返回 fs & DC_DIRTY_RAO,源码注释 "FIXME: Wrong flag!",即返回值语义未定)。
  • 作用 :供 ntuser(窗口管理器)通知 ntgdi:窗口的可见区(VisRgn)已失效/有效,需要(或不需要)更新 DC 的可见区域。
  • 使用方式与调用点 (ntuser/windc.c):
    • IntGdiSetHookFlags(Dce->hDC, DCHF_INVALIDATEVISRGN)(187、1158 行):窗口显示/滚动/层次变化后,DCE 的可见区已失效,置 DC_DIRTY_RAO,后续任何使用 Rao/可见区的操作(如 NtGdiGetBoundsRect 732 行、NtGdiGetRandomRgn 736 行)都会先调用 CLIPPING_UpdateGCRegion(pdc) 重建。
    • IntGdiSetHookFlags(Dce->hDC, DCHF_VALIDATEVISRGN)(610、931 行):DCE 刷新完毕后通知。当前实现 清除 DC_DIRTY_RAO(清除代码被注释),即"验证"操作实际是空操作------这是已知简化,可能造成可见区被重复重建,但不影响正确性。
  • 注意事项DCHF_INVALIDATEVISRGN=0x0001、DCHF_VALIDATEVISRGN=0x0002 定义于 ntuser/dce.h(与 gdi32 侧 wine/gdi_driver.h 一致)。返回 wRet 是调用方判断"是否需要更新 VisRgn"的依据(windc.c 610 行 if (IntGdiSetHookFlags(...)) bUpdateVisRgn = TRUE)。

3.11 NtGdiGetDCDword(454~554 行)

c 复制代码
BOOL APIENTRY NtGdiGetDCDword(HDC hDC, UINT u, DWORD *Result)
{
    ...
    switch (u)
    {
        case GdiGetJournal:        break;                      /* 空操作 */
        case GdiGetRelAbs:         SafeResult = pdcattr->lRelAbs; break;
        case GdiGetBreakExtra:     SafeResult = pdcattr->lBreakExtra; break;
        case GdiGetCharBreak:      SafeResult = pdcattr->cBreak; break;
        case GdiGetArcDirection:   /* RTL 时取反 */  break;
        case GdiGetEMFRestorDc:    SafeResult = pdc->dclevel.lSaveDepth; break;
        case GdiGetFontLanguageInfo: SafeResult = IntGetFontLanguageInfo(pdc); break;
        case GdiGetIsMemDc:        SafeResult = pdc->dctype; break;
        case GdiGetMapMode:        SafeResult = pdcattr->iMapMode; break;
        case GdiGetTextCharExtra:  SafeResult = pdcattr->lTextExtra; break;
        default:                   EngSetLastError(ERROR_INVALID_PARAMETER); Ret = FALSE; break;
    }
    if (Ret)
    {
        _SEH2_TRY { ProbeForWrite(Result, sizeof(DWORD), 1); *Result = SafeResult; }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); }
        _SEH2_END;
        if (!NT_SUCCESS(Status)) { SetLastNtError(Status); Ret = FALSE; }
    }
    DC_UnlockDc(pdc);
    return Ret;
}
  • 签名BOOL NtGdiGetDCDword(HDC hDC, UINT u, DWORD *Result)u 是属性代号(GdiGet* 枚举),Result 为用户态输出指针。
  • 作用 :按属性代号读取 DC 的一个 DWORD 属性。这是 NT 3.1 时代遗留的"通用 DC 属性查询"接口,主要供老式驱动/兼容代码与 gdi32 内部使用(user32 的 GetArcDirection 等最终也走这类通道)。
  • 各 case 语义
    • GdiGetJournal:无属性,仅占位(break 不设置 SafeResult,保持 0)。

    • GdiGetRelAbs:读取 lRelAbs(相对/绝对坐标标志,MoveToEx 等维护)。

    • GdiGetBreakExtra / GdiGetCharBreak:读取 lBreakExtra(断字额外间距)与 cBreak(断字字符数),均为文本排版属性。

    • GdiGetArcDirection:读取弧线方向 AD_COUNTERCLOCKWISE(1)/AD_CLOCKWISE(2)。实现与 dclevel.flPath & DCPATH_CLOCKWISE 相关:

      c 复制代码
      if (pdcattr->dwLayout & LAYOUT_RTL)
          SafeResult = AD_CLOCKWISE - ((pdc->dclevel.flPath & DCPATH_CLOCKWISE) != 0);
      else
          SafeResult = ((pdc->dclevel.flPath & DCPATH_CLOCKWISE) != 0) + AD_COUNTERCLOCKWISE;

      RTL 布局下方向取反(顺时针↔逆时针互换)。

    • GdiGetEMFRestorDc:读取 dclevel.lSaveDepth(SaveDC 嵌套深度,EMF 播放用)。

    • GdiGetFontLanguageInfo:调用 IntGetFontLanguageInfo(pdc)(text.h,返回字体语言能力位,如 FLI_GLYPHS)。

    • GdiGetIsMemDc:返回 dctype(0=DIRECT,1=MEMORY,2=INFO),即查询"是否为内存 DC"。

    • GdiGetMapMode:返回 iMapMode(与 GreGetMapMode 等价)。

    • GdiGetTextCharExtra:返回 lTextExtra(SetTextCharacterExtra 设置的字符间距)。

    • default:非法代号 → ERROR_INVALID_PARAMETER + FALSE。

  • 实现流程 :① 参数校验(Result 为 NULL 直接失败)→ ② 锁定 DC → ③ switch 取值到本地 SafeResult → ④ SEH 保护写回ProbeForWrite(Result, sizeof(DWORD), 1) 校验用户缓冲可写后赋值;异常捕获 NTSTATUS 经 SetLastNtError 传播 → ⑤ 解锁返回。
  • 注意事项 :所有属性值先在内核本地 计算(SafeResult),再一次性写回用户态------避免"锁内多次触碰用户内存"。IntGetFontLanguageInfo 需要访问字体对象,调用时 DC 仍持锁。

3.12 NtGdiGetAndSetDCDword(556~690 行)

c 复制代码
_Success_(return != FALSE)
BOOL APIENTRY NtGdiGetAndSetDCDword(_In_ HDC hdc, _In_ UINT u,
                                    _In_ DWORD dwIn, _Out_ DWORD *pdwResult)
{
    ...
    switch (u)
    {
        case GdiGetSetEPSPrintingEscape:
            SafeResult = pdc->fs & DC_EPSPRINTINGESCAPE;   /* 读 */
            pdc->fs &= ~DC_EPSPRINTINGESCAPE;              /* 且清除(一次性) */
            break;
        case GdiGetSetCopyCount:
            SafeResult = pdc->ulCopyCount;
            pdc->ulCopyCount = dwIn;                       /* 写 */
            break;
        case GdiGetSetTextAlign:
            SafeResult = pdcattr->lTextAlign;
            pdcattr->lTextAlign = dwIn;
            // pdcattr->flTextAlign = dwIn; // Flags!      /* 注释:不更新 flTextAlign */
            break;
        case GdiGetSetRelAbs:       SafeResult = pdcattr->lRelAbs;   pdcattr->lRelAbs = dwIn; break;
        case GdiGetSetTextCharExtra:SafeResult = pdcattr->lTextExtra;pdcattr->lTextExtra = dwIn; break;
        case GdiGetSetSelectFont:   break;                            /* 空 */
        case GdiGetSetMapperFlagsInternal:
            if (dwIn & ~1) { EngSetLastError(ERROR_INVALID_PARAMETER); Ret = FALSE; break; }
            SafeResult = pdcattr->flFontMapper;  pdcattr->flFontMapper = dwIn;
            break;
        case GdiGetSetMapMode:      SafeResult = IntGdiSetMapMode(pdc, dwIn); break;
        case GdiGetSetArcDirection: /* 校验 + RTL 感知的方向设置 */ break;
        default:                    EngSetLastError(ERROR_INVALID_PARAMETER); Ret = FALSE; break;
    }
    if (Ret) { /* SEH: ProbeForWrite + 写回 pdwResult,异常经 SetLastNtError 传播 */ }
    DC_UnlockDc(pdc);
    return Ret;
}
  • 签名BOOL NtGdiGetAndSetDCDword(HDC, UINT u, DWORD dwIn, DWORD *pdwResult)。与 NtGdiGetDCDword 的区别:读旧值的同时写入新值 ("Get-And-Set" 原子语义,类似 InterlockedExchange 的 DC 属性版)。
  • 作用 :NT 遗留的 DC 属性"交换"接口,供 gdi32 内部(如 SetTextAlign 的实现)一次系统调用完成"返回旧值 + 设置新值",避免 Get/Set 两次调用之间的竞态。
  • 各 case 语义
    • GdiGetSetEPSPrintingEscape:读取 fs & DC_EPSPRINTINGESCAPE清除该位(一次性读清,语义为"消费一个 EPS 转义事件")。

    • GdiGetSetCopyCount:读 ulCopyCount(打印份数),写 dwIn

    • GdiGetSetTextAlign:读 lTextAlign、写 lTextAlign = dwIn注意注释 // pdcattr->flTextAlign = dwIn; // Flags! ------源码明确不更新 flTextAlign(RTL 修正后的对齐位),这是一个已知不完整处:走此通道设置文本对齐不会产生 RTL 镜像修正(IntGdiSetTextAlign 才会)。

    • GdiGetSetRelAbs / GdiGetSetTextCharExtra:对称读写 lRelAbs / lTextExtra

    • GdiGetSetSelectFont:空操作(占位,字体选择另有专路)。

    • GdiGetSetMapperFlagsInternal:设置字体映射标志 flFontMapper校验dwIn & ~1 非零(即除位 0 外还有其它位)→ ERROR_INVALID_PARAMETER + FALSE;合法则返回旧值并写入新值。

    • GdiGetSetMapMode核心 case ------直接调用 IntGdiSetMapMode(pdc, dwIn)(coord.c 826~911 行),返回值(旧映射模式)作为 SafeResult。注意 IntGdiSetMapMode 返回旧模式且失败返回 0;此处不额外校验。

    • GdiGetSetArcDirection:带参数校验与 RTL 感知的设置弧线方向:

      c 复制代码
      if (dwIn != AD_COUNTERCLOCKWISE && dwIn != AD_CLOCKWISE) { /* ERROR_INVALID_PARAMETER; Ret=FALSE; */ }
      if (pdcattr->dwLayout & LAYOUT_RTL) {
          SafeResult = AD_CLOCKWISE - ((pdc->dclevel.flPath & DCPATH_CLOCKWISE) != 0);
          if (dwIn == AD_CLOCKWISE) { pdc->dclevel.flPath &= ~DCPATH_CLOCKWISE; break; }
          pdc->dclevel.flPath |= DCPATH_CLOCKWISE;
      } else {
          SafeResult = ((pdc->dclevel.flPath & DCPATH_CLOCKWISE) != 0) + AD_COUNTERCLOCKWISE;
          if (dwIn == AD_COUNTERCLOCKWISE) { pdc->dclevel.flPath &= ~DCPATH_CLOCKWISE; break; }
          pdc->dclevel.flPath |= DCPATH_CLOCKWISE;
      }

      方向状态实际存储在 dclevel.flPath & DCPATH_CLOCKWISE 位中:设置 AD_COUNTERCLOCKWISE(LTR 下)清位、AD_CLOCKWISE 置位;RTL 布局下两个方向的操作互换,且返回旧值时也做了 RTL 取反。

  • 实现流程 :① 校验 pdwResult 非空 → ② 锁定 → ③ switch(读旧值→写新值,顺序因 case 而异)→ ④ SEH 保护写回 pdwResult → ⑤ 解锁。
  • 注意事项 :本函数与 NtGdiGetDCDword 覆盖的属性集合不重叠 (Get 版有 GdiGetJournal/ArcDirection/EMFRestorDc/FontLanguageInfo/IsMemDc/MapMode,GetAndSet 版有 EPS/CopyCount/TextAlign/RelAbs/TextCharExtra/SelectFont/MapperFlags/MapMode/ArcDirection)。共享的 GdiGetSetMapMode/GdiGetSetArcDirection 说明部分属性只有"读写一体"通道。

3.13 IntUpdateBoundsRect(692~704 行)

c 复制代码
VOID FASTCALL IntUpdateBoundsRect(PDC pdc, PRECTL pRect)
{
   if (pdc->fs & DC_ACCUM_APP)
      RECTL_bUnionRect(&pdc->erclBoundsApp, &pdc->erclBoundsApp, pRect);
   if (pdc->fs & DC_ACCUM_WMGR)
      RECTL_bUnionRect(&pdc->erclBounds, &pdc->erclBounds, pRect);
}
  • 签名VOID IntUpdateBoundsRect(PDC, PRECTL),DC 已锁定,矩形为设备坐标(由调用方保证)。

  • 作用 :把新矩形并入 DC 的累计边界矩形。**边界矩形(BoundsRect)**是 GDI 提供的"自上次查询以来本 DC 绘制过的区域外接框"服务(GetBoundsRect/SetBoundsRect,见《分析_31》),用于应用程序减少重绘范围(先查边界框,只重画受影响区域)。

  • 实现流程 :按 fs 中启用的累计目标分别求并集:

    • DC_ACCUM_APP(应用累计,SetBoundsRect 默认启用)→ erclBoundsApp(逻辑坐标语义,但调用方传入设备坐标矩形)。
    • DC_ACCUM_WMGR(窗口管理器累计,SetBoundsRect(hdc, NULL, DCB_WINDOWMGR|DCB_ENABLE) 启用)→ erclBounds(设备坐标,供 win32k 内部使用)。
  • 使用方式 :被 ntgdi 各绘制函数调用,如 NtGdiSetPixel(bitblt.c 1493~1507 行)在绘制 1×1 像素前:

    c 复制代码
    if (pdc->fs & (DC_ACCUM_APP|DC_ACCUM_WMGR))
    {
       RECTL_vSetRect(&rcDst, x, y, x+1, y+1);
       IntLPtoDP(pdc, (LPPOINT)&rcDst, 2);              /* 逻辑→设备 */
       rcDst.left += pdc->ptlDCOrig.x; ...               /* 叠加 DC 原点 */
       IntUpdateBoundsRect(pdc, &rcDst);
    }

    line.c(60 行)、fillshap.c 等绘制路径同样在输出前调用。

  • 注意事项RECTL_bUnionRect(rect.c 16~51 行)对空矩形(left>=right 或 top>=bottom)有特殊处理:两空→置空返回 FALSE;一空一非空→直接拷贝非空者;两个非空→逐边 min/max。

3.14 NtGdiGetBoundsRect(706~797 行)

c 复制代码
DWORD APIENTRY NtGdiGetBoundsRect(IN HDC hdc, OUT LPRECT prc, IN DWORD flags)
{
    ...
    if (!(pdc = DC_LockDc(hdc))) return 0;

    if (!(flags & DCB_WINDOWMGR))                      /* ── APP 路径 ── */
    {
       rc = pdc->erclBoundsApp;

       if (RECTL_bIsEmptyRect(&rc))                    /* 空:报告 RESET */
       {
          rc.left = rc.top = rc.right = rc.bottom = 0;
          ret = DCB_RESET;
       }
       else
       {
          RECTL rcRgn;
          if (pdc->fs & DC_DIRTY_RAO) CLIPPING_UpdateGCRegion(pdc);  /* 可见区重建 */
          if (!REGION_GetRgnBox(pdc->prgnRao, &rcRgn))               /* 先 Rao */
             REGION_GetRgnBox(pdc->prgnVis, &rcRgn);                 /* 回退 Vis */
          rc.left   = max( rc.left, 0 );               /* 与表面裁剪 */
          rc.top    = max( rc.top, 0 );
          rc.right  = min( rc.right,  rcRgn.right );
          rc.bottom = min( rc.bottom, rcRgn.bottom );
          ret = DCB_SET;
       }
       IntDPtoLP(pdc, (PPOINTL)&rc, 2);                /* 设备→逻辑 */

       if (pdc->pdcattr->dwLayout & LAYOUT_RTL)        /* RTL:交换左右边界 */
       {
           LONG temp = rc.left;
           rc.left = rc.right;
           rc.right = temp;
           rc.right--;                                 /* 独占右边界修正 */
       }
    }
    else                                               /* ── WMGR 路径 ── */
    {
       rc = pdc->erclBounds;
       ret = DCB_SET;
    }

    _SEH2_TRY { ProbeForWrite(prc, sizeof(RECT), 1); *prc = rc; }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { ret = 0; }
    _SEH2_END;

    if (flags & DCB_RESET)                             /* 查询后复位累计 */
    {
        if (!(flags & DCB_WINDOWMGR)) { pdc->erclBoundsApp.left = pdc->erclBoundsApp.top = INT_MAX;
                                        pdc->erclBoundsApp.right = pdc->erclBoundsApp.bottom = INT_MIN; }
        else                      { pdc->erclBounds.left = pdc->erclBounds.top = INT_MAX;
                                    pdc->erclBounds.right = pdc->erclBounds.bottom = INT_MIN; }
    }
    DC_UnlockDc(pdc);
    return ret;
}
  • 签名DWORD NtGdiGetBoundsRect(HDC, LPRECT prc, DWORD flags)flags 可取 DCB_WINDOWMGR(查询窗口管理器累计)与 DCB_RESET(查询后复位累计)。返回值是 DCB_SET(1)/DCB_RESET(2)/DCB_ENABLE(4)/DCB_DISABLE(8) 的组合位(语义:当前边界是否有效/是否已复位)。
  • 作用 :返回自上次(重置后)以来 DC 的绘制外接矩形。对应 gdi32.dll GetBoundsRect
  • 实现流程(APP 路径) :① 取 erclBoundsApp → ② 空矩形判定RECTL_bIsEmptyRect:left>=right 或 top>=bottom):空 → 全 0 + DCB_RESET;非空 → ③ 裁剪到可见区域 :若 DC_DIRTY_RAOCLIPPING_UpdateGCRegion 重建 Rao 区,取 prgnRao 的包围盒,失败回退 prgnVis,再将累计边界与表面区域相交(max(...,0)/min(..., rcRgn.*))→ ④ 坐标转换IntDPtoLP 把设备坐标转回逻辑坐标(边界累计在设备坐标下进行,返回给应用时按当前变换换算)→ ⑤ RTL 修正 :LAYOUT_RTL 下交换 left/right 并 right--(源码注释解释:RTL 下原左边界像素坐标成为独占右边界 ,需减 1 以符合"右开"矩形语义)→ ⑥ 返回 DCB_SET
  • 实现流程(WMGR 路径) :直接取 erclBounds(设备坐标,不转换、不裁剪)→ DCB_SET。窗口管理器(ntuser)用此路径获取自身绘制活动范围。
  • 实现流程(尾部公共) :SEH 保护写回 prc(异常则 ret=0)→ 若 flags & DCB_RESET,把对应累计矩形重置为"空集":left/top=INT_MAX、right/bottom=INT_MIN(这样下一次 RECTL_bUnionRect 的第一个矩形会直接拷贝进累计区)→ 解锁返回。
  • 注意事项 :① APP 路径的裁剪语义意味着返回的矩形不超出 当前可见/表面区域;② 空边界报告为全 0 + DCB_RESET(而不是 DCB_SET),调用方据此知道"尚无绘制发生";③ IntDPtoLP 与 RTL 修正顺序不可颠倒------先按设备坐标转逻辑,再按 RTL 语义调整边界方向。

3.15 NtGdiSetBoundsRect(799~878 行)

c 复制代码
DWORD APIENTRY NtGdiSetBoundsRect(IN HDC hdc, IN LPRECT prc, IN DWORD flags)
{
    DWORD ret;
    ...
    /* ① 参数校验:ENABLE 与 DISABLE 互斥 */
    if ((flags & DCB_ENABLE) && (flags & DCB_DISABLE)) return 0;

    if (!(pdc = DC_LockDc(hdc))) return 0;

    /* ② 构造返回值:当前累计状态 + 边界有效性 */
    ret = DCB_RESET; /* we don't have device-specific bounds */
    ret = (pdc->fs & (DC_ACCUM_APP|DC_ACCUM_WMGR) ? DCB_ENABLE : DCB_DISABLE) |
          (RECTL_bIsEmptyRect(&pdc->erclBoundsApp) ? ret & DCB_SET : DCB_SET );
    ret |= (flags & DCB_WINDOWMGR);

    /* ③ 复位累计 */
    if (flags & DCB_RESET) { /* 同 NtGdiGetBoundsRect 的重置逻辑 */ }

    /* ④ 累计新矩形 */
    if (flags & DCB_ACCUMULATE && prc != NULL)
    {
        _SEH2_TRY { ProbeForRead(prc, sizeof(RECT), 1); rcl = *prc; }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { DC_UnlockDc(pdc); _SEH2_YIELD(return 0;); }
        _SEH2_END;

        RECTL_vMakeWellOrdered(&rcl);                 /* 归一化方向 */

        if (!(flags & DCB_WINDOWMGR))
        {
           IntLPtoDP( pdc, (POINT *)&rcl, 2 );        /* 逻辑→设备 */
           RECTL_bUnionRect(&pdc->erclBoundsApp, &pdc->erclBoundsApp, &rcl);
        }
        else
           RECTL_bUnionRect(&pdc->erclBounds, &pdc->erclBounds, &rcl);
    }

    /* ⑤ 启用/禁用累计 */
    if (flags & DCB_ENABLE)
    { if (!(flags & DCB_WINDOWMGR)) pdc->fs |= DC_ACCUM_APP; else pdc->fs |= DC_ACCUM_WMGR; }
    if (flags & DCB_DISABLE)
    { if (!(flags & DCB_WINDOWMGR)) pdc->fs &= ~DC_ACCUM_APP; else pdc->fs &= ~DC_ACCUM_WMGR; }

    DC_UnlockDc(pdc);
    return ret;
}
  • 签名DWORD NtGdiSetBoundsRect(HDC, LPRECT prc, DWORD flags)flagsDCB_ACCUMULATE(2)/DCB_RESET(4)/DCB_ENABLE(8)/DCB_DISABLE(16)/DCB_WINDOWMGR(32) 的组合。对应 gdi32.dll SetBoundsRect
  • 作用 :控制边界矩形的累计开关 (ENABLE/DISABLE)、手动累计 (ACCUMULATE,把 prc 并入累计区)、复位 (RESET)。prc 为 NULL 时仅执行开关/复位操作。
  • 实现流程 :① 校验 ENABLE 与 DISABLE 同传 → 返回 0 → ② 锁定 → ③ 构造返回值:DCB_ENABLE(若任一累计已启用)或 DCB_DISABLE(都未启用),并合并 DCB_SETerclBoundsApp 非空时;空时表达式 ret & DCB_SET------初始 ret=DCB_RESET(2),2 & 1 = 0,故空时返回不含 DCB_SET);随后 ret |= (flags & DCB_WINDOWMGR) 保持调用方的 WMGR 位 → ④ DCB_RESET 时重置累计矩形 → ⑤ DCB_ACCUMULATE && prc 时:SEH 读用户矩形 → RECTL_vMakeWellOrdered(保证 left<=right、top<=bottom,见 rect.c 79~97 行)→ APP 路径先 IntLPtoDP(应用传入的是逻辑坐标)再并集到 erclBoundsApp;WMGR 路径直接并集到 erclBounds(WMGR 传入设备坐标)→ ⑥ DCB_ENABLE/DCB_DISABLE 设置 fs 对应位 → ⑦ 解锁返回 ret。
  • 注意事项 :① 注意 APP 与 WMGR 两路径的坐标约定相反IntUpdateBoundsRect(绘制路径调用)与 NtGdiGetBoundsRect(WMGR 读)使用设备坐标;而 NtGdiSetBoundsRect 的 APP 路径把用户矩形当逻辑坐标 先转设备再累计,WMGR 路径则直接按设备坐标处理------三处必须一致,否则边界会偏移;② 返回值与 GetBoundsRect 一致,可在 GetBoundsRect(hdc, NULL, DCB_RESET) 之外直接通过返回值判断累计开关状态。

3.16 TranslateCOLORREF(880~962 行)

c 复制代码
/* Translates a COLORREF to the right color in the specified DC color space */
ULONG TranslateCOLORREF(PDC pdc, COLORREF crColor)
{
    PSURFACE psurfDC = pdc->dclevel.pSurface;
    PPALETTE ppalDC;
    ULONG index, ulColor;
    EXLATEOBJ exlo;

    if (!psurfDC) psurfDC = psurfDefaultBitmap;        /* 无表面→默认位图 */

    /* ① COLORREF 类型分类(高字节标志位) */
    if ((crColor & 0xFF000000) == 0)
    { /* 纯 RGB:直接使用 */ }
    else if (crColor & 0x01000000)                     /* PALETTEINDEX */
    {
        index = crColor & 0xFFFF;
        ppalDC = pdc->dclevel.ppal;
        if (index >= ppalDC->NumColors) index = 0;
        crColor = PALETTE_ulGetRGBColorFromIndex(ppalDC, index);
    }
    else if (crColor & 0x02000000)                     /* PALETTERGB */
    {
        if (pdc->dclevel.hpal != StockObjects[DEFAULT_PALETTE])
        {
            ppalDC = pdc->dclevel.ppal;
            index = PALETTE_ulGetNearestIndex(ppalDC, crColor & 0xFFFFFF);
            crColor = PALETTE_ulGetRGBColorFromIndex(ppalDC, index);
        }
        else { crColor = crColor & 0x00FFFFFF; }       /* 默认调色板→原样 RGB */
    }
    else if ((crColor & 0x10FF0000) == 0x10FF0000)     /* DIBINDEX */
    {
        if (psurfDC->ppal->flFlags & PAL_INDEXED)      /* 表面为索引色 */
        {
            index = crColor & 0xFF;
            if (index >= psurfDC->ppal->NumColors) index = 0;
            return index;                              /* 直接返回调色板索引 */
        }
        else { crColor = crColor & 0x00FFFFFF; }
    }
    else { DPRINT("Invalid COLORREF 0x08X\n", crColor); crColor = 0; }

    /* ② 用 RGB→表面调色板的 XLATEOBJ 做最终换算 */
    EXLATEOBJ_vInitialize(&exlo, &gpalRGB, psurfDC->ppal, CLR_INVALID, CLR_INVALID, CLR_INVALID);
    ulColor = XLATEOBJ_iXlate(&exlo.xlo, crColor);
    EXLATEOBJ_vCleanup(&exlo);
    return ulColor;
}
  • 签名ULONG TranslateCOLORREF(PDC pdc, COLORREF crColor),DC 已锁定。返回目标表面格式下的颜色值(可能是索引、16/32 位 RGB 等)。
  • 作用 :把用户态的 COLORREF 翻译成"当前 DC 表面像素格式下的颜色值"。这是 SetPixel 等操作的第一步------内核中所有绘制颜色最终都要落到表面的具体位格式。
  • COLORREF 编码回顾 (高字节标志):
    • 0x00RRGGBB:直接 RGB。
    • 0x0100xxxxPALETTEINDEX):低 16 位是调色板索引,用 DC 调色板把索引换算回 RGB(越界索引归 0)。
    • 0x0200RRGGBBPALETTERGB):先经 DC 调色板找最近色索引再取回 RGB(相当于把颜色"贴"到 DC 调色板上);若 DC 用默认调色板则直接取 RGB 低 24 位。
    • 0x10FFxxxxDIBINDEX,注意是 0x10FF0000 全掩码匹配):低 8 位是表面调色板索引;仅当表面为索引色(PAL_INDEXED) 时直接返回该索引(跳过 XLATE),否则按 RGB 处理。
    • 其它(含 0xFF000000 未定义位):打印 Invalid COLORREF 并当黑色处理。
  • 实现流程 :① 表面缺省为 psurfDefaultBitmap(全局默认位图,保证索引色/位深信息可用)→ ② 分类转换得到 RGB → ③ EXLATEOBJ_vInitialize(&exlo, &gpalRGB, psurfDC->ppal, CLR_INVALID×3):构建"标准 RGB 调色板 → 表面调色板"的转换对象(第三个参数起是背景/前景色,用 CLR_INVALID 表示不用)→ ④ XLATEOBJ_iXlate(&exlo.xlo, crColor) 执行转换 → ⑤ EXLATEOBJ_vCleanup 释放 → 返回。
  • 使用方式NtGdiSetPixel(bitblt.c 1510 行)用它把 COLORREF 转为表面色后经 IntPatBlt 绘制;SetBrushOrg/文本等路径用 EBRUSHOBJ 内部的同机制。反向转换(表面色→RGB)在 NtGdiSetPixel 尾部用"表面调色板→gpalRGB"的反向 XLATEOBJ 完成。
  • 注意事项EXLATEOBJ_vInitializeXLATEOBJ_iXlate 属于 GDI 引擎(win32ss/gdi/eng/xlateobj.*),详见《分析_19》。gpalRGB 定义于 palette.c(PALETTE gpalRGB, gpalBGR, ...),是 24 位 RGB 的全局调色板常量。

4. 机制专题

4.1 脏标记机制(Dirty Flags)与画刷重建

脏标记是 dcutil.c 所有"写属性"函数的统一出口。其生命周期分为四步:

复制代码
① 写属性(dcutil.c)
   IntGdiSetBkColor / IntGdiSetTextColor / IntSetDCBrushColor / IntSetDCPenColor
        │  置位
        ▼
② ulDirty_ |= DIRTY_XXX
        │
        ├─ IntGdiSetTextColor ──► 立即调用 DC_vUpdateTextBrush/LineBrush/FillBrush(同步消费)
        └─ 其余 ──► 等待下次绘制前(惰性消费)
        │
        ▼
③ 消费(dcobjs.c)
   DC_vUpdateFillBrush      :  hbrush 变化? 换 pbrFill;置脏则 EBRUSHOBJ_vUpdateFromDC;
                                DC_BRUSH 则 EBRUSHOBJ_vSetSolidRGBColor(crBrushClr);清 DIRTY_FILL|DC_BRUSH_DIRTY
   DC_vUpdateLineBrush      :  对称;DC_PEN 则设置 crPenClr;清 DIRTY_LINE|DC_PEN_DIRTY
   DC_vUpdateTextBrush      :  置脏则 EBRUSHOBJ_vUpdateFromDC(pbrDefaultBrush);
                                无条件 EBRUSHOBJ_vSetSolidRGBColor(crForegroundClr);清 DIRTY_TEXT
   DC_vUpdateBackgroundBrush:  置脏则 EBRUSHOBJ_vUpdateFromDC(pbrDefaultBrush);
                                无条件 EBRUSHOBJ_vSetSolidRGBColor(crBackgroundClr);清 DIRTY_BACKGROUND
        │
        ▼
④ EBRUSHOBJ(驱动接口)重建
   EBRUSHOBJ_vUpdateFromDC(ntgdi/ebrushobj.c)→ 按需调用驱动 DrvRealizeBrush / 生成模式画刷

为什么文本色走"立即重建"而背景色走"惰性重建"? 观察源码:

  • IntGdiSetTextColor 在锁内直接调用三个 DC_vUpdate*------文本色在 ExtTextOut 路径中几乎每次都要用,且 DC_vUpdateTextBrush 本身是"置脏才重建 EBRUSHOBJ、无条件设纯色、清位"三步,代价极低,立即执行反而省去绘制路径的重复检查。
  • IntGdiSetBkColor 置位后不重建背景画刷,只通过"重选当前画刷"(NtGdiSelectBrush顺带 重建填充画刷;背景画刷(eboBackground)的重建推迟到实际需要背景色的路径(如虚线、文本背景)首次使用 DC_vUpdateBackgroundBrush 时。
  • IntSetDCBrushColor/IntSetDCPenColor 则完全依赖惰性消费------它们连重选都不做,因为 DC_vUpdateFillBrush/LineBrush 的 DC_BRUSH/DC_PEN 分支会在下次绘制时把新颜色写入。

"cr* 与 ul* 双份颜色"的作用ul* 是无条件写入的(API 总能看到最新值),cr* 是"上次已生效值"。比较二者可消除重复置位 :连续两次 SetBkColor(同色) 只有第一次触发脏位,第二次直接短路。这是性能优化,也是脏标记机制的正确性基础------GetBkColorulBackgroundClr 永远返回用户设置值,而内部画刷只跟随 crBackgroundClr 变化。

关键位一览(置位者 → 消费者):

脏位 置位者(本文件) 消费者(dcobjs.c)
DIRTY_FILL (0x1) IntGdiSetBkColor、IntGdiSetTextColor、IntSetDCBrushColor DC_vUpdateFillBrush
DIRTY_LINE (0x2) IntGdiSetBkColor、IntGdiSetTextColor、IntSetDCPenColor DC_vUpdateLineBrush
DIRTY_TEXT (0x4) IntGdiSetTextColor DC_vUpdateTextBrush
DIRTY_BACKGROUND (0x8) IntGdiSetBkColor DC_vUpdateBackgroundBrush

4.2 LPtoDP/DPtoLP:坐标变换链

GreLPtoDP/GreDPtoLP 只是薄壳,真正的逻辑在 coord.h 的内联函数与 coord.c 的矩阵维护函数中。

三级变换模型:GDI 的坐标体系是"世界坐标 → 页面坐标 → 设备坐标":

复制代码
世界坐标 (World)                   页面坐标 (Page)                设备坐标 (Device)
   (x,y)      ──世界变换──►     (x',y')   ──页→设备变换──►    (x'',y'')
              SetWorldTransform            窗口原点/视口原点、
          (GM_ADVANCED 才可用)             窗口范围/视口范围、MM_* 映射模式

三个矩阵缓存在 DC_ATTR/DCLEVEL 中:

  • mxWorldToPage(世界→页面):SetWorldTransform 设置的 XFORM(仅在 GM_ADVANCED 下非恒等)。
  • mxWorldToDevice(世界→设备):DC_vUpdateWorldToDevicemxWorldToPage 与"页→设备"矩阵合成。
  • mxDeviceToWorld(设备→世界):DC_vUpdateDeviceToWorldmxWorldToDevice 求逆。

惰性更新协议(coord.h):

c 复制代码
FORCEINLINE PMATRIX DC_pmxWorldToDevice(PDC pdc)
{
    if (pdc->pdcattr->flXform & (PAGE_XLATE_CHANGED|PAGE_EXTENTS_CHANGED|WORLD_XFORM_CHANGED))
         DC_vUpdateWorldToDevice(pdc);          /* 任一相关位变化→重算合成矩阵 */
    return &pdc->pdcattr->mxWorldToDevice;
}
FORCEINLINE PMATRIX DC_pmxDeviceToWorld(PDC pdc)
{
    if (pdc->pdcattr->flXform & DEVICE_TO_WORLD_INVALID)
         DC_vUpdateDeviceToWorld(pdc);          /* 逆矩阵失效→重算 */
    return &pdc->pdcattr->mxDeviceToWorld;
}

DC_vUpdateWorldToDevice(coord.c 182~200 行)的实现:用 DC_vGetPageToDevice 构造页→设备矩阵 mxPageToDevice,再 XFORMOBJ_iCombine(&xoWorldToDevice, &xoWorldToPage, &xoPageToDevice) 合成,最后清 WORLD_XFORM_CHANGEDDC_vUpdateDeviceToWorld(coord.c 204 行起)则用 XFORMOBJ_iInverse 求逆。

IntLPtoDP / IntDPtoLP(coord.h 179~197 行):

c 复制代码
static inline BOOLEAN IntLPtoDP(DC* pdc, PPOINTL ppt, UINT count)
{
    DC_vUpdateWorldToDevice(pdc);               /* 保证矩阵最新 */
    return INTERNAL_LPTODP(pdc, (LPPOINT)ppt, count);
}
static inline BOOLEAN IntDPtoLP(DC* pdc, PPOINTL ppt, UINT count)
{
    DC_vUpdateDeviceToWorld(pdc);               /* 保证逆矩阵最新 */
    return INTERNAL_DPTOLP(pdc, (LPPOINT)ppt, count);
}

INTERNAL_APPLY_MATRIX(coord.h 10~42 行):核心矩阵乘法,用 FLOATOBJ(定点浮点,避免浮点环境切换)逐点计算:

c 复制代码
/* x' = x * M11 + y * M21 + Dx */
/* y' = x * M12 + y * M22 + Dy */
FLOATOBJ_SetLong(&x, points[count].x);
FLOATOBJ_Mul(&x, &matrix->efM11);            /* x*M11 */
tmp = matrix->efM21; FLOATOBJ_MulLong(&tmp, points[count].y);  /* y*M21 */
FLOATOBJ_Add(&x, &tmp);
FLOATOBJ_Add(&x, &matrix->efDx);             /* +Dx */
... 同样处理 y 分量 ...
FLOATOBJ_bConvertToLong(&x, &points[count].x);  /* 定点→整数,溢出返回 FALSE */

MIN_COORD/MAX_COORDINT_MIN/16INT_MAX/16)是坐标空间的安全边界,防止 FLOATOBJ 中间结果溢出。

MM_ISOTROPIC 的特殊处理 (coord.h DC_pszlViewportExt):各向同性映射要求 X/Y 缩放一致,当 PAGE_EXTENTS_CHANGEDiMapMode == MM_ISOTROPIC 时,读取视口范围前调用 DC_vFixIsotropicMapping(pdc) 修正(按设备像素纵横比调整)。

触发矩阵失效的源头 (均在 coord.c):IntGdiSetMapMode(置 PAGE_XLATE_CHANGED|PAGE_EXTENTS_CHANGED|INVALIDATE_ATTRIBUTES|DEVICE_TO_PAGE_INVALID|WORLD_XFORM_CHANGED|DEVICE_TO_WORLD_INVALID)、GreSetViewportOrgEx(置 PAGE_XLATE_CHANGED|WORLD_XFORM_CHANGED|DEVICE_TO_WORLD_INVALID)、SetWindowExtEx(置 PAGE_EXTENTS_CHANGED|...)等。dcutil.c 中唯一直接触碰该机制的入口是 NtGdiGetAndSetDCDwordGdiGetSetMapMode case(IntGdiSetMapMode),以及 NtGdiGetBoundsRect/NtGdiSetBoundsRect 中通过 IntDPtoLP/IntLPtoDP 间接触发的矩阵更新。

4.3 SetPixel 颜色转换(EXLATEOBJ 双向)

任务中提到的 IntGdiSetPixel/IntGdiGetPixel 并不存在于 dcutil.c (源码核对确认)。SetPixel/GetPixel 的 NtGdi 入口位于 bitblt.cNtGdiSetPixel 1463 行、NtGdiGetPixel 1551 行),它们正是 dcutil.c 中 TranslateCOLORREFIntUpdateBoundsRect 的主要消费者:

NtGdiSetPixel 完整流程(bitblt.c 1463~1549 行):

复制代码
SetPixel(hdc, x, y, crColor)
   │
   ├─ DC_LockDc;无表面(空内存 DC/信息 DC)→ CLR_INVALID
   │
   ├─ 边界累计:若 fs & (DC_ACCUM_APP|DC_ACCUM_WMGR)
   │    RECTL_vSetRect(&rcDst, x, y, x+1, y+1)
   │    IntLPtoDP(pdc, (LPPOINT)&rcDst, 2)        ← coord.h 变换
   │    rcDst ± ptlDCOrig
   │    IntUpdateBoundsRect(pdc, &rcDst)          ← dcutil.c
   │
   ├─ iSolidColor = TranslateCOLORREF(pdc, crColor)   ← dcutil.c(RGB/索引→表面色)
   │
   ├─ pebo = &pdc->eboText                          (复用文本画刷:恒为纯色)
   │    iOldColor = EBRUSHOBJ_iSetSolidColor(pebo, iSolidColor)
   │    ulDirty = pdcattr->ulDirty_; 清 DIRTY_TEXT  (临时借用,事后恢复)
   │    bResult = IntPatBlt(pdc, x, y, 1, 1, PATCOPY, pebo)   ← 1×1 填充
   │    EBRUSHOBJ_iSetSolidColor(pebo, iOldColor); 恢复 ulDirty_
   │
   ├─ 反向转换(表面色→RGB,返回给调用方)
   │    EXLATEOBJ_vInitialize(&exlo, surf->ppal, &gpalRGB, ...)
   │    crColor = XLATEOBJ_iXlate(&exlo.xlo, iSolidColor)
   │
   └─ 返回 bResult ? crColor : CLR_INVALID

关键技巧:借用 eboText 做纯色画刷 ------文本画刷是"恒为纯色、永远存在"的 EBRUSHOBJ,临时改色绘制 1×1 后立即恢复,避免了新建/销毁画刷的开销;同时用"保存-恢复"ulDirty_ 的方式保护 DIRTY_TEXT 位不被 IntPatBlt 内部的 DC_vUpdateTextBrush 消费。TranslateCOLORREF 在其中负责正方向 (COLORREF→表面格式),反向 XLATEOBJ 负责反方向(表面格式→RGB)。

NtGdiGetPixel 则是把 (x,y) 的 1×1 区域经 IntEngBitBlt(SRCCOPY)拷贝到临时 1×1 DIB 表面,再经反向 XLATEOBJ 得到 RGB 返回。

4.4 边界矩形(BoundsRect)机制

边界矩形系统由 dcutil.c 的四个函数 + 两个状态字段构成:

组件 类型 坐标 写入者 读取者
erclBoundsApp DC 字段(RECTL) 设备坐标累计,返回时转逻辑 IntUpdateBoundsRect(绘制路径)、NtGdiSetBoundsRect(APP 累计) NtGdiGetBoundsRect(APP 路径)
erclBounds DC 字段(RECTL) 设备坐标 IntUpdateBoundsRect(绘制路径)、NtGdiSetBoundsRect(WMGR 累计) NtGdiGetBoundsRect(WMGR 路径)
DC_ACCUM_APP fs 位 --- NtGdiSetBoundsRect(DCB_ENABLE/DISABLE) IntUpdateBoundsRect
DC_ACCUM_WMGR fs 位 --- NtGdiSetBoundsRect(DCB_ENABLE/DISABLE) IntUpdateBoundsRect

工作流程:

复制代码
应用:SetBoundsRect(hdc, NULL, DCB_ENABLE)           ← 开启应用累计
      [ 执行一系列绘制:LineTo / BitBlt / SetPixel ... ]
        └─ 每次绘制前:IntUpdateBoundsRect(pdc, 设备矩形)   ← 自动累计
应用:GetBoundsRect(hdc, &rc, DCB_RESET)             ← 查询 + 复位
        ├─ 边界与 Rao/Vis 包围盒相交(裁剪到可见区)
        ├─ IntDPtoLP 转逻辑坐标
        ├─ RTL 布局交换左右边界
        └─ 返回 DCB_SET;若从未绘制返回 DCB_RESET + 全 0

设计要点:

  1. 累计发生在设备坐标 (绘制路径先 IntLPtoDP 再累计),返回时转回逻辑坐标 ------这样边界对应用透明,应用可直接用于 InvalidateRect
  2. 空集表示法left/top = INT_MAXright/bottom = INT_MIN("倒置矩形"),RECTL_bIsEmptyRect 判定 left>=right || top>=bottom 成立。复位时写这个特殊值,使下次 RECTL_bUnionRect 直接拷贝新矩形。
  3. WMGR 路径独立DCB_WINDOWMGR 的累计不经过坐标转换、不裁剪、返回设备坐标,供 win32k 内部(ntuser 绘制跟踪)使用,与应用路径完全隔离。
  4. Rao 区惰性重建的联动NtGdiGetBoundsRect 的 APP 路径若发现 DC_DIRTY_RAO(由 IntGdiSetHookFlags 或窗口滚动置位),先 CLIPPING_UpdateGCRegion 重建可见区再求包围盒------保证边界裁剪基于最新可见状态。

4.5 RTL 布局(LAYOUT_RTL)处理汇总

dcutil.c 中有三处 RTL 相关逻辑,值得总结:

函数 RTL 行为 目的
IntGdiSetTextAlign 非 TA_CENTER 时 Mode ^= TA_RIGHT 翻转左右 文本对齐在 RTL 布局下左右镜像
NtGdiGetDCDword(GdiGetArcDirection)与 NtGdiGetAndSetDCDword(GdiGetSetArcDirection) 方向值取反、设置方向时交换 CW/CCW 操作 弧线方向在镜像坐标系中语义反转
NtGdiGetBoundsRect(APP 路径) 交换 left/right 后 right-- RTL 下边界矩形方向保持视觉一致性,独占右边界修正

dwLayoutSetLayout(coord.c/gdi32)设置;GreGetDCPointGreGetWindowExtEx 等 coord.c 函数也会读取 dwLayout(如窗口范围取负 X = -X),共同构成 RTL 镜像支持。


5. 调用链(mermaid)

5.1 属性设置 → 画刷重建

渲染错误: Mermaid 渲染失败: Parse error on line 4: ...y_: DIRTY_BACKGROUND|DIRTY_LINE|DIRTY_FI -----------------------^ Expecting 'SQE', 'TAGEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PIPE'

5.2 坐标换算

#mermaid-svg-UeYwODLEIthql0vv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UeYwODLEIthql0vv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UeYwODLEIthql0vv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UeYwODLEIthql0vv .error-icon{fill:#552222;}#mermaid-svg-UeYwODLEIthql0vv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UeYwODLEIthql0vv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UeYwODLEIthql0vv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UeYwODLEIthql0vv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UeYwODLEIthql0vv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UeYwODLEIthql0vv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UeYwODLEIthql0vv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UeYwODLEIthql0vv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UeYwODLEIthql0vv .marker.cross{stroke:#333333;}#mermaid-svg-UeYwODLEIthql0vv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UeYwODLEIthql0vv p{margin:0;}#mermaid-svg-UeYwODLEIthql0vv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UeYwODLEIthql0vv .cluster-label text{fill:#333;}#mermaid-svg-UeYwODLEIthql0vv .cluster-label span{color:#333;}#mermaid-svg-UeYwODLEIthql0vv .cluster-label span p{background-color:transparent;}#mermaid-svg-UeYwODLEIthql0vv .label text,#mermaid-svg-UeYwODLEIthql0vv span{fill:#333;color:#333;}#mermaid-svg-UeYwODLEIthql0vv .node rect,#mermaid-svg-UeYwODLEIthql0vv .node circle,#mermaid-svg-UeYwODLEIthql0vv .node ellipse,#mermaid-svg-UeYwODLEIthql0vv .node polygon,#mermaid-svg-UeYwODLEIthql0vv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UeYwODLEIthql0vv .rough-node .label text,#mermaid-svg-UeYwODLEIthql0vv .node .label text,#mermaid-svg-UeYwODLEIthql0vv .image-shape .label,#mermaid-svg-UeYwODLEIthql0vv .icon-shape .label{text-anchor:middle;}#mermaid-svg-UeYwODLEIthql0vv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UeYwODLEIthql0vv .rough-node .label,#mermaid-svg-UeYwODLEIthql0vv .node .label,#mermaid-svg-UeYwODLEIthql0vv .image-shape .label,#mermaid-svg-UeYwODLEIthql0vv .icon-shape .label{text-align:center;}#mermaid-svg-UeYwODLEIthql0vv .node.clickable{cursor:pointer;}#mermaid-svg-UeYwODLEIthql0vv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UeYwODLEIthql0vv .arrowheadPath{fill:#333333;}#mermaid-svg-UeYwODLEIthql0vv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UeYwODLEIthql0vv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UeYwODLEIthql0vv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UeYwODLEIthql0vv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UeYwODLEIthql0vv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UeYwODLEIthql0vv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UeYwODLEIthql0vv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UeYwODLEIthql0vv .cluster text{fill:#333;}#mermaid-svg-UeYwODLEIthql0vv .cluster span{color:#333;}#mermaid-svg-UeYwODLEIthql0vv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UeYwODLEIthql0vv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UeYwODLEIthql0vv rect.text{fill:none;stroke-width:0;}#mermaid-svg-UeYwODLEIthql0vv .icon-shape,#mermaid-svg-UeYwODLEIthql0vv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UeYwODLEIthql0vv .icon-shape p,#mermaid-svg-UeYwODLEIthql0vv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UeYwODLEIthql0vv .icon-shape .label rect,#mermaid-svg-UeYwODLEIthql0vv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UeYwODLEIthql0vv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UeYwODLEIthql0vv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UeYwODLEIthql0vv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} gdi32: LPtoDP
GreLPtoDP

dcutil.c
IntLPtoDP

coord.h inline
DC_vUpdateWorldToDevice

coord.c: XFORMOBJ_iCombine
INTERNAL_LPTODP
INTERNAL_APPLY_MATRIX

FLOATOBJ 矩阵乘

mxWorldToDevice
gdi32: DPtoLP
GreDPtoLP

dcutil.c
IntDPtoLP

coord.h inline
DC_vUpdateDeviceToWorld

coord.c: XFORMOBJ_iInverse
INTERNAL_DPTOLP
INTERNAL_APPLY_MATRIX

mxDeviceToWorld

5.3 边界矩形

#mermaid-svg-FFCvWbbr1BpFnXZC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FFCvWbbr1BpFnXZC .error-icon{fill:#552222;}#mermaid-svg-FFCvWbbr1BpFnXZC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FFCvWbbr1BpFnXZC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FFCvWbbr1BpFnXZC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FFCvWbbr1BpFnXZC .marker.cross{stroke:#333333;}#mermaid-svg-FFCvWbbr1BpFnXZC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FFCvWbbr1BpFnXZC p{margin:0;}#mermaid-svg-FFCvWbbr1BpFnXZC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC .cluster-label text{fill:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC .cluster-label span{color:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC .cluster-label span p{background-color:transparent;}#mermaid-svg-FFCvWbbr1BpFnXZC .label text,#mermaid-svg-FFCvWbbr1BpFnXZC span{fill:#333;color:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC .node rect,#mermaid-svg-FFCvWbbr1BpFnXZC .node circle,#mermaid-svg-FFCvWbbr1BpFnXZC .node ellipse,#mermaid-svg-FFCvWbbr1BpFnXZC .node polygon,#mermaid-svg-FFCvWbbr1BpFnXZC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FFCvWbbr1BpFnXZC .rough-node .label text,#mermaid-svg-FFCvWbbr1BpFnXZC .node .label text,#mermaid-svg-FFCvWbbr1BpFnXZC .image-shape .label,#mermaid-svg-FFCvWbbr1BpFnXZC .icon-shape .label{text-anchor:middle;}#mermaid-svg-FFCvWbbr1BpFnXZC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FFCvWbbr1BpFnXZC .rough-node .label,#mermaid-svg-FFCvWbbr1BpFnXZC .node .label,#mermaid-svg-FFCvWbbr1BpFnXZC .image-shape .label,#mermaid-svg-FFCvWbbr1BpFnXZC .icon-shape .label{text-align:center;}#mermaid-svg-FFCvWbbr1BpFnXZC .node.clickable{cursor:pointer;}#mermaid-svg-FFCvWbbr1BpFnXZC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FFCvWbbr1BpFnXZC .arrowheadPath{fill:#333333;}#mermaid-svg-FFCvWbbr1BpFnXZC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FFCvWbbr1BpFnXZC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FFCvWbbr1BpFnXZC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FFCvWbbr1BpFnXZC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FFCvWbbr1BpFnXZC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FFCvWbbr1BpFnXZC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FFCvWbbr1BpFnXZC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FFCvWbbr1BpFnXZC .cluster text{fill:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC .cluster span{color:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FFCvWbbr1BpFnXZC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FFCvWbbr1BpFnXZC rect.text{fill:none;stroke-width:0;}#mermaid-svg-FFCvWbbr1BpFnXZC .icon-shape,#mermaid-svg-FFCvWbbr1BpFnXZC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FFCvWbbr1BpFnXZC .icon-shape p,#mermaid-svg-FFCvWbbr1BpFnXZC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FFCvWbbr1BpFnXZC .icon-shape .label rect,#mermaid-svg-FFCvWbbr1BpFnXZC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FFCvWbbr1BpFnXZC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FFCvWbbr1BpFnXZC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FFCvWbbr1BpFnXZC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 查询
累计
设置
gdi32: SetBoundsRect
NtGdiSetBoundsRect

dcutil.c
DCB_ENABLE/DISABLE → fs 位

DC_ACCUM_APP / DC_ACCUM_WMGR
DCB_ACCUMULATE → IntLPtoDP + RECTL_bUnionRect
绘制: LineTo/SetPixel/BitBlt
IntUpdateBoundsRect

dcutil.c
RECTL_bUnionRect

rect.c
erclBoundsApp / erclBounds
gdi32: GetBoundsRect
NtGdiGetBoundsRect

dcutil.c
CLIPPING_UpdateGCRegion 若 DIRTY_RAO
与 Rao/Vis 包围盒相交
IntDPtoLP 转逻辑坐标
RTL 交换左右边界
DCB_RESET 复位累计

5.4 SetPixel(跨文件协作)

#mermaid-svg-s4ABg01a9cBlzSjR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s4ABg01a9cBlzSjR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s4ABg01a9cBlzSjR .error-icon{fill:#552222;}#mermaid-svg-s4ABg01a9cBlzSjR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s4ABg01a9cBlzSjR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s4ABg01a9cBlzSjR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s4ABg01a9cBlzSjR .marker.cross{stroke:#333333;}#mermaid-svg-s4ABg01a9cBlzSjR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s4ABg01a9cBlzSjR p{margin:0;}#mermaid-svg-s4ABg01a9cBlzSjR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-s4ABg01a9cBlzSjR .cluster-label text{fill:#333;}#mermaid-svg-s4ABg01a9cBlzSjR .cluster-label span{color:#333;}#mermaid-svg-s4ABg01a9cBlzSjR .cluster-label span p{background-color:transparent;}#mermaid-svg-s4ABg01a9cBlzSjR .label text,#mermaid-svg-s4ABg01a9cBlzSjR span{fill:#333;color:#333;}#mermaid-svg-s4ABg01a9cBlzSjR .node rect,#mermaid-svg-s4ABg01a9cBlzSjR .node circle,#mermaid-svg-s4ABg01a9cBlzSjR .node ellipse,#mermaid-svg-s4ABg01a9cBlzSjR .node polygon,#mermaid-svg-s4ABg01a9cBlzSjR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-s4ABg01a9cBlzSjR .rough-node .label text,#mermaid-svg-s4ABg01a9cBlzSjR .node .label text,#mermaid-svg-s4ABg01a9cBlzSjR .image-shape .label,#mermaid-svg-s4ABg01a9cBlzSjR .icon-shape .label{text-anchor:middle;}#mermaid-svg-s4ABg01a9cBlzSjR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-s4ABg01a9cBlzSjR .rough-node .label,#mermaid-svg-s4ABg01a9cBlzSjR .node .label,#mermaid-svg-s4ABg01a9cBlzSjR .image-shape .label,#mermaid-svg-s4ABg01a9cBlzSjR .icon-shape .label{text-align:center;}#mermaid-svg-s4ABg01a9cBlzSjR .node.clickable{cursor:pointer;}#mermaid-svg-s4ABg01a9cBlzSjR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-s4ABg01a9cBlzSjR .arrowheadPath{fill:#333333;}#mermaid-svg-s4ABg01a9cBlzSjR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-s4ABg01a9cBlzSjR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-s4ABg01a9cBlzSjR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s4ABg01a9cBlzSjR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-s4ABg01a9cBlzSjR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s4ABg01a9cBlzSjR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-s4ABg01a9cBlzSjR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-s4ABg01a9cBlzSjR .cluster text{fill:#333;}#mermaid-svg-s4ABg01a9cBlzSjR .cluster span{color:#333;}#mermaid-svg-s4ABg01a9cBlzSjR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-s4ABg01a9cBlzSjR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-s4ABg01a9cBlzSjR rect.text{fill:none;stroke-width:0;}#mermaid-svg-s4ABg01a9cBlzSjR .icon-shape,#mermaid-svg-s4ABg01a9cBlzSjR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s4ABg01a9cBlzSjR .icon-shape p,#mermaid-svg-s4ABg01a9cBlzSjR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-s4ABg01a9cBlzSjR .icon-shape .label rect,#mermaid-svg-s4ABg01a9cBlzSjR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s4ABg01a9cBlzSjR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-s4ABg01a9cBlzSjR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-s4ABg01a9cBlzSjR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

gdi32: SetPixel
NtGdiSetPixel

bitblt.c
fs & ACCUM?
IntLPtoDP + IntUpdateBoundsRect

dcutil.c
TranslateCOLORREF

dcutil.c
EXLATEOBJ_vInitialize

eng/xlateobj.c
XLATEOBJ_iXlate

gpalRGB → 表面调色板
借 eboText 改纯色
IntPatBlt 1×1 PATCOPY

bitblt.c
反向 XLATEOBJ

表面 → gpalRGB
返回新 RGB 或 CLR_INVALID

5.5 可见区失效通知(ntuser → ntgdi)

渲染错误: Mermaid 渲染失败: Parse error on line 3: ... B --> Cdc-\>fs \|= DC_DIRTY_RAO ----------------------^ Expecting 'SQE', 'TAGEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PIPE'


6. 与《分析_31》及分册关系

说明
《分析_31》4.2 表(dcutil.c 函数清单) 本分析对其中各函数逐一展开
《分析_39》(dcobjs.c 画刷更新) 脏标记的消费者:DC_vUpdateTextBrush/LineBrush/FillBrush/BackgroundBrush;本文件置位,对方清位
《分析_34》(coord.c/coord.h 坐标) IntLPtoDP/IntDPtoLP、XFORM 矩阵惰性更新、IntGdiSetMapMode
《分析_19》(EXLATEOBJ) TranslateCOLORREFNtGdiSetPixel 双向颜色转换的核心机制
《分析_17》(MIX/ROP) jBkMode/jROP2 的下游消费者------背景模式决定 MIX 背景 ROP
本文件(dcutil.c) 属性读写、坐标入口、边界矩形、通用 DC 查询、颜色翻译
cliprgn.c CLIPPING_UpdateGCRegion------NtGdiGetBoundsRect 可见区重建的联动
bitblt.c NtGdiSetPixel/NtGdiGetPixel/IntPatBlt------TranslateCOLORREF/IntUpdateBoundsRect 的调用方
ntuser/windc.c IntGdiSetHookFlags 的唯一调用方(DCE 可见区管理)

7. 源码索引

文件 关键内容
dcutil.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcutil.c) GreDPtoLP/GreLPtoDP/GreGetBkMode/GreGetBkColor/GreGetMapMode/GreGetTextColor/IntGdiSetBkColor/IntGdiSetBkMode/IntGdiSetTextAlign/IntGdiSetTextColor/IntSetDCBrushColor/IntSetDCPenColor/GreSetBrushOrg/GreSetStretchBltMode/GreGetGraphicsMode/DCU_SetDcUndeletable/IntSetDefaultRegion/NtGdiCancelDC/IntGdiSetHookFlags/NtGdiGetDCDword/NtGdiGetAndSetDCDword/IntUpdateBoundsRect/NtGdiGetBoundsRect/NtGdiSetBoundsRect/TranslateCOLORREF(共 25 个活动函数 + 1 个 #if 0 禁用的 IntIsPrimarySurface)
dc.h(file:///d:/reactos/win32ss/gdi/ntgdi/dc.h) _DC/_DCLEVEL 结构、_DCFLAGS(DC_ACCUM_APP/WMGR、DC_DIRTY_RAO、DC_PERMANANT、DC_EPSPRINTINGESCAPE)、DC_LockDc/DC_UnlockDcDC_vSetBrushOriginDC_vUpdate*Brush 声明
coord.h(file:///d:/reactos/win32ss/gdi/ntgdi/coord.h) INTERNAL_APPLY_MATRIX/INTERNAL_LPTODP/INTERNAL_DPTOLP/IntLPtoDP/IntDPtoLPDC_pmxWorldToDevice/DC_pmxDeviceToWorld 惰性更新、MIN_COORD/MAX_COORD
coord.c(file:///d:/reactos/win32ss/gdi/ntgdi/coord.c) IntGdiSetMapMode(826 行)、DC_vUpdateWorldToDevice/DC_vUpdateDeviceToWorldGreSetViewportOrgEx 等映射模式/变换实现
ntgdihdl.h(file:///d:/reactos/win32ss/include/ntgdihdl.h) _DC_ATTR 结构(291~351 行)、脏标记(122~145 行)、Xform 标志(173~188 行)
dcobjs.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcobjs.c) DC_vUpdateFillBrush/LineBrush/TextBrush/BackgroundBrush(16~138 行)、DC_vSetBrushOrigin(140~151 行)、NtGdiSelectBrush(255~280 行)、NtGdiSetBrushOrg(162~195 行)
rect.h(file:///d:/reactos/win32ss/gdi/ntgdi/rect.h) / rect.c(file:///d:/reactos/win32ss/gdi/ntgdi/rect.c) RECTL_bIsEmptyRect/RECTL_bUnionRect/RECTL_vMakeWellOrdered 等矩形运算
bitblt.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c) NtGdiSetPixel(1463 行)/NtGdiGetPixel(1551 行)------TranslateCOLORREF/IntUpdateBoundsRect 的调用方
xlateobj.h(file:///d:/reactos/win32ss/gdi/eng/xlateobj.h) EXLATEOBJ_vInitialize/EXLATEOBJ_vCleanup/XLATEOBJ_iXlate
palette.h(file:///d:/reactos/win32ss/gdi/ntgdi/palette.h) gpalRGB 等全局调色板声明(PALETTE gpalRGB, gpalBGR, ...
dce.h(file:///d:/reactos/win32ss/user/ntuser/dce.h) DCHF_INVALIDATEVISRGN(0x1)/DCHF_VALIDATEVISRGN(0x2)
windc.c(file:///d:/reactos/win32ss/user/ntuser/windc.c) IntGdiSetHookFlags 的调用点(187、610、931、1158 行)

函数覆盖统计 :本分析覆盖 dcutil.c 中全部 26 个函数(25 个活动函数 + 1 个被 #if 0 禁用的 IntIsPrimarySurface),以及与之直接协作的 coord.h/coord.c(坐标变换)、dcobjs.c(画刷重建)、rect.c(矩形运算)、bitblt.c(SetPixel/GetPixel)、cliprgn.c(可见区重建)、xlateobj(颜色转换)等关联实现。

关联模块: dcobjs.c(画刷更新,《分析_39》)、coord.c/coord.h(坐标变换,《分析_34》)、EXLATEOBJ(《分析_19》)、MIX/ROP(《分析_17》)、cliprgn.c(Rao/可见区)、bitblt.c(SetPixel/GetPixel)、ntuser/windc.c(DCE 可见区钩子)


本文档基于 ReactOS 源代码 win32ss/gdi/ntgdi/dcutil.c(963 行)及关联模块(dc.h、coord.h、dcobjs.c、rect.c、bitblt.c、ntgdihdl.h 等)逐函数分析(2026 年 8 月)。文中行号为源码当前版本行号,函数行为描述以源码为准。

相关推荐
BreezeJuvenile1 小时前
嵌入式C语言常见考察点(2)
c语言·嵌入式
xixiaoyunya1 小时前
从零搭建跨域互联网络:个人与小团队的轻量组网方案技术解析
开发语言·智能路由器·php
资源大佬星 课it1 小时前
黑马 Java+AI新版V16零基础就业班视频课程百度网盘下载
java·开发语言·人工智能
zmzb01031 小时前
Python课后习题训练记录Day193
开发语言·python
郝学胜-神的一滴1 小时前
[简化版 GAMES 104] 现代游戏引擎 06:从Tick时序到邮局模型,拆解确定性世界的底层密码
开发语言·c++·游戏引擎·图形渲染·软件开发·opengl
二十雨辰1 小时前
[Java]-微服务面试题
java·开发语言·微服务
不会代码的小猴1 小时前
6. Qt网络编程
开发语言·c++·笔记·qt·算法
caimouse2 小时前
ReactOS 图形系统分析(50):画笔子系统 — pen.c
c语言·开发语言·stm32
caimouse2 小时前
ReactOS 图形系统分析(48):弧线绘制 — arc.c
c语言·开发语言