ReactOS 图形系统分析(36):位块传输 — bitblt.c

ReactOS 图形系统分析(36):位块传输 --- bitblt.c

1. 概述

bitblt.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c) 位于 win32ss/gdi/ntgdi/,是内核 GDI(ntgdi/)的位块传输(BitBlt)子系统 。文件共 1655 行,实现 13 个 NtGdi* 系统调用入口 ,是引擎 BitBlt 管线(《分析_4》3.1)的 ntgdi 侧发起者 ------负责 DC 锁定与解锁、源/目标表面定位、逻辑坐标到设备坐标换算、裁剪区域的准备、颜色转换对象(EXLATEOBJ)的构建,以及 ROP/画刷参数的装配,最终把统一格式的请求转发给 IntEng* 系列引擎函数。

复制代码
应用: BitBlt / StretchBlt / TransparentBlt / AlphaBlend / MaskBlt / PlgBlt /
      PatBlt / PolyPatBlt / FillRgn / PaintRgn / FrameRgn / InvertRgn /
      SetPixel / GetPixel
    ↓ gdi32.dll → win32u.dll(NtGdi* 系统调用)
bitblt.c(本分析对象)
    ├─ GDIOBJ_bLockMultipleObjects / DC_LockDc:按序锁定 1~3 个 DC
    ├─ IntLPtoDP + ptlDCOrig:逻辑坐标 → 设备坐标(含负尺寸归一化)
    ├─ DC_vPrepareDCsForBlit:更新画刷、按 hsemDevLock 序加锁、鼠标安全
    ├─ 裁剪合成(prgnClip = Vis∩Rao + API 裁剪 + DC 原点,见《分析_17/31》)
    ├─ EXLATEOBJ_vInitXlateFromDCs(Ex):源调色板 → 目标调色板颜色转换
    └─ IntEngAlphaBlend / IntEngTransparentBlt / IntEngStretchBlt /
       IntEngPaint / IntEngBitBlt / EngCopyBits
            ↓
       gdi/eng/(DDI 分发 + 软件渲染,见《分析_4》)
            ↓
       gdi/dib/ + gdi/diblib/(像素实现,见《分析_29/30》)

本文件把"应用要画什么 "翻译成"引擎怎么画 ",本身几乎不做像素级工作(仅 NtGdiGetPixel 借助 EngCopyBits 完成一次 1×1 拷贝)。全部 13 个入口最终都汇聚到 5 个引擎函数上,体现了 ntgdi 层"薄壳 + 装配"的设计思想。

1.1 入口一览

函数 API 关键逻辑 引擎对接
NtGdiBitBlt BitBlt CAPTUREBLT → StretchBlt;否则转 MaskBlt(无掩码) IntEngBitBlt(《分析_4》3.1)
NtGdiStretchBlt StretchBlt 转发 GreStretchBltMask(无掩码) IntEngStretchBlt(《分析_27》)
NtGdiTransparentBlt TransparentBlt 透明色先经源调色板转换 IntEngTransparentBlt(《分析_27》)
NtGdiAlphaBlend AlphaBlend 负尺寸/空句柄参数校验 IntEngAlphaBlend(《分析_26》)
NtGdiMaskBlt MaskBlt 掩码位图须 1BPP;ROP4 分解 IntEngBitBlt(带掩码参数)
NtGdiPlgBlt PlgBlt 未实现(FIXME 桩) ---
NtGdiPatBlt PatBlt 无源图案填充;ROP4 禁止源 IntPatBltIntEngBitBlt
NtGdiPolyPatBlt PolyPatBlt 用户缓冲区探测 + 逐矩形填充 IntGdiPolyPatBlt
NtGdiFillRgn FillRgn 锁 DC+区域+画刷 IntGdiFillRgn(《分析_17》)
NtGdiFrameRgn FrameRgn GreCreateFrameRgn 生成边框 NtGdiFillRgn
NtGdiInvertRgn InvertRgn ROP4_DSTINVERT 反色区域 IntGdiBitBltRgn
NtGdiSetPixel SetPixel 文本画刷临时换色 + 1×1 PatBlt IntPatBlt
NtGdiGetPixel GetPixel 1×1 EngCopyBits + 颜色转回 EngCopyBits(eng/copybits.c)

2. 设计动机:为什么有这么多变体

2.1 位块传输的本质

位块传输(Bit Block Transfer)把"源表面一块矩形区域"按光栅操作(ROP)与"目标表面一块矩形区域"合并,必要时再混入 图案(Pattern)掩码(Mask)。Windows GDI 把这一操作抽象为四个操作数的布尔代数问题:

  • 源 S(Source):源 DC 上的位图区域;
  • 目标 D(Dest):目标 DC 上将被覆盖的区域(也参与运算);
  • 图案 P(Pattern):画刷(纯色或图案位图);
  • 掩码 M(Mask):1BPP(或 8BPP 灰度)位图,逐位/逐字节决定使用"前景 ROP"还是"背景 ROP"。

ROP3 是三元布尔函数 f(S, P, D),共 256 种(索引 0x00~0xFF,其中 0xCC 为 SRCCOPY、0xAA 为 PATCOPY、0x55 为 DSTINVERT 等)。ROP4 把前景/背景各用一个 ROP3 编码为 (背景 << 8) | 前景,共 65536 种;掩码位为 1 用前景,为 0 用背景。引擎中的 ROP4_USES_SOURCE / ROP4_USES_PATTERN / ROP4_USES_DEST / ROP4_USES_MASK 宏(eng/inteng.h:44-47)通过比较 ROP4 高低字节的位模式,静态判定某次操作是否"用得上"某个操作数,从而裁剪掉无用的表面访问。

2.2 统一管线:一切皆 BitBlt

bitblt.c 的核心设计动机是:把形态各异的绘制请求全部归约到少数几个引擎原语上。

复制代码
PatBlt / PolyPatBlt / FillRgn / PaintRgn / FrameRgn / InvertRgn / SetPixel
    └─ 无源操作 → IntEngBitBlt(pso, NULL, NULL, ...) 或 IntEngPaint
BitBlt / MaskBlt / TransparentBlt / AlphaBlend / StretchBlt
    └─ 有源操作 → IntEngBitBlt / IntEngStretchBlt / IntEngAlphaBlend / ...

这样引擎只需维护一套裁剪(CLIPOBJ)、颜色转换(XLATEOBJ)、画刷(BRUSHOBJ)与 ROP 求值管线,硬件驱动也只需实现 DrvBitBlt 等少数回调。区域填充 (FillRgn 等)甚至不走专用填充函数,而是构造一个"纯色画刷 + MIX"交给 IntEngPaint(《分析_17》),后者再转换成一次无源的 BitBlt------这就是《分析_17》所称"区域填充完整链路"的上游。

2.3 三层职责划分

文件 职责
用户态 gdi32.dll / win32u.dll API 包装、参数打包、NtGdi* 调用
内核 GDI ntgdi/bitblt.c 句柄→对象、锁 DC、坐标变换、裁剪准备、EXLATEOBJ、ROP4 装配
引擎 gdi/eng/bitblt.c 等 裁剪枚举、驱动挂钩(Drv*)决策、软件回退
像素 gdi/dib/、gdi/diblib/ DIB 引擎逐行/逐块写像素

3. 公共设施:bitblt.c 的"三板斧"

3.1 坐标变换套路:IntLPtoDP + ptlDCOrig

PatBlt 外的所有入口都遵循同一套坐标换算(以 NtGdiAlphaBlend 70-95 行为模板):

c 复制代码
DestRect.left = XOriginDest;                    /* 逻辑坐标 */
DestRect.right = XOriginDest + WidthDest;
IntLPtoDP(DCDest, (LPPOINT)&DestRect, 2);       /* 页面→设备变换 */
DestRect.left += DCDest->ptlDCOrig.x;           /* 加上 DC 原点偏移 */
DestRect.right += DCDest->ptlDCOrig.x;
  • IntLPtoDP 施加世界变换(DC_pmxWorldToDevice)与页面变换(视口/窗口映射),见 ntgdi/coord.c;
  • ptlDCOrig 是 DC 原点(窗口 DC 的客户区偏移),区域与表面坐标都以设备坐标为准,所以矩形必须平移 DC 原点才落到表面像素坐标;
  • 矩形的 right/bottom 是开区间(半开半闭),面积 = (right-left) × (bottom-top)

3.2 负尺寸与翻转(RTL / 镜像)

Windows 允许 Width/Height 为负,表示翻转的拷贝(镜像、上下颠倒)。bitblt.c 在换算后用两种手段处理:

  1. 归一化RECTL_vMakeWellOrdered(&DestRect)(bitblt.c:525、930)把 left>righttop>bottom 的"反向矩形"交换端点,恢复 left≤right 且 top≤bottom 的不变式;
  2. 源点补偿NtGdiMaskBlt 中(bitblt.c:510-522):
c 复制代码
/* Fix BitBlt so that it will not flip left to right */
if ((DestRect.left > DestRect.right) && (nWidth < 0))
{
    SourcePoint.x += nWidth;
    nWidth = -nWidth;
}

负宽度意味着源矩形应从 XSrc + nWidth 处向右读,而不是从 XSrc 向左读。补偿后把源点平移到等效位置、宽度取绝对值,交给引擎时就不会意外产生镜像。真正的镜像/翻转由引擎层(EngBitBlt 中的 bLeftToRight/bTopToBottom 标志)根据未归一化的原始矩形方向还原(eng/bitblt.c:365-381、783-795)。

3.3 累积边界:IntUpdateBoundsRect

凡目标 DC 带有 DC_ACCUM_APPDC_ACCUM_WMGR 标志(NtGdiBeginPath/BeginDeferWindowPos 累积矩形边界,见 dcutil.c:862-875 附近的 NtGdiSetBoundsRect),绘制前都要调用 IntUpdateBoundsRect(pdc, &rc)(dcutil.c:694),把本次绘制的设备矩形并入 DC 的累积边界矩形(erclBounds),供后续 NtGdiGetBoundsRect 查询。bitblt.c 的 13 个入口中,除 NtGdiPlgBlt(桩)外几乎全部做了这一检查。

3.4 DC 准备:DC_vPrepareDCsForBlit / DC_vFinishBlit

这两个函数在 dclife.c(file:///d:/reactos/win32ss/gdi/ntgdi/dclife.c)(500-636 行),是所有 blit 类操作必须成对调用的包裹:

DC_vPrepareDCsForBlit(pdcDest, rcDest, pdcSrc, rcSrc)(dclife.c:503-609):

  1. 刷新画刷 :若 ulDirty_DIRTY_FILL|DC_BRUSH_DIRTYDC_vUpdateFillBrush;含 DIRTY_LINE|DC_PEN_DIRTYDC_vUpdateLineBrush;含 DIRTY_TEXTDC_vUpdateTextBrush(dclife.c:515-520)。DC_vUpdateFillBrush(dcobjs.c:15-57)核对句柄 pdcattr->hbrushdclevel.pbrFill 是否一致、必要时重锁新画刷、EBRUSHOBJ_vUpdateFromDC 重建引擎画刷、对 DC_BRUSH 用 crBrushClr 刷新纯色;
  2. 按序加锁 :源/目标 DC 可能属于不同 PDEV。为避免死锁,按 hsemDevLock(设备锁信号量句柄)的数值排序,先锁大者(dclife.c:525-539),保证两个 PDEV 的锁总是以相同顺序获取;
  3. 直写 DC(DCTYPE_DIRECT)更新表面 :若 ppdev->pSurface != dclevel.pSurface(显示模式切换/表面重建后),调 DC_vUpdateDC 刷新表面指针与尺寸(dclife.c:558-561、589-592);
  4. 鼠标安全 :对直写 DC 调 MouseSafetyOnDrawStart(ppdev, rc)(dclife.c:569-573),在硬件鼠标(光标)所在区域即将被覆盖时隐藏光标,见《分析_24》;
  5. 调试构建置 DC_PREPARED 标志。

DC_vFinishBlit(pdc1, pdc2) (dclife.c:612-636):对称地调用 MouseSafetyOnDrawEnd + EngReleaseSemaphore 释放设备锁,清除 DC_PREPARED若中途提前返回(异常路径),也必须先 finish 再解锁 DC ------bitblt.c 大量使用 goto done/failed/leave 保证成对性。

注意:DC_vPrepareDCsForBlitDC_vFinishBlit 管理的是 PDEV 设备锁GDIOBJ_vUnlockObject/DC_UnlockDc 管理的是 DC 对象引用锁。两者正交,必须分别成对。

3.5 颜色转换:EXLATEOBJ

源表面与目标表面的像素格式可能不同(如 8BPP 调色板源 → 32BPP 目标),需要逐像素颜色转换 。ntgdi 用 EXLATEOBJ(eng/xlateobj.c,详见《分析_19》)封装:

  • EXLATEOBJ_vInitXlateFromDCs(&exlo, pdcSrc, pdcDst)(xlateobj.c:824-845):以源表面调色板(无表面用 gppalMono)、目标表面调色板、源/目标的背景色与目标前景色初始化,并记录目标 DC 调色板 ppalDstDc
  • EXLATEOBJ_vInitXlateFromDCsEx(..., crBackColor)(xlateobj.c:847-874):多一个可指定的背景色(crBackColor == CLR_INVALID 时回退到源 DC 背景色),NtGdiMaskBlt/GreStretchBltMask 用它处理背景色参与 ROP 的转换;
  • EXLATEOBJ_vCleanup(xlateobj.c:892-902):若转换表是动态分配的则释放,复位为内嵌的 aulXlate

引擎侧通过 XLATEOBJ_iXlate(pxlo, color) 逐像素调用(xlateobj.c:906+)。bitblt.c 中 EXLATEOBJ 的生命周期严格包裹在 blit 调用前后(见第 4 节各函数)。

3.6 多对象锁定:GDIOBJ_bLockMultipleObjects

NtGdiAlphaBlend/NtGdiTransparentBlt/NtGdiMaskBlt/GreStretchBltMaskGDIOBJ_bLockMultipleObjects(2 或 3, ahDC, apObj, GDIObjType_DC_TYPE) 一次锁定多个 DC,防止句柄失效。注意掩码相关函数把不用的槽位填 NULL(如 ahDC[1] = UsesSource ? hdcSrc : NULL),锁定时跳过空槽。锁定失败返回 ERROR_INVALID_HANDLE

3.7 调用机制:从 gdi32.dll 到 NtGdi*

BitBlt 为例,用户态调用链为(详见《分析_31》2.1):

复制代码
gdi32.dll: BitBlt(hdc, x, y, cx, cy, hdcSrc, x1, y1, rop)
    └─ (内联/转发) win32u.dll: NtGdiBitBlt(...)          /* 系统调用指令 */
         └─ KiSystemService → win32k.sys w32ksvc 服务表
              └─ NtGdiBitBlt(本文件,bitblt.c:149)

要点:

  • 参数打包BLENDFUNCTIONROP4 等小结构体按值传递;PATRECT 等大结构体传指针,内核侧必须 ProbeForRead + RtlCopyMemory(如 NtGdiPolyPatBlt);
  • 句柄有效性 :HDC/HRGN/HBRUSH/HBITMAP 都是内核句柄表项,用户态不可直接解引用,必须经 DC_LockDc/REGION_LockRgn/BRUSH_ShareLockBrush/SURFACE_ShareLockSurface 转换并加引用;
  • 返回约定NtGdiSetPixel/NtGdiGetPixel 返回 COLORREFCLR_INVALID = -1 表示失败),其余返回 BOOL;错误码经 EngSetLastError/SetLastNtError 写回用户态 GetLastError
  • 调试通道 :本文件第 10 行 DBG_DEFAULT_CHANNEL(GdiBlt)------开启 GdiBlt 调试通道后,串口会输出 Locking DCsPreparing DCs for blitPerforming the alpha blend 等 TRACE 以及 DestRect/SourcePoint 的坐标打印(见 9.1 节)。

4. NtGdi* 入口逐一展开

4.1 NtGdiAlphaBlend(bitblt.c:12-147)--- AlphaBlend

c 复制代码
BOOL NtGdiAlphaBlend(HDC hDCDest, LONG XOriginDest, LONG YOriginDest,
    LONG WidthDest, LONG HeightDest, HDC hDCSrc, LONG XOriginSrc, LONG YOriginSrc,
    LONG WidthSrc, LONG HeightSrc, BLENDFUNCTION BlendFunc, HANDLE hcmXform)

作用 :带 Alpha 通道(或源/目标常量 Alpha)的合成拷贝,即 Windows AlphaBlend

实现流程

  1. BlendFunc 原样装入 BlendObj.BlendFunctionhcmXform 是颜色矩阵句柄,本实现未使用);
  2. 参数校验 :任一宽/高为负 → ERROR_INVALID_PARAMETER 返回 FALSE;任一 DC 句柄为 NULL → 同样失败;
  3. 锁 DCGDIOBJ_bLockMultipleObjects(2, ...),失败 → ERROR_INVALID_HANDLE
  4. 信息 DC 短路dctype == DCTYPE_INFO → 直接解锁并返回 TRUE(注释:"Yes, Windows really returns TRUE in this case"------信息 DC 没有表面,但 Win32 语义上 AlphaBlend 到信息 DC 是"成功"的);
  5. 坐标变换 :目标/源矩形 IntLPtoDP + ptlDCOrig(见 3.1);
  6. 零尺寸短路:若目标或源矩形的 right/bottom 为 0(退化矩形)→ 返回 TRUE;
  7. IntUpdateBoundsRect(累积边界);
  8. DC_vPrepareDCsForBlit(DCDest, &DestRect, DCSrc, &SourceRect)
  9. dclevel.pSurface,任一为 NULL → 失败跳 leave
  10. EXLATEOBJ_vInitXlateFromDCs(&exlo, DCSrc, DCDest)
  11. IntEngAlphaBlend(&BitmapDest->SurfObj, &BitmapSrc->SurfObj, (CLIPOBJ*)&DCDest->co, &exlo.xlo, &DestRect, &SourceRect, &BlendObj)------裁剪直接用 DC 的合成裁剪对象 DCDest->co(已在 DC 状态机中维护,见《分析_31》裁剪合成);
  12. 清理:EXLATEOBJ_vCleanupDC_vFinishBlit → 解锁两个 DC。

注意事项

  • 引擎侧 IntEngAlphaBlend(eng/alphablend.c,详见《分析_26》)支持源常量 Alpha、每像素 Alpha、目标 Alpha 三种模式的组合;
  • 源与目标不要求同格式 ,颜色转换交给 exlo
  • 与 BitBlt 不同,AlphaBlend 的结果与 ROP 无关(永远是合成覆盖),因此不需要 ROP4。

4.2 NtGdiBitBlt(bitblt.c:149-197)--- BitBlt

c 复制代码
BOOL NtGdiBitBlt(HDC hDCDest, INT XDest, INT YDest, INT Width, INT Height,
    HDC hDCSrc, INT XSrc, INT YSrc, DWORD dwRop, DWORD crBackColor, FLONG fl)

作用 :最经典的位块传输。本身不干活,只做路由分派:

  1. dwRop & CAPTUREBLT(0x40000000,捕获被遮挡窗口内容)→ 直接转发 NtGdiStretchBlt(等宽高等),因为 CAPTUREBLT 需要走带窗口内容合成的路径;
  2. 否则清除 NOMIRRORBITMAP|CAPTUREBLT 标志;
  3. 转发 NtGdiMaskBlt(hDCDest, ..., NULL, 0, 0, MAKEROP4(dwRop, dwRop), crBackColor)------无掩码位图、源点(0,0),前景=背景=dwRop 的 ROP4。注释调侃:"TODO: What's fl for? LOL not to send this to MaskBit!"fl 参数未使用)。

设计要点 :BitBlt 的 256 种 ROP3 天然是 MaskBlt 的 ROP4 特例(前景=背景),复用 MaskBlt 的完整管线(源点补偿、负尺寸归一化、EXLATEOBJ 带背景色)零成本获得全部 BitBlt 语义。crBackColor 在单色→彩色转换时作为背景色参与 XLATE。

4.3 NtGdiTransparentBlt(bitblt.c:199-309)--- TransparentBlt

c 复制代码
BOOL NtGdiTransparentBlt(HDC hdcDst, INT xDst, INT yDst, INT cxDst, INT cyDst,
    HDC hdcSrc, INT xSrc, INT ySrc, INT cxSrc, INT cySrc, COLORREF TransColor)

作用 :把源中等于 TransColor 的像素"抠掉"(不写目标),其余像素按 1:1 拷贝。

实现流程

  1. NULL 句柄校验 → ERROR_INVALID_PARAMETER;锁 2 DC;DCTYPE_INFO → TRUE;
  2. 目标/源矩形 IntLPtoDP + ptlDCOrigIntUpdateBoundsRect
  3. DC_vPrepareDCsForBlit;取源/目标表面;
  4. 透明色转换 (bitblt.c:290-293)------这是本函数与 MaskBlt 的关键区别:先把 TransColor 这个 RGB 值 转换到源表面调色板的索引:
c 复制代码
EXLATEOBJ_vInitialize(&exlo, &gpalRGB, BitmapSrc->ppal, 0, 0, 0);
TransparentColor = XLATEOBJ_iXlate(&exlo.xlo, (ULONG)TransColor);

gpalRGB → 源调色板 的正向转换,得到"源中哪个索引算透明";

  1. 再建 EXLATEOBJ_vInitXlateFromDCs(源调色板 → 目标调色板)用于普通像素拷贝;

  2. IntEngTransparentBlt(&BitmapDest->SurfObj, &BitmapSrc->SurfObj, (CLIPOBJ*)&DCDest->co, &exlo.xlo, &rcDest, &rcSrc, TransparentColor, 0)

  3. 清理(done 标签处统一 DC_vFinishBlit + 解锁)。

注意事项 :透明比较发生在源像素空间(比较源索引是否等于 TransparentColor),因此正确性依赖第 4 步把 RGB 精确转换为源调色板索引(对于 32BPP 源即 RGB 值本身)。引擎侧实现见《分析_27》(eng/transblt.c)。

4.4 NtGdiMaskBlt(bitblt.c:311-555)--- MaskBlt

c 复制代码
BOOL NtGdiMaskBlt(HDC hdcDest, INT nXDest, INT nYDest, INT nWidth, INT nHeight,
    HDC hdcSrc, INT nXSrc, INT nYSrc, HBITMAP hbmMask, INT xMask, INT yMask,
    DWORD dwRop4, DWORD crBackColor)

作用 :带 1BPP 掩码位图的三输入(源+图案+掩码)位块传输,是 BitBlt 的底层承载者。

实现流程

  1. rop4 = WIN32_ROP4_TO_ENG_ROP4(dwRop4)(= dwRop4 >> 16,intgdi.h:5------Win32 ROP4 高 16 位才是引擎 ROP4);
  2. UsesSource = ROP4_USES_SOURCE(rop4)UsesPattern = ROP4_USES_PATTERN(rop4);若用到源或图案但没有源 DC → 返回 FALSE;
  3. 掩码处理 (bitblt.c:354-377):若 ROP4_USES_MASK(rop4) 且给了 hbmMask
    • SURFACE_ShareLockSurface(hbmMask) 引用锁定位图;
    • 校验位图格式 gajBitsPerFormat[...] == 1(必须 1BPP),否则 ERROR_INVALID_HANDLE 并解锁;
    • 若 ROP 用掩码但没给位图 → 置 NULL,注释"the Eng function will take care of that and use the brushobject to get a mask"(引擎可用画刷的掩码面,见 5.1 节 eng/bitblt.c:507-517);
  4. 锁 DC:ahDC[1] = UsesSource ? hdcSrc : NULL;DCTYPE_INFO 短路(目标/源都查);
  5. 目标矩形变换 + 归一化(为边界计算);IntUpdateBoundsRect
  6. 源矩形推导:源只给起点 SourcePoint,尺寸取目标矩形尺寸(bitblt.c:469-472):
c 复制代码
SourceRect.right  = SourcePoint.x + DestRect.right - DestRect.left;
SourceRect.bottom = SourcePoint.y + DestRect.bottom - DestRect.top;
  1. DC_vPrepareDCsForBlit;若画刷脏 → DC_vUpdateFillBrush
  2. UsesSourceEXLATEOBJ_vInitXlateFromDCsEx(&exlo, DCSrc, DCDest, crBackColor)(背景色参与);
  3. 翻转补偿 (见 3.2)与 RECTL_vMakeWellOrdered
  4. IntEngBitBlt(&BitmapDest->SurfObj, BitmapSrc?&BitmapSrc->SurfObj:NULL, psurfMask?&psurfMask->SurfObj:NULL, (CLIPOBJ*)&DCDest->co, XlateObj, &DestRect, &SourcePoint, &MaskPoint, &DCDest->eboFill.BrushObject, &DCDest->dclevel.pbrFill->ptOrigin, rop4)------画刷原点取自填充画刷,供图案/掩码定位;
  5. 清理:解锁 EXLATE、DC_vFinishBlit、解锁 DC、SURFACE_ShareUnlockSurface;失败 → ERROR_INVALID_PARAMETER

与 BitBlt 的关系NtGdiBitBlt → 本函数(无掩码、前景=背景),见 4.2。

4.5 NtGdiPlgBlt(bitblt.c:557-574)--- PlgBlt

c 复制代码
BOOL NtGdiPlgBlt(HDC hdcTrg, LPPOINT pptlTrg, HDC hdcSrc, INT xSrc, INT ySrc,
    INT cxSrc, INT cySrc, HBITMAP hbmMask, INT xMask, INT yMask, DWORD crBackColor)

作用 :把源矩形平行投影到目标平行四边形 (由 pptlTrg 三个顶点定义),可实现旋转/斜切。当前未实现

c 复制代码
FIXME("NtGdiPlgBlt: unimplemented.\n");
return FALSE;

引擎层 EngPlgBlt 在 eng/stubs.c 中同样是桩(见《分析_28》)。当应用调用 PlgBlt 时,会直接返回 FALSE 并输出 FIXME 日志。

4.6 NtGdiStretchBlt(bitblt.c:838-871)与 GreStretchBltMask(bitblt.c:576-835)

4.6.1 NtGdiStretchBlt
c 复制代码
BOOL NtGdiStretchBlt(HDC hDCDest, INT XOriginDest, INT YOriginDest,
    INT WidthDest, INT HeightDest, HDC hDCSrc, INT XOriginSrc, INT YOriginSrc,
    INT WidthSrc, INT HeightSrc, DWORD dwRop3, DWORD dwBackColor)

作用 :缩放拷贝。实现为对 GreStretchBltMask 的薄封装:

  1. dwRop3 = dwRop3 & ~(NOMIRRORBITMAP|CAPTUREBLT)
  2. 转发 GreStretchBltMask(hDCDest, ..., MAKEROP4(dwRop3 & 0xFF0000, dwRop3), dwBackColor, NULL, 0, 0)------掩码 DC 为 NULL (无掩码),ROP4 前景取 dwRop3 & 0xFF0000(ROP3 索引位)、背景取整个 dwRop3。注意 StretchBlt 的 ROP3 位布局是 0x00CC0000 形式,dwRop3 & 0xFF0000 取出 ROP 索引供引擎 R3_OPINDEX_* 使用。
4.6.2 GreStretchBltMask(内部函数,非 NtGdi 入口)

作用 :带可选掩码的拉伸传输,是 StretchBlt/MaskBlt(缩放掩码) 的通用实现,同时被 NtGdiStretchBltNtGdiMaskBlt(CAPTUREBLT 时经 StretchBlt)间接使用。

签名GreStretchBltMask(HDC hDCDest, INT XOriginDest, INT YOriginDest, INT WidthDest, INT HeightDest, HDC hDCSrc, INT XOriginSrc, INT YOriginSrc, INT WidthSrc, INT HeightSrc, DWORD dwRop4, DWORD dwBackColor, HDC hDCMask, INT XOriginMask, INT YOriginMask)------注意掩码是 DC (而非 HBITMAP),因为它要支持把任意 DC 表面当掩码用,并参与 IntLPtoDP 坐标变换。

完整流程

  1. rop4 = WIN32_ROP4_TO_ENG_ROP4(dwRop4)UsesSource/UsesMask 判定;
  2. 零尺寸 :任一宽或高为 0 → ERROR_INVALID_PARAMETER 但返回 TRUE(Windows 语义:空操作算成功);
  3. 缺 DC:!hDCDest || (UsesSource && !hDCSrc) || (UsesMask && !hDCMask) → 失败;
  4. 锁 3 个 DCahDC[2] = UsesMask ? hDCMask : NULL
  5. DCTYPE_INFO 短路(目标、源分别查)→ TRUE;
  6. 负尺寸目标矩形 (bitblt.c:669-689):WidthDest<0left = XOriginDest + WidthDest; right = XOriginDest(先把逻辑矩形定义对,再进行 LPtoDP------与 MaskBlt 先变换后归一化不同,这里在变换前就修正方向);
  7. 目标:IntLPtoDP + ptlDCOrig + 归一化 + IntUpdateBoundsRect
  8. 源矩形 (bitblt.c:718-748):同样处理负尺寸;UsesSourceIntLPtoDP(DCSrc) + ptlDCOrig
  9. BrushOrigin = {0,0}DC_vPrepareDCsForBlit(DCDest, &DestRect, DCSrc, &SourceRect)------注释说明 "Only prepare Source and Dest, hdcMask represents a DIB"(掩码是独立 DIB,不需要设备锁/鼠标安全);
  10. 画刷脏 → DC_vUpdateFillBrush
  11. UsesSourceEXLATEOBJ_vInitXlateFromDCsEx(&exlo, DCSrc, DCDest, dwBackColor)
  12. BrushOrigin += DCDest->ptlDCOrig(画刷原点平移到设备坐标);
  13. 掩码表面检查 (bitblt.c:779-797):BitmapMask->SurfObj.sizlBitmap.cx < WidthSrc || .cy < HeightSrc → WARN "mask is smaller than bitmap" 并失败;掩码起点 MaskPoint = (XOriginMask, YOriginMask)IntLPtoDP(DCMask) + ptlDCOrig 变换;
  14. IntEngStretchBlt(&BitmapDest->SurfObj, BitmapSrc?..., BitmapMask?..., (CLIPOBJ*)&DCDest->co, XlateObj, &DCDest->dclevel.ca /* COLORADJUSTMENT */, &DestRect, &SourceRect, BitmapMask?&MaskPoint:NULL, &DCDest->eboFill.BrushObject, &BrushOrigin, rop4)------颜色调整结构 dclevel.ca 来自 DC(影响拉伸时的颜色校正),见《分析_27》;
  15. 清理(failed: 标签统一处理)。

与《分析_27》的关系IntEngStretchBlt(eng/stretchblt.c)内部按 WidthDest/WidthSrc 比例关系选择 StretchCopy / StretchMaskCopy / 逐点复制 路径,并实现 COLORONCOLOR / HALFTONE 模式,详见该分册。

4.7 NtGdiPatBlt(bitblt.c:1024-1073)与 IntPatBlt(bitblt.c:874-964)

4.7.1 NtGdiPatBlt
c 复制代码
BOOL NtGdiPatBlt(HDC hdcDest, INT x, INT y, INT cx, INT cy, DWORD dwRop)

作用:用当前画刷按 ROP 填充矩形(无源)。流程:

  1. dwRop = MAKEROP4(dwRop & 0xFF0000, dwRop)(ROP3 → ROP4);
  2. 禁止源WIN32_ROP4_USES_SOURCE(dwRop) 为真 → 返回 FALSE(PatBlt 语义上不允许源参与);
  3. 锁 DC(DC_LockDc);无表面 → TRUE(Windows 语义);
  4. 画刷脏 → DC_vUpdateFillBrush
  5. IntPatBlt(pdc, x, y, cx, cy, dwRop, &pdc->eboFill)
4.7.2 IntPatBlt(内部核心)

签名IntPatBlt(PDC pdc, INT XLeft, INT YLeft, INT Width, INT Height, DWORD dwRop3, PEBRUSHOBJ pebo)------注意这里 dwRop3 实际是已包装的 ROP4NtGdiPatBlt 传入 MAKEROP4 结果;IntGdiPolyPatBlt 直接传 API 的 dwRop)。

流程

  1. pbrush = pebo->pbrush;若 pbrush->flAttrs & BR_IS_NULL(空画刷)→ 直接 TRUE(无填充);
  2. 负尺寸矩形修正(同 4.6.2 方式);IntLPtoDP + ptlDCOrig
  3. RECTL_vMakeWellOrdered------注释指出 "RTL layout can produce a rect with left > right (backwards rect)"(从右到左布局下 LPtoDP 可能产生反向矩形);
  4. IntUpdateBoundsRect
  5. 画刷原点(bitblt.c:937-943):
c 复制代码
#ifdef _USE_DIBLIB_
    BrushOrigin.x = pbrush->ptOrigin.x + pdc->ptlDCOrig.x + XLeft;
    BrushOrigin.y = pbrush->ptOrigin.y + pdc->ptlDCOrig.y + YLeft;
#else
    BrushOrigin.x = pbrush->ptOrigin.x + pdc->ptlDCOrig.x;
#endif

DIBLib 构建下原点含矩形左上角偏移(让图案从矩形内对齐),非 DIBLib 下仅画刷原点 + DC 原点;

  1. DC_vPrepareDCsForBlit(pdc, &DestRect, NULL, NULL)(单 DC);

  2. IntEngBitBlt(&psurf->SurfObj, NULL, NULL, (CLIPOBJ*)&pdc->co, NULL, &DestRect, NULL, NULL, &pebo->BrushObject, &BrushOrigin, WIN32_ROP3_TO_ENG_ROP4(dwRop3))------无源、无掩码 ,图案由画刷对象提供;

  3. DC_vFinishBlit

注意 :此处裁剪用 pdc->co(DC 合成裁剪,含可见区/RAO/API 裁剪,见《分析_31》),与区域填充函数另行构造 XCLIPOBJ 的路线不同。

4.8 NtGdiPolyPatBlt(bitblt.c:1075-1125)与 IntGdiPolyPatBlt(bitblt.c:966-1022)

4.8.1 NtGdiPolyPatBlt(系统调用入口)

作用 :一次调用填充多个矩形(每个矩形可带不同画刷),对应 PolyPatBlt

流程

  1. cRects > 0 时以 GDITAG_PLGBLT_DATA 标签从 PagedPool 分配 sizeof(PATRECT) × cRects 的临时缓冲;
  2. _SEH2_TRYProbeForRead(pRects, cRects * sizeof(PATRECT), 1) 探测用户缓冲区可读性,再 RtlCopyMemory 拷贝------用户态指针不能直接解引用;
  3. 异常时以 SetLastError(STATUS_*) 返回 FALSE(SetLastNtError);
  4. IntGdiPolyPatBlt(hDC, dwRop, rb, cRects, Mode);最后释放缓冲。
4.8.2 IntGdiPolyPatBlt(内部)

流程

  1. DC_LockDc;DCTYPE_INFO → TRUE;
  2. 逐矩形循环(bitblt.c:993-1017):
c 复制代码
for (i = 0; i < cRects; i++)
{
    pbrush = BRUSH_ShareLockBrush(pRects->hBrush);   /* 锁该矩形的画刷 */
    if (pbrush != NULL)
    {
        EBRUSHOBJ_vInitFromDC(&eboFill, pbrush, pdc); /* 从 DC 上下文初始化画刷对象 */
        IntPatBlt(pdc, pRects->r.left, pRects->r.top,
                  pRects->r.right, pRects->r.bottom,  /* 注意:right/bottom 当宽/高用 */
                  dwRop, &eboFill);
        EBRUSHOBJ_vCleanup(&eboFill);
        BRUSH_ShareUnlockBrush(pbrush);
    }
    pRects++;
}

注意事项

  • PATRECT.rRECTL,其 right/bottom 在此被当作宽度/高度 传给 IntPatBltIntPatBlt 内部再 XLeft + Width 计算右边界)------这是 ntgdi 与用户态 PolyPatBlt 约定好的"伪矩形"语义;
  • 每个矩形独立锁/解锁画刷、独立初始化 EBRUSHOBJEBRUSHOBJ_vInitFromDC 会按 DC 的表面格式实现画刷,见 eng/engbrush.c 与《分析_17》);
  • 单个矩形失败只跳过该矩形,函数仍返回 TRUE(容忍性策略);
  • 返回值恒为 TRUE(只要 DC 有效且非信息 DC)。

4.9 NtGdiFillRgn(bitblt.c:1335-1390)--- FillRgn

c 复制代码
BOOL NtGdiFillRgn(HDC hdc, HRGN hrgn, HBRUSH hbrush)

作用:用指定画刷填充区域(任意形状,经区域裁剪)。

流程

  1. DC_LockDc(hdc),失败 → ERR 日志 + FALSE;
  2. 无表面(空内存 DC / 信息 DC)→ 解锁返回 TRUE;
  3. REGION_LockRgn(hrgn) 锁区域,失败 → ERR + FALSE;
  4. BRUSH_ShareLockBrush(hbrush) 引用锁画刷,失败 → ERR + FALSE;
  5. IntGdiFillRgn(pdc, prgn, pbrFill)(内部核心,见 5.4 节);
  6. 依次 BRUSH_ShareUnlockBrush / REGION_UnlockRgn / DC_UnlockDc

要点:三个 GDI 对象(DC/区域/画刷)分别用三种锁(DC_LockDc、REGION_LockRgn、BRUSH_ShareLockBrush),解锁顺序与加锁相反。

4.10 NtGdiFrameRgn(bitblt.c:1392-1414)--- FrameRgn

c 复制代码
BOOL NtGdiFrameRgn(HDC hdc, HRGN hrgn, HBRUSH hbrush, INT xWidth, INT yHeight)

作用 :绘制区域的边框(厚度 xWidth×yHeight)。实现非常简洁:

c 复制代码
hrgnFrame = GreCreateFrameRgn(hrgn, xWidth, yHeight);  /* 由原区域生成边框区域 */
if (hrgnFrame == NULL) return FALSE;
bResult = NtGdiFillRgn(hdc, hrgnFrame, hbrush);         /* 用画刷填充边框区域 */
GreDeleteObject(hrgnFrame);

GreCreateFrameRgn(region.c:2028)先复制原区域,再向内/向外扩张生成边框区域(内边框:原区域缩小 xWidth/yHeight 后的剩余部分;外边框:扩张后的区域减去原区域)。边框绘制完全复用 FillRgn 管线,没有任何特殊绘制代码。

4.11 NtGdiInvertRgn(bitblt.c:1416-1461)--- InvertRgn

作用:把区域内的像素颜色取反。

流程

  1. DC_LockDc,失败 → ERROR_INVALID_HANDLE
  2. 无表面 → TRUE;
  3. REGION_LockRgn
  4. IntGdiBitBltRgn(pdc, prgn, NULL, NULL, ROP4_DSTINVERT)------见 5.3 节。ROP4_DSTINVERT(dib/dib.h:7)是"目标取反"的 ROP4,无源无图案,纯目标反转;
  5. 解锁区域与 DC。

4.12 NtGdiSetPixel(bitblt.c:1463-1549)--- SetPixel

c 复制代码
COLORREF NtGdiSetPixel(HDC hdc, INT x, INT y, COLORREF crColor)

作用 :设置单像素颜色,返回实际设置后的 RGB 颜色 (若无法精确表示则返回最近似色,失败返回 CLR_INVALID,即 0xFFFFFFFF)。

实现流程------把"单像素"归约为"1×1 的 PatBlt":

  1. 锁 DC;无表面 → CLR_INVALID
  2. IntUpdateBoundsRect(1×1 矩形);
  3. 颜色转换入iSolidColor = TranslateCOLORREF(pdc, crColor)(dcutil.c:882-962,见第 8 节)------把 RGB / PALETTEINDEX / PALETTERGB / DIBINDEX 统一转换为目标表面的颜色空间;
  4. 复用文本画刷pebo = &pdc->eboText------注释:"Use the DC's text brush, which is always a solid brush"(文本画刷恒为纯色,改色代价最小);
  5. 保存旧纯色与脏标志,EBRUSHOBJ_iSetSolidColor(pebo, iSolidColor) 换色,清 DIRTY_TEXT(防止随后 PatBlt 触发文本画刷重建覆盖新色);
  6. IntPatBlt(pdc, x, y, 1, 1, PATCOPY, pebo)------以 PATCOPY(图案=结果)画 1×1;
  7. 恢复现场EBRUSHOBJ_iSetSolidColor(pebo, iOldColor)ulDirty_ = ulDirty
  8. 颜色转换出(bitblt.c:1530-1542):从目标表面调色板转回 RGB:
c 复制代码
EXLATEOBJ_vInitialize(&exlo, pdc->dclevel.pSurface->ppal, &gpalRGB,
                      CLR_INVALID, CLR_INVALID, CLR_INVALID);
crColor = XLATEOBJ_iXlate(&exlo.xlo, iSolidColor);

三个 CLR_INVALID 表示不使用背景/前景色替换(无调色板映射特殊处理),纯数值转换;

  1. 成功返回 crColor(新 RGB),失败 CLR_INVALID

注意 :FIXME 注释提示第 1530 行解引用 pSurface 时 PDEV 未加锁(潜在竞态,当前可接受)。

4.13 NtGdiGetPixel(bitblt.c:1551-1655)--- GetPixel

c 复制代码
COLORREF NtGdiGetPixel(HDC hdc, INT x, INT y)

作用:读取单像素颜色并转为 RGB。

实现流程------把"读 1 个像素"归约为"1×1 的 CopyBits":

  1. 锁 DC(失败 → ERROR_INVALID_HANDLE);无表面 → goto leave
  2. 坐标:IntLPtoDP + ptlDCOrig,构造 1×1 目标矩形 rcDest
  3. DC_vPrepareDCsForBlit(pdc, &rcDest, NULL, NULL)(只准备目标 DC);
  4. 越界检查 (bitblt.c:1597-1605):ptlSrc.x/y >= sizlBitmap.cx/cy< 0 → 失败(GetPixel 在越界时返回 CLR_INVALID,与 SetPixel 的"裁剪"语义不同------SetPixel 通过 PatBlt 的裁剪静默丢弃越界像素,而 GetPixel 必须显式检查);
  5. 分配 1×1 目标表面(bitblt.c:1608-1615):
c 复制代码
psurfDest = SURFACE_AllocSurface(STYPE_BITMAP, 1, 1, BMF_32BPP, 0, 0, 0, &ulRGBColor);

32BPP 表面,初始值即 ulRGBColor

  1. EXLATEOBJ_vInitialize(&exlo, psurfSrc->ppal, &gpalRGB, CLR_INVALID, CLR_INVALID, CLR_INVALID)------源调色板 → RGB;

  2. EngCopyBits(&psurfDest->SurfObj, &psurfSrc->SurfObj, NULL, &exlo.xlo, &rclDest/*(0,0,1,1)*/, &ptlSrc)------从源 (x,y) 拷 1 像素到临时表面 (0,0)逐像素颜色转换在此完成

  3. GDIOBJ_vDeleteObject 删除临时表面;ulRGBColor &= 0x00FFFFFF(去掉 Alpha 高位字节);

  4. leave:DC_vFinishBlit + 解锁,返回 ulRGBColor(失败为 CLR_INVALID)。

要点 :GetPixel 是 bitblt.c 中唯一真正触碰像素 的入口(通过 EngCopyBits,见 eng/copybits.c:229),其余入口都只是装配参数。

4.14 入口公共行为对照

入口 失败错误码 空/无效对象行为 信息 DC 关键短路
NtGdiAlphaBlend ERROR_INVALID_PARAMETER / _INVALID_HANDLE 负尺寸→FALSE;空 DC→FALSE 返回 TRUE 目标/源矩形 right/bottom 为 0
NtGdiBitBlt (透传 MaskBlt/StretchBlt) --- 透传 CAPTUREBLT 走 StretchBlt
NtGdiTransparentBlt ERROR_INVALID_PARAMETER / _INVALID_HANDLE 空 DC→FALSE 返回 TRUE ---
NtGdiMaskBlt ERROR_INVALID_HANDLE / _INVALID_PARAMETER 掩码非 1BPP→失败;缺源 DC→FALSE 返回 TRUE ROP 不用源则不锁源 DC
NtGdiPlgBlt --- --- --- FIXME 桩,恒 FALSE
NtGdiStretchBlt 透传 GreStretchBltMask 零尺寸→返回 TRUE 透传 NOMIRRORBITMAP/CAPTUREBLT 清除
NtGdiPatBlt ERROR_INVALID_HANDLE 无表面→TRUE --- ROP4 用源→FALSE
NtGdiPolyPatBlt ERROR_NOT_ENOUGH_MEMORY / 异常状态码 cRects=0 直接过 返回 TRUE ---
NtGdiFillRgn (内部返回 FALSE) 无表面→TRUE;区域/画刷锁失败→FALSE --- ---
NtGdiFrameRgn (透传 FillRgn) hrgnFrame 建失败→FALSE 透传 ---
NtGdiInvertRgn ERROR_INVALID_HANDLE 无表面→TRUE;区域锁失败→FALSE --- ---
NtGdiSetPixel (返回 CLR_INVALID) 无表面→CLR_INVALID --- ---
NtGdiGetPixel (返回 CLR_INVALID) 无表面/越界→CLR_INVALID --- 越界显式检查

共性规律

  1. "无表面返回 TRUE / 信息 DC 返回 TRUE" 贯穿所有入口------空操作在 Windows 语义下算成功;
  2. 负尺寸只在 AlphaBlend 被直接拒绝(ERROR_INVALID_PARAMETER),其余入口把负尺寸当翻转处理;
  3. 错误码优先级:句柄无效(ERROR_INVALID_HANDLE)> 参数非法(ERROR_INVALID_PARAMETER)> 内存不足(ERROR_NOT_ENOUGH_MEMORY);
  4. 失败路径必须先清理已获取的资源 (锁、EXLATEOBJ、临时表面)再返回,goto done/failed/leave 标签保证这一点。

5. 核心内部函数详解

5.1 裁剪合成范式(IntGdiFillRgn / IntGdiBitBltRgn 共用)

两个区域类内部函数遵循同一套裁剪合成范式 ,值得先总结(以 IntGdiBitBltRgn 1141-1225 行为模板):

c 复制代码
1. 无表面 → 返回 TRUE;
2. prgnClip = IntSysCreateRectpRgn(0, 0, 0, 0);          /* 空区域 */
3. REGION_LPTODP(pdc, prgnClip, prgn);                    /* 拷贝 + 世界→设备变换 */
4. 与 prgnRao 或 prgnVis 做 RGN_AND;                    /* 系统裁剪:RAO/可见区 */
5. REGION_bOffsetRgn(prgnClip, ptlDCOrig.x, ptlDCOrig.y); /* DC 原点平移 */
6. IntUpdateBoundsRect(累积边界);
7. DC_vPrepareDCsForBlit(pdc, &prgnClip->rdh.rcBound, NULL, NULL);
8. IntEngInitClipObj(&xcoClip);
   IntEngUpdateClipRegion(&xcoClip, rdh.nCount, Buffer, &rdh.rcBound); /* 区域→XCLIPOBJ */
9. IntEngBitBlt / IntEngPaint(用 xcoClip 作 CLIPOBJ,矩形取 rdh.rcBound);
10. DC_vFinishBlit → REGION_Delete → IntEngFreeClipResources;

其中 REGION_LPTODP(bitblt.c:1127-1139)是静态辅助:

c 复制代码
static BOOL FASTCALL REGION_LPTODP(PDC pdc, PREGION prgnDest, PREGION prgnSrc)
{
    if (IntGdiCombineRgn(prgnDest, prgnSrc, NULL, RGN_COPY) == ERROR) return FALSE;
    return REGION_bXformRgn(prgnDest, DC_pmxWorldToDevice(pdc));
}
  • IntGdiCombineRgn(..., RGN_COPY) 深拷贝区域(region.c:2490);
  • REGION_bXformRgn 用世界→设备矩阵变换区域路径(region.c:2710 附近的 REGION_bOffsetRgn 是纯平移特例);
  • 裁剪优先级prgnRao(矩形加速优化区,可空)优先于 prgnVis(可见区);两者都是"不含 DC 原点"的系统坐标,所以第 5 步必须平移 DC 原点------这是《分析_31》第 6 节裁剪合成的区域版。

5.2 IntGdiBitBltRgn(bitblt.c:1141-1225)--- 区域版无源 BitBlt

签名IntGdiBitBltRgn(PDC pdc, PREGION prgn, BRUSHOBJ *pbo, POINTL *pptlBrush, ROP4 rop4)------pbo/pptlBrush 可空。

完整流程(即 5.1 范式):

  1. 表面检查 → TRUE;
  2. 建空 prgnClip
  3. REGION_LPTODP 变换到设备坐标;
  4. prgnRao ? prgnRao : prgnVis 求交(RGN_AND)------注释:"these are (atm) without DC-origin"
  5. REGION_bOffsetRgn(+ptlDCOrig)
  6. 累积边界(REGION_GetRgnBox 取外接矩形);
  7. DC_vPrepareDCsForBlit(pdc, &rcBound, NULL, NULL)
  8. IntEngInitClipObj + IntEngUpdateClipRegion(把区域的 rdh.nCount 个矩形 + rcBound 装入 XCLIPOBJ);
  9. IntEngBitBlt(&pdc->dclevel.pSurface->SurfObj, NULL, NULL, (CLIPOBJ*)&xcoClip, NULL, &prgnClip->rdh.rcBound, NULL, NULL, pbo, pptlBrush, rop4)------无源无掩码;
  10. 清理:DC_vFinishBlitREGION_DeleteIntEngFreeClipResources

使用方NtGdiInvertRgn(ROP4_DSTINVERT,pbo=NULL)。引擎侧 IntEngBitBlt 会用 xcoClip.rclBounds 裁剪目标矩形(eng/bitblt.c:701-712),DC_COMPLEX 时枚举矩形逐块绘制。

5.3 IntGdiFillRgn(bitblt.c:1227-1324)--- 区域填充核心(重点)

签名IntGdiFillRgn(PDC pdc, PREGION prgn, PBRUSH pbrFill)------pbrFill 可空(空则用 DC 当前画刷)。

完整流程

Step 1:表面与区域准备

c 复制代码
if (pdc->dclevel.pSurface == NULL) return TRUE;        /* 无表面无操作 */
prgnClip = IntSysCreateRectpRgn(0, 0, 0, 0);           /* 空裁剪区域 */
if (prgnClip == NULL) return FALSE;

Step 2:裁剪合成(同 5.1)

c 复制代码
if (!REGION_LPTODP(pdc, prgnClip, prgn)) { REGION_Delete(prgnClip); return FALSE; }
if (pdc->prgnRao)
    IntGdiCombineRgn(prgnClip, prgnClip, pdc->prgnRao, RGN_AND);  /* RAO 优先 */
else
    IntGdiCombineRgn(prgnClip, prgnClip, pdc->prgnVis, RGN_AND);  /* 否则可见区 */
if (!REGION_bOffsetRgn(prgnClip, pdc->ptlDCOrig.x, pdc->ptlDCOrig.y)) { ... return FALSE; }

Step 3:累积边界 + 裁剪对象

c 复制代码
if (pdc->fs & (DC_ACCUM_APP|DC_ACCUM_WMGR))
{
    REGION_GetRgnBox(prgnClip, &rcrgn);
    IntUpdateBoundsRect(pdc, &rcrgn);
}
IntEngInitClipObj(&xcoClip);
IntEngUpdateClipRegion(&xcoClip, prgnClip->rdh.nCount,
                       prgnClip->Buffer, &prgnClip->rdh.rcBound);

Step 4:MIX 构造 (bitblt.c:1286-1288)------区域填充的核心语义

c 复制代码
rop2Fg = FIXUP_ROP2(pdc->pdcattr->jROP2);              /* ROP2: 1~16 归一化 */
mix = rop2Fg | (pdc->pdcattr->jBkMode == OPAQUE ? rop2Fg : R2_NOP) << 8;
  • FIXUP_ROP2(rop2) = (((rop2 - 1) & 0xF) + 1)(intgdi.h:10),把 ROP2 从 1~16 归一到 0~15 索引(引擎 ROP2 常量 R2_BLACK 等以 0 为基);
  • MIX 高字节 = 背景 ROP2,低字节 = 前景 ROP2;
  • 背景色处理BkMode == OPAQUE 时背景也用 rop2Fg(背景=前景色),否则背景用 R2_NOP(不动)------即 TRANSPARENT 背景模式下,区域外(背景)像素不被画刷覆盖。这正是 GDI SetBkMode 的填充语义;
  • MIX_TO_ROP4(eng/eng.h:5)在引擎侧把 MIX 转成 ROP4(背景<<8 | 前景)。

Step 5:DC 准备 + 画刷对象

c 复制代码
DC_vPrepareDCsForBlit(pdc, &prgnClip->rdh.rcBound, NULL, NULL);
if (pbrFill != NULL)
{
    EBRUSHOBJ_vInit(&eboFill, pbrFill, pdc->dclevel.pSurface,
                    0x00FFFFFF, 0, NULL);              /* 用指定画刷 */
    pbo = &eboFill.BrushObject;
}
else
{
    if (pdc->pdcattr->ulDirty_ & (DIRTY_FILL | DC_BRUSH_DIRTY))
        DC_vUpdateFillBrush(pdc);                      /* 刷新 DC 填充画刷 */
    pbo = &pdc->eboFill.BrushObject;                   /* 用 DC 画刷 */
}
  • EBRUSHOBJ_vInit(pebo, pbrush, psurf, 0x00FFFFFF, 0, NULL)(brush.h:145):以目标表面为基准"实现"画刷(纯色索引化/图案 DIB 化),参数 0x00FFFFFF 是实现的掩码上限、NULL 调色板表示无特殊调色板;
  • NtGdiFillRgn 传显式画刷不同,PaintRgnIntGdiPaintRgn)传 NULL 走 DC 画刷分支。

Step 6:IntEngPaint(bitblt.c:1312-1316)

c 复制代码
bRet = IntEngPaint(&pdc->dclevel.pSurface->SurfObj,
                   (CLIPOBJ *)&xcoClip,
                   pbo,
                   &pdc->ptlFillOrigin,                /* 画刷原点 = DC 填充原点 */
                   mix);
DC_vFinishBlit(pdc, NULL);
REGION_Delete(prgnClip);
IntEngFreeClipResources(&xcoClip);
return bRet;

引擎侧 IntEngPaint(eng/paint.c:85-102)先检查目标表面是否挂钩 DrvPaint(非 STYPE_BITMAP 且 HOOK_PAINT),有则调驱动,否则 EngPaintMIX_TO_ROP4(mix) 转 ROP4 再转发 IntEngBitBlt(无源)------详见《分析_17》。

调用链全貌
#mermaid-svg-8BqC98xSBbSVPO97{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-8BqC98xSBbSVPO97 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8BqC98xSBbSVPO97 .error-icon{fill:#552222;}#mermaid-svg-8BqC98xSBbSVPO97 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8BqC98xSBbSVPO97 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8BqC98xSBbSVPO97 .marker.cross{stroke:#333333;}#mermaid-svg-8BqC98xSBbSVPO97 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8BqC98xSBbSVPO97 p{margin:0;}#mermaid-svg-8BqC98xSBbSVPO97 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8BqC98xSBbSVPO97 .cluster-label text{fill:#333;}#mermaid-svg-8BqC98xSBbSVPO97 .cluster-label span{color:#333;}#mermaid-svg-8BqC98xSBbSVPO97 .cluster-label span p{background-color:transparent;}#mermaid-svg-8BqC98xSBbSVPO97 .label text,#mermaid-svg-8BqC98xSBbSVPO97 span{fill:#333;color:#333;}#mermaid-svg-8BqC98xSBbSVPO97 .node rect,#mermaid-svg-8BqC98xSBbSVPO97 .node circle,#mermaid-svg-8BqC98xSBbSVPO97 .node ellipse,#mermaid-svg-8BqC98xSBbSVPO97 .node polygon,#mermaid-svg-8BqC98xSBbSVPO97 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8BqC98xSBbSVPO97 .rough-node .label text,#mermaid-svg-8BqC98xSBbSVPO97 .node .label text,#mermaid-svg-8BqC98xSBbSVPO97 .image-shape .label,#mermaid-svg-8BqC98xSBbSVPO97 .icon-shape .label{text-anchor:middle;}#mermaid-svg-8BqC98xSBbSVPO97 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8BqC98xSBbSVPO97 .rough-node .label,#mermaid-svg-8BqC98xSBbSVPO97 .node .label,#mermaid-svg-8BqC98xSBbSVPO97 .image-shape .label,#mermaid-svg-8BqC98xSBbSVPO97 .icon-shape .label{text-align:center;}#mermaid-svg-8BqC98xSBbSVPO97 .node.clickable{cursor:pointer;}#mermaid-svg-8BqC98xSBbSVPO97 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8BqC98xSBbSVPO97 .arrowheadPath{fill:#333333;}#mermaid-svg-8BqC98xSBbSVPO97 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8BqC98xSBbSVPO97 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8BqC98xSBbSVPO97 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8BqC98xSBbSVPO97 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8BqC98xSBbSVPO97 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8BqC98xSBbSVPO97 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8BqC98xSBbSVPO97 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8BqC98xSBbSVPO97 .cluster text{fill:#333;}#mermaid-svg-8BqC98xSBbSVPO97 .cluster span{color:#333;}#mermaid-svg-8BqC98xSBbSVPO97 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-8BqC98xSBbSVPO97 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8BqC98xSBbSVPO97 rect.text{fill:none;stroke-width:0;}#mermaid-svg-8BqC98xSBbSVPO97 .icon-shape,#mermaid-svg-8BqC98xSBbSVPO97 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8BqC98xSBbSVPO97 .icon-shape p,#mermaid-svg-8BqC98xSBbSVPO97 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8BqC98xSBbSVPO97 .icon-shape .label rect,#mermaid-svg-8BqC98xSBbSVPO97 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8BqC98xSBbSVPO97 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8BqC98xSBbSVPO97 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8BqC98xSBbSVPO97 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

FillRgn(hdc,hrgn,hbrush)
NtGdiFillRgn
锁 DC / REGION_LockRgn / BRUSH_ShareLockBrush
IntGdiFillRgn
REGION_LPTODP: 世界→设备变换
prgnClip ∩ prgnRao/prgnVis (RGN_AND)
REGION_bOffsetRgn(+ptlDCOrig)
IntEngInitClipObj / IntEngUpdateClipRegion → XCLIPOBJ
MIX = rop2Fg | (BkMode==OPAQUE ? rop2Fg : R2_NOP)<<8
EBRUSHOBJ_vInit / DC_vUpdateFillBrush → BRUSHOBJ
IntEngPaint(SurfObj, xcoClip, pbo, ptlFillOrigin, mix)
表面挂钩 DrvPaint?
GDIDEVFUNCS.Paint (硬件驱动)
EngPaint: MIX_TO_ROP4 → IntEngBitBlt(无源)
EngBitBlt → BltPatCopy / 纯色 / DIB 引擎

5.4 IntGdiPaintRgn(bitblt.c:1326-1333)--- PaintRgn

c 复制代码
BOOL FASTCALL IntGdiPaintRgn(PDC pdc, PREGION prgn)
{
    return IntGdiFillRgn(pdc, prgn, NULL);   /* 无显式画刷 → 用 DC 画刷 */
}

作用 :用 DC 当前画刷(pdc->eboFill,由 SelectObject 选择)填充区域。整个实现就是一行转发------pbrFill = NULL 触发 5.3 的"DC 画刷分支"。该函数不是系统调用入口,由 NtGdiPaintRgn(在 win32k.sys 服务表中映射到本函数)使用。

5.5 IntPatBlt 总结

IntPatBlt 已在 4.7.2 详述。它是 PatBlt / PolyPatBlt / SetPixel 三个入口共用的"无源矩形填充"核心:矩形变换 → 画刷原点 → DC_vPrepareDCsForBlitIntEngBitBlt(无源)。注意它不使用 IntGdiFillRgn 的"区域合成 + XCLIPOBJ"路线,而是直接利用 DC 的合成裁剪 pdc->co(矩形裁剪更轻量)。

5.6 GreStretchBltMask 小结

见 4.6.2,要点是:负尺寸先行修正 → 3 DC 按序锁定 → 掩码尺寸校验 → 带 COLORADJUSTMENTIntEngStretchBlt。它是本文件中最复杂的内部函数(260 行),承担了 StretchBlt 的全部参数装配。

5.7 两种裁剪路线对比:pdc->co 与 XCLIPOBJ

bitblt.c 内部存在两条裁剪路线,选择标准是"绘制形状是矩形还是任意区域":

路线 裁剪来源 使用方 特点
矩形路线 (CLIPOBJ *)&pdc->co(DC 合成裁剪对象,含可见区/RAO/API 裁剪) AlphaBlend、TransparentBlt、MaskBlt、GreStretchBltMask、IntPatBlt 零额外开销,直接复用 DC 状态机已合成的裁剪;矩形外由引擎按 iDComplexity 分块
区域路线 临时 XCLIPOBJ xcoClipIntEngInitClipObj + IntEngUpdateClipRegion 现场构造) IntGdiFillRgn、IntGdiBitBltRgn 需要把任意 REGION 转换为 CLIPOBJ(nCount 矩形 + rcBound);绘制矩形取 rdh.rcBound

为什么区域必须单独合成pdc->co 是"矩形裁剪"(CLIPOBJ 语义),而区域(REGION)是"矩形集合",两者对象类型不同。区域类函数把 REGION 展开为 XCLIPOBJ(其 iDComplexityIntEngUpdateClipRegion 中按矩形数量判定为 DC_RECT/DC_COMPLEX),引擎据此决定单块还是分块绘制。

裁剪合成顺序(区域路线独有):

复制代码
用户区域(逻辑坐标)
 → REGION_LPTODP:世界→设备变换(REGION_bXformRgn)
 → ∩ prgnRao/prgnVis(RGN_AND):系统可见性
 → REGION_bOffsetRgn(+ptlDCOrig):DC 原点对齐
 → IntEngUpdateClipRegion:REGION → XCLIPOBJ

而矩形路线把"用户矩形 → IntLPtoDP → +ptlDCOrig"后直接丢给 pdc->co(DC 原点已在合成裁剪时处理),少一次区域运算,因此矩形绘制(PatBlt 等)比区域绘制(FillRgn 等)更轻。


6. 内存位图暂存机制详解

6.1 问题域

位块传输有三类"表面访问风险":

  1. 设备管理表面(device-managed) :非 STYPE_BITMAP 的表面(如显示表面、驱动管理表面)可能没有可直接读写的 pvScan0/lDelta,或需要驱动协作才能访问像素;
  2. 源/目标重叠 :源与目标是同一表面(同 DC 的 BitBlt)且区域重叠时,直接拷贝会产生"串扰"(拷贝过的像素被再次当作源);
  3. 跨设备拷贝:源与目标属于不同 PDEV(不同显示设备/打印机),驱动回调可能不支持异构拷贝。

bitblt.c 层面解决这些问题的方式是分层配合

  • 锁序保证 (ntgdi 层):DC_vPrepareDCsForBlithsemDevLock 排序加锁,杜绝死锁;MouseSafetyOnDrawStart/End 保护硬件光标;
  • 临时表面暂存(引擎层):真正把"不能直接访问的表面"转成"可访问的 DIB 位图"。

6.2 引擎层暂存:IntEngEnter / IntEngLeave

引擎提供通用进入/离开机制(eng/engmisc.c:14-159):

复制代码
IntEngEnter(EnterLeave, psoDest, DestRect, ReadOnly, Translate, &ppsoOutput)
    ├─ 若 psoDest 是设备管理表面(iType != STYPE_BITMAP 且无 pvScan0/lDelta):
    │    ├─ 要求驱动支持 DrvCopyBits(HOOK_COPYBITS),否则失败
    │    ├─ EngCreateBitmap(宽,高,格式,TOPDOWN) 分配临时 DIB
    │    ├─ EngLockSurface 锁定 → ppsoOutput = 临时表面
    │    ├─ GDIDEVFUNCS(psoDest).CopyBits(临时表面 ← 设备表面, ClippedDestRect)
    │    │      ------ 把设备表面目标区域"暂存"进内存位图
    │    └─ Translate = -DestRect.left/top(坐标平移:临时表面从 (0,0) 起算)
    └─ 否则:ppsoOutput = psoDest,Translate = 0(普通 DIB 直接操作)
IntEngLeave(EnterLeave)
    ├─ 若 OutputObj != DestObj(曾暂存)且非 ReadOnly:
    │    └─ GDIDEVFUNCS(DestObj).CopyBits(设备表面 ← 临时表面)   /* 结果拷回 */
    └─ 解锁/删除临时表面

具体实例------IntEngMaskBlt(eng/bitblt.c:1167-1223):当目标表面是设备管理表面时:

c 复制代码
/* Allocate a temporary surface */
psurfTemp = SURFACE_AllocSurface(STYPE_BITMAP, rcTemp.right, rcTemp.bottom,
                                 psoDest->iBitmapFormat, 0, 0, 0, NULL);
/* Copy the current target surface bits to the temp surface */
ret = EngCopyBits(&psurfTemp->SurfObj, psoDest, NULL, NULL, &rcTemp, (PPOINTL)&rcDest);
/* Do the operation on the temp surface */
ret = EngMaskBitBlt(&psurfTemp->SurfObj, psoMask, NULL, pxloDest, pxloSource, ...);
/* Copy the result back to the dest surface */
ret = EngCopyBits(psoDest, &psurfTemp->SurfObj, pco, NULL, &rcDest, (PPOINTL)&rcTemp);
GDIOBJ_vDeleteObject(&psurfTemp->BaseObject);

即"拷出 → 内存操作 → 拷回 "三步,保证设备表面只发生可驱动的批量拷贝,逐像素逻辑全部在内存 DIB 上进行。IntEngStretchBlt(eng/stretchblt.c,见《分析_27》)对设备管理 表面也做同样的暂存(先 EngCopyBits 拷到临时位图,再以临时位图为源拉伸),从而避免设备源表面被反复 DrvCopyBits 逐行读取。

6.3 重叠问题的处理

同表面重叠时,方向选择由引擎的裁剪枚举决定(eng/bitblt.c:594-612):

c 复制代码
case DC_COMPLEX:
    if (OutputObj == InputObj)              /* 源 == 目标表面 */
    {
        if (OutputRect.top < InputPoint.y)  /* 目标在源上方 */
            Direction = OutputRect.left < InputPoint.x ? CD_RIGHTDOWN : CD_LEFTDOWN;
        else
            Direction = OutputRect.left < InputPoint.x ? CD_RIGHTUP : CD_LEFTUP;
    }
    else
        Direction = CD_ANY;
    CLIPOBJ_cEnumStart(pco, FALSE, CT_RECTANGLES, Direction, 0);

CLIPOBJ_cEnumStart 的 Direction 参数(CD_LEFTUP/CD_RIGHTUP/CD_LEFTDOWN/CD_RIGHTDOWN/CD_ANY)指示枚举器从远离源区的角落开始分块 :例如目标矩形在源矩形的右下方时,从左上角开始逐块拷贝,保证"刚写入的目标像素"永远不会被后续块当源读取------这就是扫描方向规避重叠 (scissoring + direction)。引擎侧(eng/clip.c 的 CLIPOBJ_bEnum)据此切分矩形序列。

6.4 源/目标不同 DC 时的处理

当源、目标是不同 DC (可能不同表面、不同设备)时,ntgdi 层不暂存------直接把两个表面的 SurfObj 交给引擎;引擎只在设备管理表面跨设备psoSrc->hdev != psoTrg->hdev,eng/bitblt.c:763)时介入。异 DC 的"暂存"需求实际上由 EXLATEOBJ(格式转换)与 IntEngEnter(设备→内存)共同消化;若跨设备且两者都要求 HOOK_BITBLT,会触发 DPRINT1("Need to copy to standard bitmap format!") 断言(eng/bitblt.c:765-766)------当前实现假定目标可回退到软件路径。

6.5 暂存触发条件汇总

综合 ntgdi 与引擎两层,一次 blit 是否发生"内存位图暂存"取决于:

条件 处理 所在层
源/目标为普通 DIB(STYPE_BITMAP,有 pvScan0/lDelta) 直接操作,零拷贝 引擎
目标为设备管理表面(无 pvScan0) IntEngEnter/IntEngMaskBlt:拷出→内存操作→拷回 引擎
源为设备管理表面且需逐像素读 EngCopyBits 到临时 DIB 再操作 引擎
跨 PDEV 且源/目标都挂钩 DrvBitBlt 断言/降级(当前不支持) 引擎
源与目标同表面且重叠 方向化裁剪枚举(CD_LEFTUP 等)规避,不暂存 引擎
源/目标格式不同 EXLATEOBJ 逐像素转换,不暂存 ntgdi
多 DC 并发 hsemDevLock 排序加锁 + 鼠标安全,不暂存 ntgdi

结论 :bitblt.c(ntgdi)层从不主动分配临时位图;所有"内存位图暂存"都发生在引擎层(eng/),ntgdi 只负责提供正确的表面、裁剪、颜色与 ROP 参数,以及保证多 DC 访问的锁序。这也是"薄壳"架构的直接推论------把最复杂的表面管理问题留在持有 SURFOBJ 语义的引擎层解决。


7. 区域填充完整链路

区域类五个 API(FillRgn / PaintRgn / FrameRgn / InvertRgn)共享同一条从"区域"到"像素"的链路:

复制代码
FillRgn(hdc, hrgn, hbrush)
    └─ NtGdiFillRgn(bitblt.c:1335)
         ├─ DC_LockDc → REGION_LockRgn → BRUSH_ShareLockBrush
         └─ IntGdiFillRgn(bitblt.c:1227)★ 核心
              ├─ REGION_LPTODP(世界→设备)
              ├─ ∩ prgnRao/prgnVis + REGION_bOffsetRgn(ptlDCOrig)
              ├─ IntEngInitClipObj/IntEngUpdateClipRegion → XCLIPOBJ
              ├─ MIX = rop2Fg | (BkMode==OPAQUE ? rop2Fg : R2_NOP) << 8
              ├─ EBRUSHOBJ_vInit(pbrFill) 或 DC_vUpdateFillBrush
              └─ IntEngPaint(pso, &xcoClip, pbo, &ptlFillOrigin, mix)
                   ├─ 挂钩 DrvPaint → 驱动
                   └─ EngPaint: MIX_TO_ROP4 → IntEngBitBlt(pso, NULL, NULL, pco, ...)
                        └─ EngBitBlt: clippingType 分派
                             ├─ DC_TRIVIAL → 一次 BltRectFunc
                             ├─ DC_RECT    → 与 rclBounds 求交后一次 BltRectFunc
                             └─ DC_COMPLEX → 方向化枚举逐块绘制

PaintRgn(hdc, hrgn)
    └─ NtGdiPaintRgn → IntGdiPaintRgn(pdc, prgn)
         └─ IntGdiFillRgn(pdc, prgn, NULL)     /* 画刷 = DC 当前画刷 */

FrameRgn(hdc, hrgn, hbrush, xWidth, yHeight)
    └─ NtGdiFrameRgn
         ├─ GreCreateFrameRgn(hrgn, xWidth, yHeight)  /* 生成边框区域 */
         ├─ NtGdiFillRgn(hdc, hrgnFrame, hbrush)      /* 复用填充管线 */
         └─ GreDeleteObject(hrgnFrame)

InvertRgn(hdc, hrgn)
    └─ NtGdiInvertRgn
         └─ IntGdiBitBltRgn(pdc, prgn, NULL, NULL, ROP4_DSTINVERT)
              └─(裁剪合成范式)→ IntEngBitBlt(无源, rop4=DSTINVERT)
                   └─ EngBitBlt → 逐像素 DIB_DoRop(目标取反)

三个分支的差别只在"入口装配":FillRgn 带显式画刷、PaintRgn 用 DC 画刷、FrameRgn 先造边框区域、InvertRgn 用 DSTINVERT 的 ROP4 且无画刷(pbo=NULL 时引擎把图案当 0 处理,DSTINVERT 不读图案)。绘制内核(IntGdiFillRgn 的裁剪合成 + MIX/ROP 驱动)完全一致,这正是"一切皆 BitBlt"设计的体现。

7.1 边界情况与语义对齐

区域填充涉及的边界情况(行为均以 Windows 为准):

场景 行为 原因
空区域(零矩形) REGION_LPTODP∩ prgnVis 得空,IntEngUpdateClipRegion 装入空矩形集,引擎判 OutputRect 退化后直接返回 TRUE 空区域是合法 GDI 对象,填充是空操作
区域完全在可见区外 求交后为空,同上返回 TRUE 裁剪合成自然淘汰
信息 DC / 无表面 DC NtGdiFillRgn/IntGdiFillRgn 在锁表面前短路返回 TRUE Windows 语义
画刷为空画刷(BR_IS_NULL) IntPatBlt 返回 TRUE;FillRgn 路径经 EBRUSHOBJ 实现后同样无像素写出 空画刷 = 不画
世界变换含缩放/旋转 REGION_bXformRgn 变换区域路径,ptlFillOrigin 画刷原点随 DC 平移 区域变换而非矩形变换
TRANSPARENT 背景模式 MIX 高字节 = R2_NOP,区域外不覆盖 SetBkMode 语义
OPAQUE 背景模式 MIX 高字节 = rop2Fg,区域外被前景色覆盖 同上

一个容易忽略的点IntGdiFillRgn∩ prgnRao/prgnVisREGION_bOffsetRgn 的先后顺序不能颠倒------RAO/可见区是不含 DC 原点的屏幕坐标,用户区域经 LPTODP 后也是屏幕坐标,两者求交后再统一平移 DC 原点;若先平移再求交,会把两个坐标系混在一起导致填充位置偏移。


8. Get/SetPixel 的颜色转换(EXLATEOBJ 应用)

8.1 TranslateCOLORREF:入方向转换

NtGdiSetPixel 调用 TranslateCOLORREF(pdc, crColor)(dcutil.c:882-962),把四种 COLORREF 编码统一翻译为目标表面颜色空间的索引/数值

COLORREF 类型 判定(高位字节) 处理
RGB(r,g,b) 0x00------ 直接作为 RGB 参与转换
PALETTEINDEX(i) 0x01------ 用 DC 调色板 PALETTE_ulGetRGBColorFromIndex 取 RGB
PALETTERGB(r,g,b) 0x02------ 非默认调色板时先 PALETTE_ulGetNearestIndex 取最近索引再转 RGB
DIBINDEX(i) 0x10FF---- 表面调色板是索引型时低 8 位直接作索引返回;否则按 RGB 处理
其他 --- DPRINT 警告,按 0 处理

统一得到 RGB 后:

c 复制代码
EXLATEOBJ_vInitialize(&exlo, &gpalRGB, psurfDC->ppal, CLR_INVALID, CLR_INVALID, CLR_INVALID);
ulColor = XLATEOBJ_iXlate(&exlo.xlo, crColor);   /* gpalRGB → 目标表面调色板 */

gpalRGB 是系统内建的 32BPP RGB 调色板;XLATEOBJ_iXlate 对 8BPP 调色板表面做"最近色查表",对 16/24/32BPP 表面做位深裁剪(掩码)。无表面时用 psurfDefaultBitmap 兜底。

8.2 回读转换:出方向转换

NtGdiSetPixel 的返回值与 NtGdiGetPixel 的读取都要做反向转换(目标表面 → RGB),两者共用同一构造:

c 复制代码
EXLATEOBJ_vInitialize(&exlo, psurf->ppal, &gpalRGB,   /* 表面调色板 → RGB */
                      CLR_INVALID, CLR_INVALID, CLR_INVALID);
crColor = XLATEOBJ_iXlate(&exlo.xlo, iSolidColor);

三个 CLR_INVALID 参数分别对应 crBackgroundClr / crForegroundClr / crForegroundClr,置 CLR_INVALID 表示不做背景/前景色替换 (单色表面转 RGB 时保留原始 0/1 映射,见 xlateobj.c 的 EXLATEOBJ_vInitialize),保证 GetPixel 读到的是像素真实值而非调色板替换后的值。

8.3 为什么 SetPixel 用文本画刷

pdc->eboTextDC_vInitDc 时以 pbrDefaultBrush 初始化(dclife.c:292),其 EBRUSHOBJ 恒为纯色画刷。SetPixel 通过 EBRUSHOBJ_iSetSolidColor(brush.h:185-190)直接改 BrushObject.iSolidColor 实现"换色",避免走 SelectObject 的完整画刷替换流程 ;绘制后立即恢复旧色,对 DC 状态无副作用(保存/恢复 ulDirty_ 防止 DIRTY_TEXT 标志泄漏)。这一技巧把 SetPixel 的代价压缩到"一次 1×1 的 PatBlt"。


9. 调用链总览

#mermaid-svg-YvCTomyrn7Yrs0Fv{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-YvCTomyrn7Yrs0Fv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YvCTomyrn7Yrs0Fv .error-icon{fill:#552222;}#mermaid-svg-YvCTomyrn7Yrs0Fv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YvCTomyrn7Yrs0Fv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .marker.cross{stroke:#333333;}#mermaid-svg-YvCTomyrn7Yrs0Fv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YvCTomyrn7Yrs0Fv p{margin:0;}#mermaid-svg-YvCTomyrn7Yrs0Fv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .cluster-label text{fill:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .cluster-label span{color:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .cluster-label span p{background-color:transparent;}#mermaid-svg-YvCTomyrn7Yrs0Fv .label text,#mermaid-svg-YvCTomyrn7Yrs0Fv span{fill:#333;color:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .node rect,#mermaid-svg-YvCTomyrn7Yrs0Fv .node circle,#mermaid-svg-YvCTomyrn7Yrs0Fv .node ellipse,#mermaid-svg-YvCTomyrn7Yrs0Fv .node polygon,#mermaid-svg-YvCTomyrn7Yrs0Fv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .rough-node .label text,#mermaid-svg-YvCTomyrn7Yrs0Fv .node .label text,#mermaid-svg-YvCTomyrn7Yrs0Fv .image-shape .label,#mermaid-svg-YvCTomyrn7Yrs0Fv .icon-shape .label{text-anchor:middle;}#mermaid-svg-YvCTomyrn7Yrs0Fv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .rough-node .label,#mermaid-svg-YvCTomyrn7Yrs0Fv .node .label,#mermaid-svg-YvCTomyrn7Yrs0Fv .image-shape .label,#mermaid-svg-YvCTomyrn7Yrs0Fv .icon-shape .label{text-align:center;}#mermaid-svg-YvCTomyrn7Yrs0Fv .node.clickable{cursor:pointer;}#mermaid-svg-YvCTomyrn7Yrs0Fv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .arrowheadPath{fill:#333333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YvCTomyrn7Yrs0Fv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YvCTomyrn7Yrs0Fv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YvCTomyrn7Yrs0Fv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .cluster text{fill:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .cluster span{color:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv 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-YvCTomyrn7Yrs0Fv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv rect.text{fill:none;stroke-width:0;}#mermaid-svg-YvCTomyrn7Yrs0Fv .icon-shape,#mermaid-svg-YvCTomyrn7Yrs0Fv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YvCTomyrn7Yrs0Fv .icon-shape p,#mermaid-svg-YvCTomyrn7Yrs0Fv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .icon-shape .label rect,#mermaid-svg-YvCTomyrn7Yrs0Fv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YvCTomyrn7Yrs0Fv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YvCTomyrn7Yrs0Fv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YvCTomyrn7Yrs0Fv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 引擎 gdi/eng/
内核 ntgdi/bitblt.c
用户态 gdi32.dll
BitBlt
StretchBlt
TransparentBlt
AlphaBlend
MaskBlt
PatBlt
PolyPatBlt
FillRgn/PaintRgn/FrameRgn/InvertRgn
SetPixel/GetPixel
NtGdiBitBlt
NtGdiStretchBlt(CAPTUREBLT) 或 NtGdiMaskBlt
NtGdiStretchBlt
GreStretchBltMask
NtGdiMaskBlt
IntEngBitBlt
NtGdiTransparentBlt
IntEngTransparentBlt
NtGdiAlphaBlend
IntEngAlphaBlend
NtGdiPatBlt
IntPatBlt
NtGdiPolyPatBlt
IntGdiPolyPatBlt
NtGdiFillRgn
IntGdiFillRgn
IntEngPaint
NtGdiInvertRgn
IntGdiBitBltRgn
NtGdiSetPixel
NtGdiGetPixel
EngCopyBits
IntEngStretchBlt
EngBitBlt/BltMask/BlitRectFunc
IntEngTransparentBlt
IntEngAlphaBlend
EngPaint → IntEngBitBlt
IntEngStretchBlt
EngCopyBits → DrvCopyBits


10. 与 eng/bitblt.c 及《分析_4/17/26/27》的关联

10.1 与 eng/bitblt.c 的分工

阶段 ntgdi/bitblt.c eng/bitblt.c
句柄/对象 锁 DC、锁定掩码位图(SURFACE_ShareLockSurface) 不涉及
坐标 IntLPtoDP + ptlDCOrig + 负尺寸补偿 RECTL_vMakeWellOrdered、rclSrc 从目标矩形推导、bLeftToRight/bTopToBottom 还原翻转
裁剪 直接用 pdc->co 或构造 XCLIPOBJ pco->iDComplexity 分派 DC_TRIVIAL/DC_RECT/DC_COMPLEX
颜色 EXLATEOBJ 构造/清理 XLATEOBJ_iXlate 逐像素
ROP ROP4 装配(MAKEROP4/WIN32_ROP4_TO_ENG_ROP4) ROP4_USES_* 静态分析、Rop4 == ROP4_NOOP 短路
画刷 EBRUSHOBJ_vInit/vInitFromDC/vSetSolidColor BRUSHOBJ_psoPattern/psoMask 取图案/掩码面
执行 --- BltMask(逐像素掩码 ROP)、BltPatCopy(纯色填充)、CallDibBitBlt(DIB 引擎)、IntEngEnter/Leave(暂存)

IntEngBitBlt(eng/bitblt.c:656-815)是衔接点:先裁剪目标矩形到表面尺寸与 pco->rclBounds,再按 psurfTrg->flags & HOOK_BITBLT 决定调 GDIDEVFUNCS(psoTrg).BitBlt(驱动)还是 EngBitBlt(软件)。本文件的 ROP4 与坐标约定必须与引擎完全一致,否则会出现镜像/错位/颜色错误。

10.2 与各分册的对应

分册 对应内容
《分析_4》3.1 BitBlt 引擎管线:EngBitBlt / IntEngBitBlt 双层(本文 10.1)
《分析_17》 区域填充:IntEngPaint/EngPaint、EBRUSHOBJ 实现、MIX→ROP4(本文 5.3/5.4)
《分析_26》 AlphaBlend:IntEngAlphaBlend 三种 Alpha 模式(本文 4.1 的引擎侧)
《分析_27》 拉伸与透明:IntEngStretchBlt / IntEngTransparentBlt(本文 4.3/4.6)
《分析_19》 颜色转换:EXLATEOBJ/XLATEOBJ(本文 3.5/8)
《分析_24》 MouseSafetyOnDrawStart/End 鼠标安全(本文 3.4)
《分析_28》 PlgBlt 桩:EngPlgBlt(本文 4.5)
《分析_30》 DibLib:USE_DIBLIB 下 IntPatBlt 画刷原点差异(本文 4.7.2)
《分析_31》 ntgdi 总览:13 入口归属、裁剪合成、DC 状态机(本文开头)

10.3 设计经验小结

  1. 薄壳架构:13 个入口平均每个 60 行,几乎全是"校验→锁→坐标→装配→转发→清理",像素逻辑全部下沉到 eng/dib;
  2. 复用优先:BitBlt→MaskBlt、PatBlt/PolyPatBlt/SetPixel→IntPatBlt、FrameRgn/InvertRgn→FillRgn 式内核,用"特例归约"消灭重复代码;
  3. 成对纪律 :DC 锁 / 设备锁 / 区域锁 / 画刷引用锁 / EXLATEOBJ / 临时表面,每类资源都必须成对获取释放,goto 标签(done/failed/leave)是保证异常路径正确性的惯用手法;
  4. Windows 语义对齐:信息 DC 返回 TRUE、空操作返回 TRUE、越界 GetPixel 返回 CLR_INVALID、负尺寸翻转------这些行为都刻意与 Windows 保持一致,不能按"直觉"优化。

11. 源码索引

文件 关键内容
bitblt.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c) 13 个 NtGdi* 入口 + IntGdiFillRgn/IntGdiPaintRgn/IntGdiBitBltRgn/IntPatBlt/IntGdiPolyPatBlt/GreStretchBltMask/REGION_LPTODP
dclife.c(file:///d:/reactos/win32ss/gdi/ntgdi/dclife.c) DC_vPrepareDCsForBlit(503)/DC_vFinishBlit(612)、DC_vInitDc、画刷/表面更新
dcobjs.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcobjs.c) DC_vUpdateFillBrush/LineBrush/TextBrush
dc.h(file:///d:/reactos/win32ss/gdi/ntgdi/dc.h) DC 结构:dclevel.pSurface、ptlDCOrig、prgnVis/prgnRao、eboFill、co
dcutil.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcutil.c) TranslateCOLORREF(882)、IntUpdateBoundsRect(694)
eng/bitblt.c(file:///d:/reactos/win32ss/gdi/eng/bitblt.c) IntEngBitBlt(656)、EngBitBlt(327)、BltMask、BltPatCopy、IntEngMaskBlt(1104)、gxcoTrivial
eng/paint.c(file:///d:/reactos/win32ss/gdi/eng/paint.c) IntEngPaint(85)/EngPaint(MIX→ROP4)
eng/copybits.c(file:///d:/reactos/win32ss/gdi/eng/copybits.c) IntEngCopyBits(229)
eng/xlateobj.c(file:///d:/reactos/win32ss/gdi/eng/xlateobj.c) EXLATEOBJ_vInitXlateFromDCs(826)/Ex(849)/vInitialize/vCleanup
eng/engmisc.c(file:///d:/reactos/win32ss/gdi/eng/engmisc.c) IntEngEnter(15)/IntEngLeave(162)------内存位图暂存
eng/inteng.h(file:///d:/reactos/win32ss/gdi/eng/inteng.h) ROP4_USES_* 宏、IntEng* 签名
ntgdi/intgdi.h(file:///d:/reactos/win32ss/gdi/ntgdi/intgdi.h) WIN32_ROP4_TO_ENG_ROP4、FIXUP_ROP2
ntgdi/region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c) GreCreateFrameRgn(2028)、REGION_LockRgn、IntGdiCombineRgn(2490)、REGION_bOffsetRgn(2710)
ntgdi/brush.h(file:///d:/reactos/win32ss/gdi/ntgdi/brush.h) EBRUSHOBJ_vInit/vInitFromDC/iSetSolidColor 声明
ntgdi/region.h(file:///d:/reactos/win32ss/gdi/ntgdi/region.h) REGION_LockRgn/UnlockRgn/GetRgnBox/Delete 声明

关联模块: eng/bitblt_new.c(DibLib BitBlt,《分析_30》)、eng/alphablend.c / transblt.c / stretchblt.c(《分析_26/27》)、eng/engbrush.c(BRUSHOBJ 实现)、gdi/dib(DIB_DoRop、DIB_Get/PutPixel)


12. 总结

bitblt.c 是 ntgdi 层绘制命令的"装配车间"

  • 13 个 NtGdi 入口*覆盖了 14 个 Win32 位块/填充 API(BitBlt 与 MaskBlt 共用 MaskBlt 实现),其中 12 个可用、1 个(PlgBlt)为 FIXME 桩;
  • 每个入口的核心工作可归纳为四步:校验与锁定 (参数、句柄、多 DC 按序加锁)→ 坐标变换 (IntLPtoDP + ptlDCOrig + 负尺寸/翻转处理 + 累积边界)→ 参数装配 (EXLATEOBJ、ROP4、BRUSHOBJ、CLIPOBJ)→ 转发引擎(IntEngBitBlt / IntEngStretchBlt / IntEngAlphaBlend / IntEngTransparentBlt / IntEngPaint / EngCopyBits);
  • 区域类操作 (FillRgn/PaintRgn/FrameRgn/InvertRgn)通过 IntGdiFillRgn 的裁剪合成范式(LPTODP → ∩RAO/Vis → DC 原点 → XCLIPOBJ)与 MIX/ROP 驱动,把任意形状区域归约为无源 BitBlt;
  • Get/SetPixel 通过"1×1 归约"(PatBlt / EngCopyBits)+ EXLATEOBJ 双向颜色转换,把单像素操作纳入同一管线;
  • 表面安全(设备管理表面、重叠、跨设备)由 DC_vPrepareDCsForBlit 的锁序/鼠标安全与引擎层 IntEngEnter/Leave 及临时位图暂存共同保证。

理解 bitblt.c 的钥匙是"归约 "二字:BitBlt 归约到 MaskBlt,填充归约到 Paint,像素归约到位块,位块归约到引擎的 ROP 求值。沿着这条归约链,就能把任意 GDI 绘制请求追溯到最终的 DIB_DoRop 像素运算。


本文档基于 ReactOS 源代码 win32ss/gdi/ntgdi/bitblt.c(1655 行,2026-08 版本)及关联模块(eng/bitblt.c、dclife.c、dc.h、paint.c、xlateobj.c、engmisc.c、dcutil.c、region.c 等)分析。文中行号均指当前源码。关联分册:《分析_4/17/19/24/26/27/28/30/31》。

相关推荐
Tyler_TXZ2 小时前
C++C语言之——树
c语言·c++·算法·
小羊先生car2 小时前
F429-HAL-Can总线(回环及双机实验)(2026/8/15)
c语言·单片机·嵌入式硬件·软件构建
caimouse2 小时前
ReactOS 图形系统分析(38):区域子系统 — region.c
c语言
嵌入式阿蔡2 小时前
STM32时钟树详解:配错一个全乱套
c语言·stm32·单片机·嵌入式硬件
caimouse3 小时前
ReactOS 图形系统分析(37):路径子系统 — path.c
c语言·reactos
CarIise3 小时前
C语言基础课程笔记:硬件原理、数据类型、结构体与格式化输入输出详解
c语言·开发语言·笔记
GeekZHR4 小时前
C语言指针2:数组名、二级指针、指针数组,一次把“指针和数组“讲透
c语言·数据结构·算法·指针
Dr.kangder4 小时前
嵌入式面试总结(二十一)——C语言关键字
c语言·开发语言·面试·职场和发展·架构·虚拟化
caimouse5 小时前
ReactOS 图形系统分析(31):内核 GDI — ntgdi
性能优化·reactos