ReactOS 图形系统分析(18):路径对象 — PATHOBJ(pathobj.c)

ReactOS 图形系统分析(18):路径对象 --- PATHOBJ(pathobj.c)

1. 概述

pathobj.c 位于 win32ss/gdi/eng/pathobj.c,实现 PATHOBJ(路径对象)的 DDI 服务函数 ------即 Windows 图形 DDI 契约中面向显示/打印驱动的路径构建与枚举接口。

在 Windows 的 DDI 模型中,驱动实现 DrvStrokePath/DrvFillPath/DrvStrokeAndFillPath/DrvLineTo 时会收到一个 PATHOBJ*,驱动通过 PATHOBJ_bMoveTo/PATHOBJ_bPolyLineTo/PATHOBJ_bEnum构建或枚举路径。pathobj.c 就是这些服务的实现。

复制代码
┌─────────────────────────────────────────────────────────────┐
│ ntgdi/path.c(内核路径记录层,Wine 风格 POINT+BYTE 数组)      │
│   PATH_MoveTo/LineTo/PolyBezier → PATH_AddEntry 累积到        │
│   PATH.pPoints / pFlags 数组 → FillPath/StrokePath 消费       │
├─────────────────────────────────────────────────────────────┤
│ gdi/eng/pathobj.c(本分析对象:PATHOBJ DDI 服务)             │
│   PATH 对象中并存另一套表示:EXTPATHDATA 链表                  │
│   (ppdFirst/ppdLast/ppdCurrent),仅由 PATHOBJ_* 维护        │
│   ├── 生命周期  EngCreatePath / EngDeletePath                │
│   ├── 构建      PATHOBJ_bMoveTo / bPolyLineTo /              │
│   │             bPolyBezierTo / bCloseFigure                 │
│   ├── 枚举      PATHOBJ_vEnumStart / bEnum                   │
│   ├── 查询      PATHOBJ_vGetBounds                           │
│   └── 未实现    PATHOBJ_vEnumStartClipLines /                │
│                 PATHOBJ_bEnumClipLines(宽线裁剪枚举)        │
├─────────────────────────────────────────────────────────────┤
│ 驱动侧(Windows 模型)                                        │
│   DrvStrokePath/DrvFillPath 中调用 PATHOBJ_bEnum 枚举子路径   │
└─────────────────────────────────────────────────────────────┘

重要事实: 与 string.c(STROBJ)类似,pathobj.c 也是为 DDI 契约预留的服务层 ------全树(含 vga/framebuf 驱动)没有任何代码调用 PATHOBJ_*/EngCreatePathEngStrokePath/EngFillPath 等入口在 stubs.c(file:///d:/reactos/win32ss/gdi/eng/stubs.c) 中仍是桩,路径的实际描边/填充由 ntgdi 自己完成(见第 5 节)。


2. 核心数据结构

2.1 PATHOBJ --- DDI 公开路径对象(winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L952-L955))

c 复制代码
typedef struct _PATHOBJ {
  FLONG  fl;        // 路径标志(PO_BEZIERS 等)
  ULONG  cCurves;   // 子路径数量(直线段 + 贝塞尔段计数)
} PATHOBJ;

2.2 PATHDATA --- 枚举单元(winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L937-L941))

c 复制代码
typedef struct _PATHDATA {
  FLONG     flags;   // PD_* 标志(本段属性)
  ULONG     count;   // 本段点数
  POINTFIX *pptfx;   // 定点数坐标数组(FIX,1/64 像素)
} PATHDATA, *PPATHDATA;

*PD_ 标志(winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L926-L935)):**

标志 含义
PD_BEGINSUBPATH 本段是子路径起点
PD_ENDSUBPATH 本段是子路径终点
PD_CLOSEFIGURE 子路径闭合(终点回到起点,且加连接样式)
PD_BEZIERS 本段是贝塞尔曲线(每 3 点一组)
PD_ALL 上述全部标志掩码

PO_ 路径标志: * PO_BEZIERS(含曲线)、PO_ELLIPSEPO_ALL_INTEGERS(所有点整数坐标)、PO_ENUM_AS_INTEGERSPO_WIDENED(宽化路径)。

2.3 EPATHOBJ --- 引擎扩展路径对象(path.h(file:///d:/reactos/win32ss/gdi/ntgdi/path.h#L61-L66))

c 复制代码
typedef struct _EPATHOBJ
{
  PATHOBJ  po;      // 公开头(fl + cCurves)
  PPATH    pPath;   // 引擎路径对象(含 EXTPATHDATA 链表)
  CLIPOBJ *pco;     // 关联裁剪对象
} EPATHOBJ, *PEPATHOBJ;

2.4 PATH --- 引擎路径对象(path.h(file:///d:/reactos/win32ss/gdi/ntgdi/path.h#L34-L59),节选关键字段)

c 复制代码
typedef struct _PATH
{
  BASEOBJECT    BaseObject;       // GDI 对象头(句柄/锁/标志)
  RECTFX        rcfxBoundBox;     // 包围盒(FIX)
  POINTFX       ptfxSubPathStart; // 当前子路径起点
  FLONG         flType;           // PATHTYPE_KEEPME / PATHTYPE_STACK
  PEXTPATHDATA  ppdFirst;         // ← pathobj.c 维护的链表头
  PEXTPATHDATA  ppdLast;          // ← 链表尾
  FLONG         flags;            // PATHDATA flags
  PEXTPATHDATA  ppdCurrent;       // ← 枚举游标
  FLONG         fl;               // 保存的 PO_* 标志
  ULONG         cCurves;          // 线段 + 贝塞尔计数
  struct _EPATHOBJ *epo;          // 反向指针(EngCreatePath 时设置)

  /* ntgdi/path.c 使用的另一套表示(Wine 风格) */
  POINT        *pPoints;          // 点数组
  BYTE         *pFlags;           // 点标志数组
  int          numEntriesUsed;
  int          numEntriesAllocated;
  ...
} PATH, *PPATH;

关键设计: PATH 对象里并存两套路径表示 ------ntgdi 的 pPoints/pFlags 数组(记录用)与 pathobj.c 的 EXTPATHDATA 链表(DDI 用),二者互不转换、独立维护(详见第 5 节)。

2.5 EXTPATHDATA --- 链表节点(path.h(file:///d:/reactos/win32ss/gdi/ntgdi/path.h#L28-L32))

c 复制代码
typedef struct _EXTPATHDATA
{
  PATHDATA pd;               // 内嵌 DDI PATHDATA(flags/count/pptfx)
  struct _EXTPATHDATA *ppdNext;  // 下一段
} EXTPATHDATA, *PEXTPATHDATA;

3. 函数实现明细

3.1 生命周期:EngCreatePath / EngDeletePath

c 复制代码
PATHOBJ* EngCreatePath(VOID)
{
    PPATH pPath = PATH_AllocPathWithHandle();      // 分配带句柄的 PATH GDI 对象
    pPathObj = ExAllocatePoolWithTag(PagedPool, sizeof(EPATHOBJ), GDITAG_PATHOBJ);
    RtlZeroMemory(pPathObj, size);
    pPathObj->pPath = pPath;
    pPath->flType = PATHTYPE_KEEPME;               // 标记:路径由 PATHOBJ 保持
    pPath->epo = pPathObj;                          // 双向关联
    return &pPathObj->po;                           // 返回公开 PATHOBJ*
}

EngDeletePath 沿 ppdFirst 链表释放所有 EXTPATHDATA 节点、释放 EPATHOBJ,再通过 GDIOBJ_vDeleteObject 删除 PATH GDI 对象。

3.2 构建:PATHOBJ_bMoveTo

开启新子路径:

c 复制代码
ppd->pd.flags = PD_BEGINSUBPATH;   // 标记子路径起点
ppd->pd.count = 1;                  // 单个点
ppd->pd.pptfx[0] = ptfx;            // 定点坐标

if (ppdLast)                        // 已有子路径 →
    ppdLast->pd.flags |= PD_ENDSUBPATH;   // 上一段标记结束
    ppdLast->ppdNext = ppd;               // 链表追加
else                                // 首个 →
    ppdFirst = ppdLast = ppd;
po.cCurves++;                       // 子路径计数 +1

3.3 构建:PATHOBJ_bPolyLineTo / PATHOBJ_bPolyBezierTo

两者结构对称,均按 ppdLast 状态三路分发(以 PolyLineTo 为例):

分支 条件 动作
① 无任何子路径 ppdLast == NULL 新建节点:PD_BEGINSUBPATH,拷贝全部点
② 上一段需结束 `flags & (PD_BEZIERS PD_ENDSUBPATH)`
③ 可连续拼接 其他 原地合并 :重分配 count_old+count_new 的点数组,拷贝新旧点,释放旧数组,count += cptfx

对称性差异(设计细节):

  • 分支②的判定:PolyLineTo 为 pd.flags & (PD_BEZIERS|PD_ENDSUBPATH);PolyBezierTo 为 !(pd.flags & PD_BEZIERS) || (pd.flags & PD_ENDSUBPATH)------即直线段不能混入贝塞尔段,反之亦然,两种段型各自成段。
  • PolyBezierTo 额外置 po.fl |= PO_BEZIERS,向驱动通告路径含曲线。
  • 合并策略(分支③)避免频繁分配,尽量让连续同类线段共享一个 PATHDATA。

3.4 闭合:PATHOBJ_bCloseFigure

c 复制代码
ppd = pPath->ppdLast;                    // 取最后一段
if (ppd == NULL) return FALSE;
ppd->pd.flags |= PD_CLOSEFIGURE | PD_ENDSUBPATH;   // 闭合 + 结束
return TRUE;

3.5 枚举:PATHOBJ_vEnumStart / PATHOBJ_bEnum

c 复制代码
VOID PATHOBJ_vEnumStart(IN PATHOBJ *ppo)
{
    pPathObj->pPath->ppdCurrent = pPathObj->pPath->ppdFirst;  // 游标复位到头
}

BOOL PATHOBJ_bEnum(IN PATHOBJ *ppo, OUT PATHDATA *ppd)
{
    if (ppdCurrent == NULL) return FALSE;          // 枚举结束
    *ppd = ppdCurrent->pd;                         // 返回当前段(值拷贝)
    ppdCurrent = ppdCurrent->ppdNext;              // 游标推进
    return (ppdCurrent != NULL);                   // 还有更多段
}

语义: 一次 bEnum 返回一段 PATHDATA(含 flags/count/pptfx 数组),驱动据此绘制。游标实现为 ppdCurrent 指针,vEnumStart 负责复位------Windows 驱动通常在 DrvStrokePath 中先 vEnumStart 再循环 bEnum

3.6 查询:PATHOBJ_vGetBounds

c 复制代码
yTop = xLeft = MAXLONG;  yBottom = xRight = MINLONG;
for (ppd = ppdFirst; ppd; ppd = ppdNext)
    for (i = 0; i < ppd->pd.count; ++i) { /* 逐点求 min/max */ }
if (xLeft <= xRight && yTop <= yBottom) {
    prectfx->xLeft = xLeft;  prectfx->yTop = yTop;
    prectfx->xRight = xRight + 1;  prectfx->yBottom = yBottom + 1;  // 半开区间
} else {
    RtlZeroMemory(prectfx, sizeof(*prectfx));       // 空路径 → 零矩形
}

3.7 未实现:PATHOBJ_vEnumStartClipLines / PATHOBJ_bEnumClipLines

宽线(wide pen)描边时的裁剪线条枚举 服务。Windows 中 DrvStrokePath 处理复杂裁剪时,GDI 通过 vEnumStartClipLines 初始化、bEnumClipLines 循环返回 CLIPLINE 记录(裁剪后的短线段),驱动据此逐段绘制。ReactOS 中两者均为 UNIMPLEMENTED(文件头 FIXME 注释也明确标注了这一点)。


4. 服务导出与调用链

PATHOBJ 函数是 win32k.sys 导出的 DDI 服务win32k.spec(file:///d:/reactos/win32ss/win32k.spec#L186-L194)):

win32k 导出 实现状态 UMPD 系统调用(gdi32→win32u)
EngCreatePath ---
EngDeletePath NtGdiEngDeletePathumpdstubs(file:///d:/reactos/win32ss/gdi/eng/umpdstubs.c#L224-L232) 桩)
PATHOBJ_bCloseFigure ---
PATHOBJ_bEnum NtGdiPATHOBJ_bEnum(桩)
PATHOBJ_bMoveTo ---
PATHOBJ_bPolyBezierTo ---
PATHOBJ_bPolyLineTo ---
PATHOBJ_vEnumStart NtGdiPATHOBJ_vEnumStart(桩)
PATHOBJ_vEnumStartClipLines ---
PATHOBJ_bEnumClipLines ---
PATHOBJ_vGetBounds NtGdiPATHOBJ_vGetBounds(桩)

调用关系:

复制代码
内核驱动:  DrvStrokePath/DrvFillPath 收到 PATHOBJ*
              ↓
          PATHOBJ_bEnum / PATHOBJ_bMoveTo ...(直接调 win32k 导出)
              ↓
          eng/pathobj.c(本文件)

UMPD 驱动: gdi32.PATHOBJ_bEnum → NtGdiPATHOBJ_bEnum → umpdstubs → eng/pathobj.c
              (仅 4 个函数有 UMPD 路径,其余仅内核可用)

当前状态: 全代码树(含 vga/framebuf)没有任何调用者 ------这些函数仅作为 ABI 导出保留,等待 EngStrokePath/EngFillPath 实现或第三方驱动接入。


5. 对照:ntgdi/path.c 的实际路径管线

ReactOS 当前描边/填充完全由 ntgdi 自己完成,不经过 PATHOBJ

复制代码
BeginPath → PATH_MoveTo/LineTo/BezierTo → PATH_AddEntry 写入 pPoints/pFlags 数组
    ↓
PATH_FillPath      → PATH_PathToRegion(转区域)→ IntGdiFillRgn
PATH_StrokePath    → 宽笔: PATH_WidenPathEx(轮廓化)→ 填充
                     细笔: 逐段 → IntEngLineTo
EndPath → NtGdiEndPath → ...

两套表示并存于同一 PATH 对象但互不转换:

表示 维护者 用途
pPoints / pFlags 数组 ntgdi/path.c(file:///d:/reactos/win32ss/gdi/ntgdi/path.c)(PATH_AddEntry) BeginPath 记录、FillPath/StrokePath 内部消费
EXTPATHDATA 链表 eng/pathobj.c(file:///d:/reactos/win32ss/gdi/eng/pathobj.c)(PATHOBJ_*) DDI 服务(当前无调用者)

这与 string.c/STROBJ 的情形完全同构:DDI 服务层已实现/预留,但引擎的实际渲染路径尚未接线到 DDI 模型。


6. 与《分析_4》标注对照

标注 核对
《_4》第 38 行:pathobj.c =「路径对象」 准确。补充:实现主体是 PATHOBJ DDI 服务(构建/枚举/查询),win32k.spec 导出 9 个符号,树内无驱动调用,为预留服务层

7. 源码索引

文件 关键内容
pathobj.c(file:///d:/reactos/win32ss/gdi/eng/pathobj.c) EngCreatePath/EngDeletePath、PATHOBJ_bMoveTo/bPolyLineTo/bPolyBezierTo/bCloseFigure/vEnumStart/bEnum/vGetBounds、vEnumStartClipLines/bEnumClipLines(桩)
path.h(file:///d:/reactos/win32ss/gdi/ntgdi/path.h) PATH / EPATHOBJ / EXTPATHDATA 结构、PATH_* 内部接口
winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h) PATHOBJ / PATHDATA 结构、PD_* / PO_* 标志
ntgdi/path.c(file:///d:/reactos/win32ss/gdi/ntgdi/path.c) 实际路径记录/描边/填充(pPoints/pFlags 数组管线)
stubs.c(file:///d:/reactos/win32ss/gdi/eng/stubs.c) EngStrokePath/EngFillPath/EngStrokeAndFillPath 桩
win32k.spec(file:///d:/reactos/win32ss/win32k.spec#L186-L194) PATHOBJ/EngCreatePath 导出
umpdstubs.c(file:///d:/reactos/win32ss/gdi/eng/umpdstubs.c) NtGdiPATHOBJ_*/NtGdiEngDeletePath(UMPD 桩)

关联模块:

  • 上层记录层:ntgdi/path.c(file:///d:/reactos/win32ss/gdi/ntgdi/path.c)(BeginPath/EndPath 系列)
  • 潜在 DDI 消费者:驱动 DrvStrokePath / DrvFillPath / DrvStrokeAndFillPath(ReactOS 树内驱动均未实现)
  • 相关引擎入口:EngStrokePath / EngFillPath(stubs.c 桩)

本文档基于 ReactOS 源代码 win32ss/gdi/eng/pathobj.c 及关联模块分析(2026 年 8 月)

相关推荐
冯汉栩3 小时前
Swift Control View,Label渐变颜色(源码)
开发语言·ios·swift
白狐_7984 小时前
408 数据结构|线索二叉树两题详解:先序线索化后的空链域 + 中序前驱/后继判断
c语言·数据结构·链表
从小就看凹凸曼^o^4 小时前
Python基础5 - 字典与集合:(1)字典
开发语言·python
GeekZHR4 小时前
C语言指针进阶补充6:动态内存管理、mem系列内存函数、复杂指针声明,一次补齐指针的“三大盲区“
java·c语言·算法·指针
侧耳倾听1115 小时前
java 日志框架简介
java·开发语言
zlinear数据采集卡5 小时前
数据采集卡从入门到精通(9):分辨率与精度——16位卡不等于1/65536的精度
开发语言·数据库·fpga开发·开源·c#
全栈技术负责人5 小时前
解密 MCP(Model Context Protocol):大模型时代的“Type-C”总线与三驾马车架构深度解析
开发语言·ai·架构
丰锋ff5 小时前
1.7在Qt窗口中添加右键菜单
开发语言·qt
Titan20245 小时前
Linux网络学习:套接字、UDP的封装与应用
linux·服务器·开发语言·网络·c++
流浪0016 小时前
Python基础语法(三):函数
开发语言·python