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_*/EngCreatePath。EngStrokePath/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_ELLIPSE、PO_ALL_INTEGERS(所有点整数坐标)、PO_ENUM_AS_INTEGERS、PO_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 |
✓ | NtGdiEngDeletePath(umpdstubs(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 月)