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 禁止源 IntPatBlt → IntEngBitBlt
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>right 或 top>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_APP 或 DC_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_DIRTY → DC_vUpdateFillBrush;含 DIRTY_LINE|DC_PEN_DIRTY → DC_vUpdateLineBrush;含 DIRTY_TEXT → DC_vUpdateTextBrush(dclife.c:515-520)。DC_vUpdateFillBrush(dcobjs.c:15-57)核对句柄 pdcattr->hbrush 与 dclevel.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_vPrepareDCsForBlit 与 DC_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/GreStretchBltMask 用 GDIOBJ_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)

要点:

  • 参数打包 :BLENDFUNCTION、ROP4 等小结构体按值传递;PATRECT 等大结构体传指针,内核侧必须 ProbeForRead + RtlCopyMemory(如 NtGdiPolyPatBlt);
  • 句柄有效性 :HDC/HRGN/HBRUSH/HBITMAP 都是内核句柄表项,用户态不可直接解引用,必须经 DC_LockDc/REGION_LockRgn/BRUSH_ShareLockBrush/SURFACE_ShareLockSurface 转换并加引用;
  • 返回约定 :NtGdiSetPixel/NtGdiGetPixel 返回 COLORREF(CLR_INVALID = -1 表示失败),其余返回 BOOL;错误码经 EngSetLastError/SetLastNtError 写回用户态 GetLastError;
  • 调试通道 :本文件第 10 行 DBG_DEFAULT_CHANNEL(GdiBlt)------开启 GdiBlt 调试通道后,串口会输出 Locking DCs、Preparing DCs for blit、Performing 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.BlendFunction(hcmXform 是颜色矩阵句柄,本实现未使用);
  2. 参数校验 :任一宽/高为负 → ERROR_INVALID_PARAMETER 返回 FALSE;任一 DC 句柄为 NULL → 同样失败;
  3. 锁 DC :GDIOBJ_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_vCleanup → DC_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 + ptlDCOrig;IntUpdateBoundsRect;
  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. UsesSource 时 EXLATEOBJ_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(缩放掩码) 的通用实现,同时被 NtGdiStretchBlt 和 NtGdiMaskBlt(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 个 DC :ahDC[2] = UsesMask ? hDCMask : NULL;
  5. DCTYPE_INFO 短路(目标、源分别查)→ TRUE;
  6. 负尺寸目标矩形 (bitblt.c:669-689):WidthDest<0 时 left = XOriginDest + WidthDest; right = XOriginDest(先把逻辑矩形定义对,再进行 LPtoDP------与 MaskBlt 先变换后归一化不同,这里在变换前就修正方向);
  7. 目标:IntLPtoDP + ptlDCOrig + 归一化 + IntUpdateBoundsRect;
  8. 源矩形 (bitblt.c:718-748):同样处理负尺寸;UsesSource 时 IntLPtoDP(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. UsesSource 时 EXLATEOBJ_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 实际是已包装的 ROP4 (NtGdiPatBlt 传入 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_TRY 中 ProbeForRead(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.r 是 RECTL,其 right/bottom 在此被当作宽度/高度 传给 IntPatBlt(IntPatBlt 内部再 XLeft + Width 计算右边界)------这是 ntgdi 与用户态 PolyPatBlt 约定好的"伪矩形"语义;
  • 每个矩形独立锁/解锁画刷、独立初始化 EBRUSHOBJ(EBRUSHOBJ_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_vFinishBlit、REGION_Delete、IntEngFreeClipResources。

使用方 :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 传显式画刷不同,PaintRgn(IntGdiPaintRgn)传 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),有则调驱动,否则 EngPaint 用 MIX_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_vPrepareDCsForBlit → IntEngBitBlt(无源)。注意它不使用 IntGdiFillRgn 的"区域合成 + XCLIPOBJ"路线,而是直接利用 DC 的合成裁剪 pdc->co(矩形裁剪更轻量)。

5.6 GreStretchBltMask 小结

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

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

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

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

为什么区域必须单独合成 :pdc->co 是"矩形裁剪"(CLIPOBJ 语义),而区域(REGION)是"矩形集合",两者对象类型不同。区域类函数把 REGION 展开为 XCLIPOBJ(其 iDComplexity 在 IntEngUpdateClipRegion 中按矩形数量判定为 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_vPrepareDCsForBlit 按 hsemDevLock 排序加锁,杜绝死锁;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/prgnVis 与 REGION_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->eboText 由 DC_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》。

相关推荐
晓蛋6 天前
c语言指的是什么意思
c语言·编译器·编程开发·集成开发环境·程序实例
傲世仙尊6 天前
目录即文件-Ext文件系统收尾篇
linux·c语言
牵猫散步的鱼儿6 天前
重载、重写(覆盖)、重定义区别
c语言
phltxy6 天前
C 语言指针:从内存地址到灵活的数据访问
c语言
phltxy6 天前
C 语言中的数据存储:从类型到二进制位
c语言
wuminyu6 天前
HotSpot的轻量锁与膨胀后的ObjectMonitor状态重建原理
java·linux·c语言·jvm·c++
码不停蹄Zzz6 天前
指针用法篇——malloc和free指针详解
c语言
彧azz6 天前
操作系统时间管理与系统核心板块学习总结
c语言·笔记·学习·系统架构
白色的北极熊6 天前
c语言 牛客网 BC15
c语言
老当益壮梁奶奶6 天前
ARM汇编学习笔记(六):【i.MX6ULL】时钟系统与定时器(EPIT & GPT)
c语言·arm开发·嵌入式硬件·学习·51单片机