ReactOS 图形系统分析(50):画笔子系统 --- pen.c
1. 概述
1.1 本文范围
本文分析 ReactOS Win32 子系统(win32ss)中 ntgdi 的画笔(PEN)子系统 ,核心源码为
pen.c(file:///d:/reactos/win32ss/gdi/ntgdi/pen.c)(全文约 516 行)与
pen.h(file:///d:/reactos/win32ss/gdi/ntgdi/pen.h)。该子系统负责:
- 画笔对象创建 :
NtGdiCreatePen(简单笔)与NtGdiExtCreatePen(扩展笔)两个系统调用入口; - 内部创建中枢 :
IntGdiExtCreatePen------所有画笔最终都汇聚于此完成字段初始化与样式解析; - 对象分配 :
PEN_AllocPenWithHandle/PEN_AllocExtPenWithHandle(经 GDI 句柄表分配); - 颜色修改 :
IntGdiSetSolidPenColor(配合SetDCPenColor链路使用); - 对象查询 :
PEN_GetObject(填充LOGPEN/EXTLOGPEN,供GetObject使用)。
画笔在 Windows GDI 中决定了线条的样式(实线/虚线/点线)、宽度与颜色 。ReactOS 的实现有一个
鲜明的特点:画笔(PEN)在内存布局上完全复用画刷(BRUSH) ------pen.h 中直接写
typedef BRUSH PEN, *PPEN;。这是因为线条本质上是"用笔的画刷沿路径扫掠出的区域",绘制引擎对
线条、填充、文字三者的画刷做了统一抽象(EBRUSHOBJ:eboLine / eboFill / eboText),
笔与刷共享 BRUSHBODY 内核布局可以极大复用代码(见第 2 章与第 7 章)。
1.2 设计动机
理解 pen.c 的设计,需要先回答几个"为什么":
-
为什么 PEN 复用 BRUSH 布局?
在 GDI 内部,画直线 / 画边框 / 填区域最终都要生成一个"笔刷"交给显示驱动(
Eng*系列函数)。BRUSHBODY的注释明确指出其布局取自冯远(Feng Yuan)《Windows Graphics Programming》一书,并要求"除非确有必要,否则不得修改该结构,修改前须先在邮件列表上讨论"。把 PEN 定义为 BRUSH,
使
PEN_ShareLockPen可以直接调用GDIOBJ_ReferenceObjectByHandle(hobj, GDIObjType_BRUSH_TYPE)以画刷身份 引用句柄,使
dcobjs.c的DC_vUpdateLineBrush能用同一套画刷实现逻辑(EBRUSHOBJ_vUpdateFromDC)来更新线条画刷。
-
为什么要有"简单笔"与"扩展笔"两种对象类型?
老式
CreatePen只支持宽度、样式、颜色三个要素;ExtCreatePen额外支持几何笔(
PS_GEOMETRIC)、自定义样式数组(PS_USERSTYLE)、端点样式(PS_ENDCAP_*)与连接样式(
PS_JOIN_*)。ReactOS 为二者分配了不同的 GDI 低级对象类型(
GDILoObjType_LO_PEN_TYPE与GDILoObjType_LO_EXTPEN_TYPE),并在flAttrs中打上BR_IS_OLDSTYLEPEN标记。这一区分带来的直接收益是:PEN_GetObject可按对象类型返回LOGPEN或EXTLOGPEN;PATH_WidenPathEx(宽化路径)可据此判断当前笔的类型,进而决定从LOGPEN.lopnStyle
还是EXTLOGPEN.elpPenStyle读取样式;- 宽笔判定宏
IntIsEffectiveWidePen需要知道是否为几何笔 / 老式笔。
-
为什么宽笔要走"路径描边"而非直接画线?
宽度 > 1 的线条不再是"数学上的线",而是一个有面积的区域。直接让引擎画一条粗线,
不同驱动厂商的算法千差万别,且端点、连接的样式无法保证。ReactOS 的折中方案是:
先把线条转成临时路径(
PATH_MoveTo/PATH_LineTo),再调用PATH_StrokePath对路径进行描边 ------若为宽笔则进一步调用
IntGdiWidenPath把路径展开为笔宽的轮廓(一个闭合多边形),最后用 WINDING 填充模式填充该轮廓。这样粗细、端点、连接全部由
path.c的几何算法统一决定(详见第 7 章)。 -
为什么创建参数要在用户态与内核态各校验一遍?
内核态是安全边界:
NtGdiExtCreatePen收到的pUnsafeStyle、ulHatch都是用户指针,必须用
ProbeForRead+ SEH 保护复制,防止越权访问内核内存;而gdi32的ExtCreatePen先做一次"友好"校验(例如
PS_USERSTYLE必须提供样式数组、PS_ALTERNATE只允许美容笔),可以提前返回错误、避免不必要的系统调用开销。
1.3 整体架构图
应用层 (user32 / 应用程序)
CreatePen ExtCreatePen SetDCPenColor GetObject
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ gdi32.dll(用户态薄封装,还负责 EMF 记录) │
│ objects/pen.c CreatePen → NtGdiCreatePen │
│ objects/brush.c ExtCreatePen → NtGdiExtCreatePen(DIB 转换/预校验) │
│ objects/dc.c SetDCPenColor → 直接改 DC 属性 ulPenClr + DIRTY_LINE │
└──────────────────────────────┬──────────────────────────────────────────┘
│ 系统调用(Nt*)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ win32k.sys ntgdi/pen.c(本文核心) │
│ NtGdiCreatePen ──────────┐ │
│ NtGdiExtCreatePen ───────┼──▶ IntGdiExtCreatePen │
│ │ ├─ PEN_AllocPenWithHandle(老式笔) │
│ │ │ └─ GDIOBJ_AllocObjWithHandle │
│ │ │ └─ PEN_vInit(pBrushAttr) │
│ │ ├─ PEN_AllocExtPenWithHandle(扩展笔)│
│ │ ├─ 样式数组/端帽/连接/类型校验 │
│ │ └─ flAttrs 标志与字段初始化 │
│ IntGdiSetSolidPenColor(改纯色,当前无调用方) │
│ PEN_GetObject(LOGPEN / EXTLOGPEN 查询) │
│ PEN_ShareLockPen / PEN_UnlockPen / PEN_ShareUnlockPen(句柄引用) │
└──────────────┬──────────────────────────────────────────────────────────┘
│ 被选中/被查询
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 绘制链路(关联模块) │
│ dcobjs.c DC_vUpdateLineBrush → eboLine(线条画刷) │
│ line.c IntGdiLineTo / IntGdiPolyline → IntIsEffectiveWidePen 判定 │
│ ├─ 细线 → IntEngLineTo / IntEngPolyline(eboLine) │
│ └─ 宽笔 → 临时路径 → PATH_StrokePath │
│ path.c PATH_StrokePath → PATH_WidenPathEx → IntGdiWidenPath(轮廓) │
│ → PATH_FillPathEx(WINDING 填充) │
│ fillshap.c 椭圆/矩形边框绘制同样按 IntIsEffectiveWidePen 分流 │
└─────────────────────────────────────────────────────────────────────────┘
1.4 文件组成
| 文件 | 角色 |
|---|---|
win32ss/gdi/ntgdi/pen.c |
画笔子系统实现(2 个 NtGdi* 系统调用 + 内部函数) |
win32ss/gdi/ntgdi/pen.h |
PEN 类型别名、分配/引用/查询接口、IntIsEffectiveWidePen 宏 |
win32ss/gdi/ntgdi/brush.h |
BRUSH/BRUSHBODY 结构定义(PEN 复用的画刷布局)、BR_IS_* 标志 |
win32ss/gdi/ntgdi/line.c |
AddPenLinesBounds 实现、IntGdiLineTo/IntGdiPolyline 宽笔分流 |
win32ss/gdi/ntgdi/path.c |
PATH_StrokePath、PATH_WidenPathEx、IntGdiWidenPath |
win32ss/gdi/ntgdi/dcobjs.c |
DC_vUpdateLineBrush(画笔 → 线条画刷) |
win32ss/gdi/ntgdi/brush.cpp |
BRUSH 析构(释放非默认样式数组) |
win32ss/gdi/gdi32/objects/pen.c |
用户态 CreatePen / CreatePenIndirect |
win32ss/gdi/gdi32/objects/brush.c |
用户态 ExtCreatePen(DIB 转换、预校验) |
win32ss/gdi/gdi32/objects/dc.c |
用户态 SetDCPenColor |
win32ss/include/ntgdihdl.h |
BRUSH_ATTR 定义 |
sdk/include/psdk/wingdi.h |
PS_* 常量、LOGPEN、EXTLOGPEN 定义 |
2. 核心数据结构
2.1 PEN 与 BRUSH 的关系
pen.h(file:///d:/reactos/win32ss/gdi/ntgdi/pen.h) 全文如下(内部接口):
c
#pragma once
/* Internal interface */
typedef BRUSH PEN, *PPEN; // ← 关键:PEN 就是 BRUSH
PPEN NTAPI PEN_AllocPenWithHandle(VOID);
PPEN NTAPI PEN_AllocExtPenWithHandle(VOID);
#define PEN_UnlockPen(pPenObj) GDIOBJ_vUnlockObject((POBJ)pPenObj)
#define PEN_ShareUnlockPen(ppen) GDIOBJ_vDereferenceObject((POBJ)ppen)
PPEN FASTCALL PEN_ShareLockPen(HPEN hpen);
INT NTAPI PEN_GetObject(
_In_ PPEN pPen,
_In_ INT Count,
_Out_ PLOGPEN Buffer);
VOID FASTCALL AddPenLinesBounds(PDC, int, POINT *);
#define IntIsEffectiveWidePen(pbrLine) ( \
(pbrLine)->lWidth > 1 && \
((pbrLine->flAttrs & BR_IS_OLDSTYLEPEN) || \
((pbrLine)->ulPenStyle & PS_TYPE_MASK) == PS_GEOMETRIC) \
)
要点:
PEN是BRUSH的别名,PPEN即PBRUSH。因此 pen.c 中几乎所有函数都返回/操作PBRUSH。
分配函数PEN_AllocPenWithHandle的返回值类型就是PBRUSH(见 3.2 节源码)。IntIsEffectiveWidePen("是否生效的宽笔")宏被 line.c、fillshap.c、path.c 三处使用,
是笔对象与绘制引擎之间的关键纽带(详见 7.1 节)。
2.2 BRUSHBODY 结构(画笔/画刷共用布局)
brush.h(file:///d:/reactos/win32ss/gdi/ntgdi/brush.h) 中定义:
c
#define NB_HATCH_STYLES 6
/*
* The layout of this structure is taken from "Windows Graphics Programming"
* book written by Feng Yuan.
*
* DON'T MODIFY THIS STRUCTURE UNLESS REALLY NEEDED AND EVEN THEN ASK ON
* A MAILING LIST FIRST.
*/
typedef struct _BRUSHBODY
{
ULONG iHatch; // This is not the brush style, but the hatch style!
HBITMAP hbmPattern;
HBITMAP hbmClient;
ULONG flAttrs;
ULONG ulBrushUnique;
BRUSH_ATTR *pBrushAttr; // Pointer to the currently active brush attribute
BRUSH_ATTR BrushAttr; // Internal brush attribute for global brushes
POINT ptOrigin;
ULONG bCacheGrabbed;
COLORREF crBack;
COLORREF crFore;
ULONG ulPalTime;
ULONG ulSurfTime;
PVOID pvRBrush;
HDEV hdev;
//DWORD unk054;
/* The following members are for PENs only */
LONG lWidth;
FLOATOBJ eWidth;
ULONG ulPenStyle;
DWORD *pStyle;
ULONG dwStyleCount;
BYTE jJoin; // 0x06c Join styles for geometric wide lines
BYTE jEndCap; // end cap style for a geometric wide line
//WORD unk06e; // 0x06e
INT iBrushStyle; // 0x070
//PREGION prgn; // 0x074
//DWORD unk078; // 0x078
//DWORD unk07c; // 0x07c
ULONG ulStyleSize;
LIST_ENTRY ListHead; // 0x080
} BRUSHBODY;
#ifndef __cplusplus
typedef struct _BRUSH
{
/* Header for all gdi objects in the handle table.
Do not (re)move this. */
BASEOBJECT BaseObject;
BRUSHBODY;
} BRUSH, *PBRUSH;
#else
class BRUSH;
typedef class BRUSH *PBRUSH;
#endif
BRUSH 由两部分组成:
BASEOBJECT BaseObject:GDI 句柄表对象公共头(含hHmgr句柄、pEntry句柄表项指针、
对象类型、引用计数等)。IntGdiExtCreatePen中hPen = pbrushPen->BaseObject.hHmgr;
就是把句柄表里分配到的句柄直接作为画笔句柄返回。BRUSHBODY:画刷主体。前 17 个字段(iHatch到hdev)是画刷与画笔共用 的;
从lWidth开始是"仅画笔使用"的成员。
各字段对画笔的意义:
| 字段 | 画笔中的含义 |
|---|---|
iHatch |
影线样式(对画笔一般不使用;注释强调"这不是画刷样式,而是影线样式") |
hbmPattern |
图案位图(BS_PATTERN 类画刷用) |
hbmClient |
客户端位图;在画笔中保存用户提供的 hatch 参数 ulClientHatch(PEN_GetObject 将其作为 elpHatch 返回) |
flAttrs |
属性标志位 (BR_IS_* 集合),见 2.4 节 |
ulBrushUnique |
画刷唯一序号(缓存/驱动同步用) |
pBrushAttr |
指向"当前生效的画刷属性";PEN_vInit 将其指向对象内嵌的 BrushAttr |
BrushAttr |
对象内嵌的画刷属性({AttrFlags, lbColor}),画笔的颜色 存放在 BrushAttr.lbColor |
ptOrigin |
画刷原点(图案对齐用) |
bCacheGrabbed / crBack / crFore / ulPalTime / ulSurfTime / pvRBrush / hdev |
画刷实现/调色板/设备句柄等(画笔基本不用) |
lWidth |
逻辑宽度 。老式笔与美容笔为设备单位;几何笔为世界单位。IntIsEffectiveWidePen 依据它判断宽笔 |
eWidth |
宽度浮点表示(FLOATOBJ),IntGdiExtCreatePen 用 FLOATOBJ_SetLong 由 lWidth 生成 |
ulPenStyle |
完整笔样式 :PS_STYLE_MASK 位(线型)+ PS_ENDCAP_MASK 位(端点)+ PS_JOIN_MASK 位(连接)+ PS_TYPE_MASK 位(类型) |
pStyle |
样式数组指针。默认样式指向 pen.c 的静态数组;PS_USERSTYLE 指向内核池拷贝 |
dwStyleCount |
样式数组元素个数(PS_USERSTYLE 时为用户提供;默认样式为内建数组长度) |
jJoin |
几何宽线的连接样式(当前代码未单独写入,连接样式实际从 ulPenStyle 提取) |
jEndCap |
几何宽线的端点样式(同上) |
iBrushStyle |
画刷样式(BS_SOLID / BS_HATCHED / BS_PATTERN ...),即 ExtCreatePen 的 lplb->lbStyle |
ulStyleSize |
样式数组元素之和(虚线/点线一个周期的总长度),IntGdiExtCreatePen 末尾累加 |
ListHead |
链表节点(对象挂接用) |
注意:结构注释中的
0x06c、0x070、0x080等偏移标注来自冯远书中的Windows 2000 布局,仅供参考,ReactOS 实际偏移以编译结果为准。
2.3 BRUSH_ATTR(画笔颜色存放处)
ntgdihdl.h(file:///d:/reactos/win32ss/include/ntgdihdl.h) 第 353 行:
c
typedef struct _BRUSH_ATTR /* Used with pen too. */
{
FLONG AttrFlags;
COLORREF lbColor;
} BRUSH_ATTR, *PBRUSH_ATTR;
注释明确写着"画笔也使用它 "。IntGdiExtCreatePen 设置 pbrushPen->BrushAttr.lbColor = ulColor;
即画笔颜色。PEN_vInit 把 pBrushAttr 指向内嵌 BrushAttr,构成
"pBrushAttr → 当前生效属性"的双层结构:若画笔被跨进程共享并需要不同的颜色/属性时,
可把 pBrushAttr 换到进程池(GdiPoolFree(GetBrushAttrPool(), ...),见 brush.cpp 的析构与
vReleaseAttribute)。
2.4 flAttrs 标志位(BR_IS_*)
brush.h 中与画笔相关的标志位:
c
#define BR_IS_SOLID 0x00000010 /* Solid brush */
#define BR_IS_NULL 0x00000100 /* Null/hollow brush */
#define BR_IS_PEN 0x00000400 /* Pen */
#define BR_IS_OLDSTYLEPEN 0x00000800 /* Geometric pen */
#define BR_IS_DEFAULTSTYLE 0x00004000
#define BR_IS_INSIDEFRAME 0x00010000
IntGdiExtCreatePen 对 flAttrs 的赋值逻辑:
- 老式笔:
flAttrs = BR_IS_OLDSTYLEPEN;扩展笔:flAttrs = BR_IS_PEN; PS_NULL→ 追加BR_IS_NULL;PS_SOLID→ 追加BR_IS_SOLID;PS_ALTERNATE/PS_DOT/PS_DASH/PS_DASHDOT/PS_DASHDOTDOT→
追加BR_IS_SOLID | BR_IS_DEFAULTSTYLE(样式由内建静态数组提供,故标记"默认样式");PS_INSIDEFRAME→ 追加BR_IS_SOLID | BR_IS_INSIDEFRAME;PS_USERSTYLE→ 追加BR_IS_SOLID(不做 DEFAULTSTYLE 标记,因为样式数组来自用户)。
这些标志在绘制链路中起决定作用:
BR_IS_NULL:line.c 中if (!(pbrLine->flAttrs & BR_IS_NULL))决定是否真的画线;BR_IS_OLDSTYLEPEN:IntIsEffectiveWidePen与PEN_GetObject判断老式/扩展笔;BR_IS_DEFAULTSTYLE:PEN_GetObject计算elpNumEntries时把默认样式记作 0;
brush.cpp 析构时不释放默认样式(静态数组)而只释放用户样式。
2.5 PS_* 样式 / 端点 / 连接常量(wingdi.h)
wingdi.h(file:///d:/reactos/sdk/include/psdk/wingdi.h) 中的定义:
c
#define PS_GEOMETRIC 65536 /* 0x00010000 */
#define PS_COSMETIC 0
#define PS_ALTERNATE 8
#define PS_SOLID 0
#define PS_DASH 1
#define PS_DOT 2
#define PS_DASHDOT 3
#define PS_DASHDOTDOT 4
#define PS_NULL 5
#define PS_USERSTYLE 7
#define PS_INSIDEFRAME 6
#define PS_ENDCAP_ROUND 0
#define PS_ENDCAP_SQUARE 256 /* 0x00000100 */
#define PS_ENDCAP_FLAT 512 /* 0x00000200 */
#define PS_JOIN_BEVEL 4096 /* 0x00001000 */
#define PS_JOIN_MITER 8192 /* 0x00002000 */
#define PS_JOIN_ROUND 0
#define PS_JOIN_MASK 0x0000F000
#define PS_STYLE_MASK 15 /* 0x0000000F */
#define PS_ENDCAP_MASK 3840 /* 0x00000F00 */
#define PS_TYPE_MASK 983040 /* 0x000F0000 */
注意几个"0 即默认值"的陷阱:
PS_SOLID == 0、PS_COSMETIC == 0、PS_ENDCAP_ROUND == 0、PS_JOIN_ROUND == 0------
与"无标志"同值,因此校验时必须用掩码(& PS_STYLE_MASK等)而不能直接比较;PS_ALTERNATE (8)数值上大于PS_INSIDEFRAME (6),所以 gdi32 的CreatePen
在范围钳制时会把 ALTERNATE 归为非法并降级为PS_SOLID;PS_GEOMETRIC (0x10000)与PS_TYPE_MASK (0xF0000)位于高位,与低 4 位线型、
8-11 位端点、12-15 位连接互不冲突,可以|组合成一个 32 位ulPenStyle。
掩码布局(32 位 dwPenStyle):
bit 0-3 线型 PS_SOLID/DASH/DOT/DASHDOT/DASHDOTDOT/NULL/INSIDEFRAME/USERSTYLE/ALTERNATE
bit 8-11 端点 PS_ENDCAP_ROUND/SQUARE/FLAT
bit 12-15 连接 PS_JOIN_ROUND/BEVEL/MITER
bit 16-19 类型 PS_COSMETIC/PS_GEOMETRIC
2.6 LOGPEN 与 EXTLOGPEN(用户可见结构)
wingdi.h 第 2290、2387 行:
c
typedef struct tagLOGPEN {
UINT lopnStyle;
POINT lopnWidth;
COLORREF lopnColor;
} LOGPEN,*PLOGPEN,*LPLOGPEN;
typedef struct tagEXTLOGPEN {
DWORD elpPenStyle;
DWORD elpWidth;
UINT elpBrushStyle;
COLORREF elpColor;
ULONG_PTR elpHatch;
DWORD elpNumEntries;
DWORD elpStyleEntry[1];
} EXTLOGPEN,*PEXTLOGPEN,*LPEXTLOGPEN,*NPEXTLOGPEN;
LOGPEN是CreatePen/CreatePenIndirect的落点:lopnWidth是POINT(只用.x表示宽度),
lopnColor即颜色;EXTLOGPEN是ExtCreatePen的落点:elpWidth是DWORD(几何笔世界单位),
elpBrushStyle对应LOGBRUSH.lbStyle,elpHatch对应LOGBRUSH.lbHatch,
elpStyleEntry[]为柔性数组存放用户样式。PEN_GetObject正是按老式/扩展笔分别填充这两个结构(见第 6 章)。
3. 内部辅助函数
3.1 PEN_vInit(pen.c:16-23)
c
static
VOID
PEN_vInit(
PPEN ppen)
{
/* Start with kmode brush attribute */
ppen->pBrushAttr = &ppen->BrushAttr;
}
- 签名 :
static VOID PEN_vInit(PPEN ppen)------静态函数,仅本文件内调用。 - 作用 :把画笔对象的
pBrushAttr指针指向对象内嵌 的BrushAttr字段。
注释"Start with kmode brush attribute"说明初始状态是"内核模式画刷属性",
即属性直接内嵌在对象里,无需从进程 GDI 池(GetBrushAttrPool)单独分配。 - 使用方式 :被
PEN_AllocPenWithHandle与PEN_AllocExtPenWithHandle在对象分配成功后调用。 - 注意事项 :若后续画笔被跨进程共享(share),可能把
pBrushAttr指向进程池中的独立属性块,
此时析构函数BRUSH::~BRUSH会先GdiPoolFree释放它(见 brush.cpp:59-63)。
3.2 PEN_AllocPenWithHandle(pen.c:25-40)
c
PBRUSH
NTAPI
PEN_AllocPenWithHandle(
VOID)
{
PPEN ppen;
ppen = (PBRUSH)GDIOBJ_AllocObjWithHandle(GDILoObjType_LO_PEN_TYPE, sizeof(PEN));
if (ppen == NULL)
{
return NULL;
}
PEN_vInit(ppen);
return ppen;
}
- 签名 :
PBRUSH NTAPI PEN_AllocPenWithHandle(VOID)------注意返回类型是PBRUSH,
再次印证 PEN 与 BRUSH 同构。 - 作用 :从 GDI 句柄表分配一个老式(简单)笔 对象:
- 调用
GDIOBJ_AllocObjWithHandle(GDILoObjType_LO_PEN_TYPE, sizeof(PEN))------
以LO_PEN_TYPE(低级对象类型)登记并分配sizeof(PEN)(即 BRUSH)大小的对象,
同时从句柄表取得句柄存入BaseObject.hHmgr; - 失败返回
NULL(调用方IntGdiExtCreatePen随即设置ERROR_NOT_ENOUGH_MEMORY); - 成功则
PEN_vInit初始化属性指针。
- 调用
- 使用方式 :仅在
IntGdiExtCreatePen的bOldStylePen == TRUE分支调用
(即来自NtGdiCreatePen的路径)。
3.3 PEN_AllocExtPenWithHandle(pen.c:42-57)
c
PBRUSH
NTAPI
PEN_AllocExtPenWithHandle(
VOID)
{
PPEN ppen;
ppen = (PBRUSH)GDIOBJ_AllocObjWithHandle(GDILoObjType_LO_EXTPEN_TYPE, sizeof(PEN));
if (ppen == NULL)
{
return NULL;
}
PEN_vInit(ppen);
return ppen;
}
与 3.2 完全对称,唯一区别是对象类型为 GDILoObjType_LO_EXTPEN_TYPE。
这一个类型位的差别决定了后续:
PEN_ShareLockPen的类型校验分支(两种类型都接受);PEN_GetObject返回LOGPEN还是EXTLOGPEN(按flAttrs & BR_IS_OLDSTYLEPEN判断,见第 6 章);PATH_WidenPathEx用GDI_HANDLE_GET_TYPE判断对象类型(GDI_OBJECT_TYPE_PEN/
GDI_OBJECT_TYPE_EXTPEN)以决定读取lopnStyle还是elpPenStyle(见 7.5 节)。
3.4 PEN_ShareLockPen(pen.c:59-70)
c
PBRUSH
FASTCALL
PEN_ShareLockPen(HPEN hobj)
{
if ((GDI_HANDLE_GET_TYPE(hobj) != GDILoObjType_LO_PEN_TYPE) &&
(GDI_HANDLE_GET_TYPE(hobj) != GDILoObjType_LO_EXTPEN_TYPE))
{
return NULL;
}
return (PBRUSH)GDIOBJ_ReferenceObjectByHandle(hobj, GDIObjType_BRUSH_TYPE);
}
- 签名 :
PBRUSH FASTCALL PEN_ShareLockPen(HPEN hobj)------FASTCALL 约定,按句柄取对象。 - 作用 :把画笔句柄"共享锁定"为对象指针:
- 先用
GDI_HANDLE_GET_TYPE(hobj)校验句柄确为LO_PEN_TYPE或LO_EXTPEN_TYPE,
否则返回NULL; - 调用
GDIOBJ_ReferenceObjectByHandle(hobj, GDIObjType_BRUSH_TYPE)------
以 BRUSH 对象类型 增加引用计数并返回对象指针。这里显式传BRUSH_TYPE
正是 PEN 复用 BRUSH 布局的直接体现:画笔句柄可以放心地按画刷对象来引用。
- 先用
- 使用方式 :
IntGdiSetSolidPenColor(改颜色前取对象);dcobjs.c的DC_vUpdateLineBrush(选中新笔时换 pbrLine)。
- 注意事项 :返回的指针必须配对
PEN_ShareUnlockPen(即GDIOBJ_vDereferenceObject)释放引用。
3.5 PEN_UnlockPen / PEN_ShareUnlockPen 宏(pen.h:17-18)
c
#define PEN_UnlockPen(pPenObj) GDIOBJ_vUnlockObject((POBJ)pPenObj)
#define PEN_ShareUnlockPen(ppen) GDIOBJ_vDereferenceObject((POBJ)ppen)
PEN_UnlockPen:撤销"独占锁"(对象创建后持有的锁)。IntGdiExtCreatePen分配对象后
由GDIOBJ_AllocObjWithHandle持有独占锁,字段初始化完成后调用PEN_UnlockPen解锁,
使句柄可被其他进程/线程引用;PEN_ShareUnlockPen:撤销PEN_ShareLockPen增加的共享引用。
二者语义不同:前者是"解锁对象(降级为可共享)",后者是"减少一次共享引用"。
3.6 AddPenLinesBounds(声明于 pen.h:31,实现于 line.c:19)
c
VOID FASTCALL AddPenLinesBounds(PDC, int, POINT *);
- 虽声明在 pen.h,但实现在 line.c(file:///d:/reactos/win32ss/gdi/ntgdi/line.c) 第 19 行。
- 作用:当 DC 处于累积模式(
DC_ACCUM_APP/DC_ACCUM_WMGR)时,把折线各点加入
"笔线边界"(用于窗口管理器累积绘制时的边界合并)。IntGdiLineTo与IntGdiPolyline
在正式绘制前调用它。
4. 创建流程
4.1 用户态入口 CreatePen(gdi32/objects/pen.c)
c
HPEN WINAPI
CreatePen(
int nPenStyle,
int nWidth,
COLORREF crColor)
{
if (nPenStyle < PS_SOLID) nPenStyle = PS_SOLID;
if (nPenStyle > PS_DASHDOTDOT)
{
if (nPenStyle == PS_NULL) return GetStockObject(NULL_PEN);
if (nPenStyle != PS_INSIDEFRAME) nPenStyle = PS_SOLID;
}
return NtGdiCreatePen(nPenStyle, nWidth, crColor, NULL);
}
行为要点:
- 样式钳制:小于
PS_SOLID一律按实线;大于PS_DASHDOTDOT时,
PS_NULL直接返回库存空笔(不再进内核)、PS_INSIDEFRAME放行、其余(含
PS_USERSTYLE、PS_ALTERNATE)降级为PS_SOLID------因为简单笔不支持自定义样式; CreatePenIndirect只是解包LOGPEN后调用CreatePen(注意:Windows 系实现
不检查lplgpn是否为 NULL,直接解引用,可能崩溃,与 2K/XP/Vista 行为一致);- 最终调用系统调用
NtGdiCreatePen(nPenStyle, nWidth, crColor, NULL),
第 4 个参数hbr恒为 NULL。
4.2 用户态入口 ExtCreatePen(gdi32/objects/brush.c)
c
HPEN APIENTRY
ExtCreatePen(DWORD dwPenStyle,
DWORD dwWidth,
CONST LOGBRUSH *lplb,
DWORD dwStyleCount,
CONST DWORD *lpStyle)
{
...
if ((dwPenStyle & PS_STYLE_MASK) == PS_USERSTYLE)
{
if(!lpStyle) { SetLastError(ERROR_INVALID_PARAMETER); return 0; }
}
else if ((dwPenStyle & PS_STYLE_MASK) == PS_INSIDEFRAME &&
(dwPenStyle & PS_TYPE_MASK) != PS_GEOMETRIC)
{
SetLastError(ERROR_INVALID_PARAMETER); return 0;
}
else if ((dwPenStyle & PS_STYLE_MASK) == PS_ALTERNATE &&
(dwPenStyle & PS_TYPE_MASK) != PS_COSMETIC)
{
SetLastError(ERROR_INVALID_PARAMETER); return 0;
}
else
{
if (dwStyleCount || lpStyle)
{
SetLastError(ERROR_INVALID_PARAMETER); return 0;
}
}
...
hPen = NtGdiExtCreatePen(dwPenStyle, dwWidth, lbStyle, lplb->lbColor,
lplb->lbHatch, (ULONG_PTR)pConvertedInfo,
dwStyleCount, (PULONG)lpStyle,
ConvertedInfoSize, FALSE, NULL);
...
}
行为要点(预校验,避免无谓的系统调用):
PS_USERSTYLE必须携带样式数组指针;PS_INSIDEFRAME只允许几何笔(Windows 规则:框架内样式仅几何笔合法);PS_ALTERNATE只允许美容笔;- 其余样式禁止携带
dwStyleCount/lpStyle; BS_DIBPATTERN类会先把用户 DIB 句柄GlobalLock并做ConvertBitmapInfo转换
(ConvertedInfoSize作为cjDIB传入内核,内核据此ProbeForRead);- 调用内核时
bOldStylePen = FALSE(扩展笔)、hBrush = NULL。
4.3 NtGdiCreatePen(pen.c:355-380)
c
HPEN
APIENTRY
NtGdiCreatePen(
INT PenStyle,
INT Width,
COLORREF Color,
IN HBRUSH hbr)
{
if ((PenStyle < PS_SOLID) ||( PenStyle > PS_INSIDEFRAME))
{
EngSetLastError(ERROR_INVALID_PARAMETER);
return NULL;
}
return IntGdiExtCreatePen(PenStyle,
Width,
BS_SOLID,
Color,
0,
0,
0,
NULL,
0,
TRUE,
hbr);
}
- 签名 :
HPEN APIENTRY NtGdiCreatePen(INT PenStyle, INT Width, COLORREF Color, IN HBRUSH hbr)。 - 参数 :
PenStyle仅允许[PS_SOLID(0) .. PS_INSIDEFRAME(6)]闭区间
(排除了PS_USERSTYLE=7与PS_ALTERNATE=8);hbr保留参数恒为 NULL。 - 作用 :简单笔的极薄封装------校验后原样转发给
IntGdiExtCreatePen:ulBrushStyle = BS_SOLID(简单笔永远是纯色);- 样式数组为
0, NULL; bOldStylePen = TRUE→ 分配LO_PEN_TYPE对象并标记BR_IS_OLDSTYLEPEN。
- 注意事项 :
Width为INT,可为负。IntGdiExtCreatePen开头的
dwWidth = abs(dwWidth)会把负数取绝对值(Windows 文档:宽度取绝对值)。
宽度 0 的老式笔=1 像素笔(除非样式为非实线时强制dwWidth = 1,见 4.5.2)。
4.4 NtGdiExtCreatePen(pen.c:382-514)
c
HPEN
APIENTRY
NtGdiExtCreatePen(
DWORD dwPenStyle,
DWORD ulWidth,
IN ULONG ulBrushStyle,
IN ULONG ulColor,
IN ULONG_PTR ulClientHatch,
IN ULONG_PTR ulHatch,
DWORD dwStyleCount,
PULONG pUnsafeStyle,
IN ULONG cjDIB,
IN BOOL bOldStylePen,
IN OPTIONAL HBRUSH hBrush)
参数说明:
| 参数 | 含义 |
|---|---|
dwPenStyle |
完整笔样式(线型 |
ulWidth |
宽度(几何笔为世界单位) |
ulBrushStyle |
画刷样式 BS_*(BS_SOLID/BS_HATCHED/BS_PATTERN/BS_DIBPATTERN/BS_DIBPATTERNPT/BS_NULL) |
ulColor |
颜色(纯色笔) |
ulClientHatch |
客户端 hatch(LOGBRUSH.lbHatch) |
ulHatch |
图案 DIB 指针(BS_PATTERN 类时为用户指针,需探测) |
dwStyleCount |
用户样式元素个数 |
pUnsafeStyle |
用户态指针的样式数组(非安全指针,必须探测复制) |
cjDIB |
DIB 字节数(供 ProbeForRead) |
bOldStylePen |
是否老式笔(gdi32 的 CreatePen→TRUE,ExtCreatePen→FALSE) |
hBrush |
保留参数(OPTIONAL,当前恒为 NULL) |
处理流程(按源码顺序):
-
样式个数边界:
cif ((int)dwStyleCount < 0) return 0; if (dwStyleCount > 16) { EngSetLastError(ERROR_INVALID_PARAMETER); return 0; }把 DWORD 按有符号判负(堵住超大值),上限 16 个元素。
-
美容笔强制纯色:
cif (((dwPenStyle & PS_TYPE_MASK) == PS_COSMETIC) && (ulBrushStyle != BS_SOLID)) { EngSetLastError(ERROR_INVALID_PARAMETER); return 0; }美容笔只能是实心纯色(宽度恒 1 像素)。
-
空笔捷径:
cif (((dwPenStyle & PS_STYLE_MASK) == PS_NULL) || (ulBrushStyle == BS_NULL)) return StockObjects[NULL_PEN];返回库存
NULL_PEN(全局共享对象,无需分配)。 -
画刷样式归类:
cif ((ulBrushStyle == BS_PATTERN) || (ulBrushStyle == BS_DIBPATTERN) || (ulBrushStyle == BS_DIBPATTERNPT)) ulColor = 0; // 图案笔:颜色无意义,清零 else if ((ulBrushStyle != BS_SOLID) && (ulBrushStyle != BS_HATCHED)) { EngSetLastError(ERROR_INVALID_PARAMETER); return 0; }图案类笔不携带颜色;其余只接受
BS_SOLID/BS_HATCHED。 -
非 USERSTYLE 清空样式参数:
cif ((dwPenStyle & PS_STYLE_MASK) != PS_USERSTYLE) { dwStyleCount = 0; pUnsafeStyle = NULL; } -
样式数组安全复制(核心安全逻辑):
cif (dwStyleCount > 0) { if (pUnsafeStyle == NULL) { EngSetLastError(ERROR_INVALID_PARAMETER); return 0; } pSafeStyle = ExAllocatePoolWithTag(NonPagedPool, dwStyleCount * sizeof(DWORD), GDITAG_PENSTYLE); if (!pSafeStyle) { SetLastNtError(ERROR_NOT_ENOUGH_MEMORY); return 0; } _SEH2_TRY { ProbeForRead(pUnsafeStyle, dwStyleCount * sizeof(DWORD), 1); RtlCopyMemory(pSafeStyle, pUnsafeStyle, dwStyleCount * sizeof(DWORD)); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END if(!NT_SUCCESS(Status)) { SetLastNtError(Status); ExFreePoolWithTag(pSafeStyle, GDITAG_PENSTYLE); return 0; } }要点:
- 分配非分页池 (
NonPagedPool)并以GDITAG_PENSTYLE标记------样式数组可能被
DPC/中断级路径读取,不能分页; ProbeForRead+RtlCopyMemory包在 SEH 里,用户指针非法(坏地址/越界)时
转为异常码返回,绝不 crash 内核;- 所有权转移 :成功后
pSafeStyle不再释放------它成为画笔对象的一部分
(IntGdiExtCreatePen存入pStyle),由BRUSH::~BRUSH在对象销毁时释放
(brush.cpp:73-76,且仅在非BR_IS_DEFAULTSTYLE时释放)。
- 分配非分页池 (
-
BS_PATTERN 的 DIB 探测:
cif (ulBrushStyle == BS_PATTERN) { _SEH2_TRY { ProbeForRead((PVOID)ulHatch, cjDIB, 1); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END if(!NT_SUCCESS(Status)) { SetLastNtError(Status); if (pSafeStyle) ExFreePoolWithTag(pSafeStyle, GDITAG_PENSTYLE); return 0; } }只做探测 (验证可读),不复制------
IntGdiExtCreatePen把ulHatch原样存入hbmClient(注释// FIXME: Copy the bitmap first ?表明当前尚未复制,是已知待办)。 -
调用内部中枢并清理:
chPen = IntGdiExtCreatePen(dwPenStyle, ulWidth, ulBrushStyle, ulColor, ulClientHatch, ulHatch, dwStyleCount, pSafeStyle, cjDIB, bOldStylePen, hBrush); if (!hPen && pSafeStyle) ExFreePoolWithTag(pSafeStyle, GDITAG_PENSTYLE); return hPen;成功时
pSafeStyle留在笔对象里;失败(IntGdiExtCreatePen返回 NULL 且已自清理)时在此回收样式池。
4.5 IntGdiExtCreatePen 详解(pen.c:72-269,创建中枢)
c
HPEN APIENTRY
IntGdiExtCreatePen(
DWORD dwPenStyle,
DWORD dwWidth,
IN ULONG ulBrushStyle,
IN ULONG ulColor,
IN ULONG_PTR ulClientHatch,
IN ULONG_PTR ulHatch,
DWORD dwStyleCount,
PULONG pStyle,
IN ULONG cjDIB,
IN BOOL bOldStylePen,
IN OPTIONAL HBRUSH hbrush)
4.5.1 默认样式静态数组
c
static ULONG aulStyleAlternate[] = { 1, 1 };
static ULONG aulStyleDash[] = { 6, 2 };
static ULONG aulStyleDot[] = { 1, 1 };
static ULONG aulStyleDashDot[] = { 3, 2, 1, 2 };
static ULONG aulStyleDashDotDot[] = { 3, 1, 1, 1, 1, 1 };
- 这些数组是静态共享 的,绝不修改(只读),因此多个笔可以安全地让
pStyle指向同一数组; - 数值单位是"逻辑单位":
PS_DASH= 画 6 停 2;PS_DOT= 画 1 停 1;
PS_DASHDOT= 画 3 停 2 画 1 停 2;PS_DASHDOTDOT= 画 3 停 1 画 1 停 1 画 1 停 1;
PS_ALTERNATE= 每像素交替(画 1 停 1)。
4.5.2 预处理与参数校验
c
dwWidth = abs(dwWidth);
if ( (dwPenStyle & PS_STYLE_MASK) == PS_NULL)
{
return StockObjects[NULL_PEN];
}
if (bOldStylePen)
pbrushPen = PEN_AllocPenWithHandle();
else
pbrushPen = PEN_AllocExtPenWithHandle();
if (!pbrushPen)
{
EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
DPRINT("Can't allocate pen\n");
return 0;
}
hPen = pbrushPen->BaseObject.hHmgr;
if (bOldStylePen)
{
// If nWidth is zero, the pen is a single pixel wide, regardless of the current transformation.
if (!dwWidth && (dwPenStyle & PS_STYLE_MASK) != PS_SOLID)
dwWidth = 1;
}
else
{
switch (dwPenStyle & PS_ENDCAP_MASK)
{
case PS_ENDCAP_ROUND: case PS_ENDCAP_SQUARE: case PS_ENDCAP_FLAT:
break;
default:
goto ExitCleanup;
}
switch (dwPenStyle & PS_JOIN_MASK)
{
case PS_JOIN_ROUND: case PS_JOIN_BEVEL: case PS_JOIN_MITER:
break;
default:
goto ExitCleanup;
}
switch (dwPenStyle & PS_TYPE_MASK)
{
case PS_COSMETIC:
if (dwWidth != 1 || ulBrushStyle != BS_SOLID)
goto ExitCleanup;
break;
case PS_GEOMETRIC:
break;
default:
goto ExitCleanup;
}
}
校验要点:
- 宽度取绝对值(
abs); PS_NULL直接返回库存空笔;- 老式笔:宽度 0 且非实线时强制 1 (注释强调"宽度为 0 时笔恒为单像素,与当前变换无关");
实线 + 宽度 0 保持 0(等价 1 像素细线,IntIsEffectiveWidePen不成立); - 扩展笔三层校验:
- 端点必须是
ROUND/SQUARE/FLAT之一; - 连接必须是
ROUND/BEVEL/MITER之一; - 类型:
PS_COSMETIC要求dwWidth == 1 && ulBrushStyle == BS_SOLID(美容笔必须 1 像素纯色),
PS_GEOMETRIC任意; - 任一不合法 →
goto ExitCleanup(设ERROR_INVALID_PARAMETER并删除对象)。
- 端点必须是
4.5.3 字段初始化
c
pbrushPen->lWidth = dwWidth;
FLOATOBJ_SetLong(&pbrushPen->eWidth, pbrushPen->lWidth);
pbrushPen->ulPenStyle = dwPenStyle;
pbrushPen->BrushAttr.lbColor = ulColor;
pbrushPen->iBrushStyle = ulBrushStyle;
// FIXME: Copy the bitmap first ?
pbrushPen->hbmClient = (HANDLE)ulClientHatch;
pbrushPen->dwStyleCount = 0;
pbrushPen->pStyle = NULL;
pbrushPen->ulStyleSize = 0;
pbrushPen->flAttrs = bOldStylePen ? BR_IS_OLDSTYLEPEN : BR_IS_PEN;
lWidth与eWidth双份宽度:整型(逻辑/设备)与浮点(世界单位);BrushAttr.lbColor存颜色(这是PEN_GetObject、IntGdiSetSolidPenColor读写的字段);hbmClient存用户 hatch;flAttrs先打类型标记,随后按线型追加。
4.5.4 样式分发(switch PS_STYLE_MASK)
| 线型 | flAttrs 追加 | pStyle / dwStyleCount |
|---|---|---|
PS_NULL |
BR_IS_NULL |
不设样式(保持 NULL/0) |
PS_SOLID |
BR_IS_SOLID |
不设样式 |
PS_ALTERNATE |
`BR_IS_SOLID | BR_IS_DEFAULTSTYLE` |
PS_DOT |
`BR_IS_SOLID | BR_IS_DEFAULTSTYLE` |
PS_DASH |
`BR_IS_SOLID | BR_IS_DEFAULTSTYLE` |
PS_DASHDOT |
`BR_IS_SOLID | BR_IS_DEFAULTSTYLE` |
PS_DASHDOTDOT |
`BR_IS_SOLID | BR_IS_DEFAULTSTYLE` |
PS_INSIDEFRAME |
`BR_IS_SOLID | BR_IS_INSIDEFRAME` |
PS_USERSTYLE |
BR_IS_SOLID |
用户数组(见 4.5.5) |
| 其他 | --- | DPRINT1 警告后 goto ExitCleanup |
4.5.5 PS_USERSTYLE 的合法性检查
c
case PS_USERSTYLE:
{
UINT i;
BOOL has_neg = FALSE, all_zero = TRUE;
for(i = 0; (i < dwStyleCount) && !has_neg; i++)
{
has_neg = has_neg || (((INT)(pStyle[i])) < 0);
all_zero = all_zero && (pStyle[i] == 0);
}
if(all_zero || has_neg)
{
goto ExitCleanup;
}
}
/* FIXME: What style here? */
pbrushPen->flAttrs |= BR_IS_SOLID;
pbrushPen->dwStyleCount = dwStyleCount;
pbrushPen->pStyle = pStyle;
break;
- 规则:样式数组不允许全零 (否则永无落笔,无意义),也不允许负数 (长度必须非负);
任一违规 →ERROR_INVALID_PARAMETER; - 注意
pStyle此时已是内核安全拷贝(pSafeStyle)或默认静态数组; /* FIXME: What style here? */是源码遗留注释:USERSTYLE 笔仍被标记为BR_IS_SOLID
(因为绘制时样式由数组驱动,底色实心)。
4.5.6 ulStyleSize 累加与断言
c
if (pbrushPen->pStyle != NULL)
{
for (i = 0; i < pbrushPen->dwStyleCount; i++)
{
pbrushPen->ulStyleSize += pbrushPen->pStyle[i];
}
}
NT_ASSERT((pbrushPen->dwStyleCount == 0) || (pbrushPen->pStyle != NULL));
PEN_UnlockPen(pbrushPen);
return hPen;
ulStyleSize= 样式数组元素之和,即一个"画-停"周期总长度(供描边时推进样式状态机);- 断言保证"有样式个数必有样式指针";
- 成功路径最后
PEN_UnlockPen解锁对象并返回hPen。
4.5.7 失败路径 ExitCleanup
c
ExitCleanup:
EngSetLastError(ERROR_INVALID_PARAMETER);
pbrushPen->pStyle = NULL;
GDIOBJ_vDeleteObject(&pbrushPen->BaseObject);
return NULL;
- 所有参数错误统一落到这里:设
ERROR_INVALID_PARAMETER、清空pStyle
(避免把pSafeStyle交给析构重复释放------NtGdiExtCreatePen会在外部回收它)、
直接删除 GDI 对象、返回 NULL。 - 内存分配失败则走另一条路:
ERROR_NOT_ENOUGH_MEMORY+ 返回 0(在 4.5.2 中)。
4.6 简单笔 vs 几何笔 vs 样式笔对比
| 维度 | 简单笔(CreatePen / 老式) | 几何笔(PS_GEOMETRIC) | 美容笔(PS_COSMETIC) |
|---|---|---|---|
| 对象类型 | LO_PEN_TYPE |
LO_EXTPEN_TYPE |
LO_EXTPEN_TYPE |
| 宽度语义 | 设备单位,0 或 1 = 单像素 | 世界单位(受世界变换影响) | 恒 1 像素 |
| 颜色 | BrushAttr.lbColor |
可搭配图案/影线画刷 | 必须 BS_SOLID 纯色 |
| 端点/连接 | 不适用(简单笔无此概念) | PS_ENDCAP_*/PS_JOIN_* 生效 |
仅有样式 |
| 宽笔判定 | 宽度 > 1 即宽笔 | 宽度 > 1 即宽笔 | 永远窄笔 |
| 样式 | 仅内建 7 种 | 内建或 PS_USERSTYLE 用户数组 |
内建或用户数组 |
| 宽化路径 | 支持 | 支持 | 不支持(PATH_WidenPathEx 拒绝) |
4.7 创建调用链(mermaid)
#mermaid-svg-0KmtWqNF52Hzdyna{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-0KmtWqNF52Hzdyna .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0KmtWqNF52Hzdyna .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0KmtWqNF52Hzdyna .error-icon{fill:#552222;}#mermaid-svg-0KmtWqNF52Hzdyna .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0KmtWqNF52Hzdyna .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0KmtWqNF52Hzdyna .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0KmtWqNF52Hzdyna .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0KmtWqNF52Hzdyna .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0KmtWqNF52Hzdyna .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0KmtWqNF52Hzdyna .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0KmtWqNF52Hzdyna .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0KmtWqNF52Hzdyna .marker.cross{stroke:#333333;}#mermaid-svg-0KmtWqNF52Hzdyna svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0KmtWqNF52Hzdyna p{margin:0;}#mermaid-svg-0KmtWqNF52Hzdyna .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0KmtWqNF52Hzdyna .cluster-label text{fill:#333;}#mermaid-svg-0KmtWqNF52Hzdyna .cluster-label span{color:#333;}#mermaid-svg-0KmtWqNF52Hzdyna .cluster-label span p{background-color:transparent;}#mermaid-svg-0KmtWqNF52Hzdyna .label text,#mermaid-svg-0KmtWqNF52Hzdyna span{fill:#333;color:#333;}#mermaid-svg-0KmtWqNF52Hzdyna .node rect,#mermaid-svg-0KmtWqNF52Hzdyna .node circle,#mermaid-svg-0KmtWqNF52Hzdyna .node ellipse,#mermaid-svg-0KmtWqNF52Hzdyna .node polygon,#mermaid-svg-0KmtWqNF52Hzdyna .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0KmtWqNF52Hzdyna .rough-node .label text,#mermaid-svg-0KmtWqNF52Hzdyna .node .label text,#mermaid-svg-0KmtWqNF52Hzdyna .image-shape .label,#mermaid-svg-0KmtWqNF52Hzdyna .icon-shape .label{text-anchor:middle;}#mermaid-svg-0KmtWqNF52Hzdyna .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0KmtWqNF52Hzdyna .rough-node .label,#mermaid-svg-0KmtWqNF52Hzdyna .node .label,#mermaid-svg-0KmtWqNF52Hzdyna .image-shape .label,#mermaid-svg-0KmtWqNF52Hzdyna .icon-shape .label{text-align:center;}#mermaid-svg-0KmtWqNF52Hzdyna .node.clickable{cursor:pointer;}#mermaid-svg-0KmtWqNF52Hzdyna .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0KmtWqNF52Hzdyna .arrowheadPath{fill:#333333;}#mermaid-svg-0KmtWqNF52Hzdyna .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0KmtWqNF52Hzdyna .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0KmtWqNF52Hzdyna .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0KmtWqNF52Hzdyna .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0KmtWqNF52Hzdyna .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0KmtWqNF52Hzdyna .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0KmtWqNF52Hzdyna .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0KmtWqNF52Hzdyna .cluster text{fill:#333;}#mermaid-svg-0KmtWqNF52Hzdyna .cluster span{color:#333;}#mermaid-svg-0KmtWqNF52Hzdyna 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-0KmtWqNF52Hzdyna .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0KmtWqNF52Hzdyna rect.text{fill:none;stroke-width:0;}#mermaid-svg-0KmtWqNF52Hzdyna .icon-shape,#mermaid-svg-0KmtWqNF52Hzdyna .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0KmtWqNF52Hzdyna .icon-shape p,#mermaid-svg-0KmtWqNF52Hzdyna .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0KmtWqNF52Hzdyna .icon-shape .label rect,#mermaid-svg-0KmtWqNF52Hzdyna .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0KmtWqNF52Hzdyna .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0KmtWqNF52Hzdyna .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0KmtWqNF52Hzdyna :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
失败
通过
应用: CreatePen
gdi32 objects/pen.c CreatePen
样式钳制/PS_NULL 走库存
应用: ExtCreatePen
gdi32 objects/brush.c ExtCreatePen
预校验 + DIB 转换
NtGdiCreatePen
范围校验 PS_SOLID..PS_INSIDEFRAME
NtGdiExtCreatePen
样式个数<=16 / 美容笔强制纯色
SEH 复制样式数组到 NonPagedPool
BS_PATTERN 探测 DIB
IntGdiExtCreatePen
bOldStylePen=TRUE
bOldStylePen?
PEN_AllocPenWithHandle
LO_PEN_TYPE
PEN_AllocExtPenWithHandle
LO_EXTPEN_TYPE
PEN_vInit: pBrushAttr=&BrushAttr
三层校验
ENDCAP / JOIN / TYPE
ExitCleanup:
ERROR_INVALID_PARAMETER
GDIOBJ_vDeleteObject
字段初始化 lWidth/eWidth/ulPenStyle
BrushAttr.lbColor/flAttrs
线型分发 switch PS_STYLE_MASK
内建样式数组
- BR_IS_DEFAULTSTYLE
USERSTYLE
检查全零/负数
SOLID/NULL/INSIDEFRAME
累加 ulStyleSize
NT_ASSERT + PEN_UnlockPen
返回 HPEN
5. 颜色修改
5.1 IntGdiSetSolidPenColor(pen.c:271-286)
c
VOID
FASTCALL
IntGdiSetSolidPenColor(HPEN hPen, COLORREF Color)
{
PBRUSH pbrPen;
pbrPen = PEN_ShareLockPen(hPen);
if (pbrPen)
{
if (pbrPen->flAttrs & BR_IS_SOLID)
{
pbrPen->BrushAttr.lbColor = Color & 0xFFFFFF;
}
PEN_ShareUnlockPen(pbrPen);
}
}
- 签名 :
VOID FASTCALL IntGdiSetSolidPenColor(HPEN hPen, COLORREF Color)。 - 作用 :按句柄把纯色笔的颜色改为
Color:PEN_ShareLockPen共享锁定(非法句柄返回 NULL,静默忽略);- 仅当
flAttrs & BR_IS_SOLID才修改------非实心笔(图案/影线/空笔)不改颜色; Color & 0xFFFFFF屏蔽高位(COLORREF 只取低 24 位 RGB,忽略高位标志位);PEN_ShareUnlockPen释放引用。
- 使用方式 :这是内核内部工具,理论上配合
SetDCPenColor使用
(SetDCPenColor改 DC 属性后,若 DC 的画笔恰好是实心笔,可同步改笔对象颜色)。
经全仓库检索,当前树内没有任何调用方 (仅 pen.c 定义 + pen.h 无声明),属于"留作内部 API"
的未使用函数------这是源码事实,阅读时不要误以为它是SetDCPenColor的直接实现。
5.2 SetDCPenColor 用户态链路(gdi32/objects/dc.c:939-970)
c
COLORREF WINAPI
SetDCPenColor(_In_ HDC hdc, _In_ COLORREF crColor)
{
PDC_ATTR pdcattr;
pdcattr = GdiGetDcAttr(hdc);
if (pdcattr == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return CLR_INVALID; }
HANDLE_EMETAFDC(COLORREF, SetDCPenColor, CLR_INVALID, hdc, crColor);
crOldColor = pdcattr->ulPenClr;
pdcattr->ulPenClr = (ULONG)crColor;
if (pdcattr->crPenClr != crColor)
{
pdcattr->ulDirty_ |= DIRTY_LINE;
pdcattr->crPenClr = crColor;
}
return crOldColor;
}
要点:
- ReactOS 的 DC 属性(
PDC_ATTR)在用户态可读 (GdiGetDcAttr),
所以SetDCPenColor不走系统调用,直接写ulPenClr并置DIRTY_LINE脏标记; HANDLE_EMETAFDC宏负责在增强图元文件 DC 上同时记录 EMR;- 真正的"落笔"发生在内核绘制时:
DIRTY_LINE会随下一次绘制被
DC_vUpdateLineBrush消费(见 5.4)。
5.3 内核态 IntSetDCPenColor(dcutil.c:258-279)
c
COLORREF FASTCALL
IntSetDCPenColor(HDC hdc, COLORREF crColor)
{
COLORREF OldColor;
PDC dc;
if (!(dc = DC_LockDc(hdc)))
{
EngSetLastError(ERROR_INVALID_PARAMETER);
return CLR_INVALID;
}
OldColor = (COLORREF)dc->pdcattr->ulPenClr;
dc->pdcattr->ulPenClr = (ULONG)crColor;
if (dc->pdcattr->crPenClr != crColor)
{
dc->pdcattr->ulDirty_ |= DIRTY_LINE;
dc->pdcattr->crPenClr = crColor;
}
DC_UnlockDc(dc);
return OldColor;
}
- 供内核态用户模块 (ntuser 的
draw.c、nonclient.c、menu.c等绘制 3D 边框时)使用:
IntSetDCPenColor(hdc, IntGetSysColor(...)); - 与 gdi32 版本逻辑完全一致(锁 DC → 改
ulPenClr→ 置DIRTY_LINE),
只是发生在内核侧。
5.4 DC_vUpdateLineBrush(dcobjs.c:60-104)------"笔 → 线条画刷"的兑现点
c
VOID FASTCALL
DC_vUpdateLineBrush(PDC pdc)
{
PDC_ATTR pdcattr = pdc->pdcattr;
PBRUSH pbrLine;
/* 1. 画笔句柄是否更换 */
if (pdcattr->hpen != pdc->dclevel.pbrLine->BaseObject.hHmgr)
{
pbrLine = PEN_ShareLockPen(pdcattr->hpen);
if (pbrLine)
{
BRUSH_ShareUnlockBrush(pdc->dclevel.pbrLine); // 释放旧笔
pdc->dclevel.pbrLine = pbrLine; // 换新笔
pdcattr->ulDirty_ |= DIRTY_LINE;
}
else
{
pdcattr->hpen = pdc->dclevel.pbrLine->BaseObject.hHmgr; // 非法句柄还原
}
}
/* 2. EBRUSHOBJ 重实现 */
if (pdcattr->ulDirty_ & DIRTY_LINE)
{
EBRUSHOBJ_vUpdateFromDC(&pdc->eboLine, pdc->dclevel.pbrLine, pdc);
}
/* 3. DC_PEN 特例:用 SetDCPenColor 的颜色覆盖线条画刷 */
if (pdcattr->hpen == StockObjects[DC_PEN])
{
EBRUSHOBJ_vSetSolidRGBColor(&pdc->eboLine, pdcattr->crPenClr);
}
/* 4. 清脏 */
pdcattr->ulDirty_ &= ~(DIRTY_LINE | DC_PEN_DIRTY);
}
这就是 SetDCPenColor 的完整生效链路:
SetDCPenColor(crColor) → pdcattr->ulPenClr = crColor; DIRTY_LINE |= ...
↓(下一次 SelectObject/绘制前)
DC_vUpdateLineBrush → EBRUSHOBJ_vUpdateFromDC(&eboLine, pbrLine, pdc)
↓(若 hpen == DC_PEN 库存笔)
EBRUSHOBJ_vSetSolidRGBColor(&eboLine, crPenClr)
↓
IntEngLineTo / PATH_StrokePath ... 使用 eboLine.BrushObject 绘制
调用 DC_vUpdateLineBrush 的地方包括:IntGdiSelectBrush(dcobjs.c:305,换笔)、
IntGdiLineTo(line.c:560)、IntGdiEllipse/IntGdiRoundRect/IntGdiRectangle 等
(fillshap.c)、圆弧(arc.c)、PATH_StrokePath(path.c:2713/3060/3117)等。
另外 SetDCPenColor 之后若未发生绘制,DC_PEN_DIRTY 也会在下次 DC_vUpdateLineBrush
调用时被处理(dcobjs.c:647-648 的 if (pdcattr->ulDirty_ & (DIRTY_LINE | DC_PEN_DIRTY)))。
SetDCPenColor 链路(mermaid):
#mermaid-svg-AzBoOuRSDHu4zLq8{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-AzBoOuRSDHu4zLq8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AzBoOuRSDHu4zLq8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AzBoOuRSDHu4zLq8 .error-icon{fill:#552222;}#mermaid-svg-AzBoOuRSDHu4zLq8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AzBoOuRSDHu4zLq8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AzBoOuRSDHu4zLq8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AzBoOuRSDHu4zLq8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AzBoOuRSDHu4zLq8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AzBoOuRSDHu4zLq8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AzBoOuRSDHu4zLq8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AzBoOuRSDHu4zLq8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AzBoOuRSDHu4zLq8 .marker.cross{stroke:#333333;}#mermaid-svg-AzBoOuRSDHu4zLq8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AzBoOuRSDHu4zLq8 p{margin:0;}#mermaid-svg-AzBoOuRSDHu4zLq8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AzBoOuRSDHu4zLq8 .cluster-label text{fill:#333;}#mermaid-svg-AzBoOuRSDHu4zLq8 .cluster-label span{color:#333;}#mermaid-svg-AzBoOuRSDHu4zLq8 .cluster-label span p{background-color:transparent;}#mermaid-svg-AzBoOuRSDHu4zLq8 .label text,#mermaid-svg-AzBoOuRSDHu4zLq8 span{fill:#333;color:#333;}#mermaid-svg-AzBoOuRSDHu4zLq8 .node rect,#mermaid-svg-AzBoOuRSDHu4zLq8 .node circle,#mermaid-svg-AzBoOuRSDHu4zLq8 .node ellipse,#mermaid-svg-AzBoOuRSDHu4zLq8 .node polygon,#mermaid-svg-AzBoOuRSDHu4zLq8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AzBoOuRSDHu4zLq8 .rough-node .label text,#mermaid-svg-AzBoOuRSDHu4zLq8 .node .label text,#mermaid-svg-AzBoOuRSDHu4zLq8 .image-shape .label,#mermaid-svg-AzBoOuRSDHu4zLq8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-AzBoOuRSDHu4zLq8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AzBoOuRSDHu4zLq8 .rough-node .label,#mermaid-svg-AzBoOuRSDHu4zLq8 .node .label,#mermaid-svg-AzBoOuRSDHu4zLq8 .image-shape .label,#mermaid-svg-AzBoOuRSDHu4zLq8 .icon-shape .label{text-align:center;}#mermaid-svg-AzBoOuRSDHu4zLq8 .node.clickable{cursor:pointer;}#mermaid-svg-AzBoOuRSDHu4zLq8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AzBoOuRSDHu4zLq8 .arrowheadPath{fill:#333333;}#mermaid-svg-AzBoOuRSDHu4zLq8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AzBoOuRSDHu4zLq8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AzBoOuRSDHu4zLq8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AzBoOuRSDHu4zLq8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AzBoOuRSDHu4zLq8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AzBoOuRSDHu4zLq8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AzBoOuRSDHu4zLq8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AzBoOuRSDHu4zLq8 .cluster text{fill:#333;}#mermaid-svg-AzBoOuRSDHu4zLq8 .cluster span{color:#333;}#mermaid-svg-AzBoOuRSDHu4zLq8 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-AzBoOuRSDHu4zLq8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AzBoOuRSDHu4zLq8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-AzBoOuRSDHu4zLq8 .icon-shape,#mermaid-svg-AzBoOuRSDHu4zLq8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AzBoOuRSDHu4zLq8 .icon-shape p,#mermaid-svg-AzBoOuRSDHu4zLq8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AzBoOuRSDHu4zLq8 .icon-shape .label rect,#mermaid-svg-AzBoOuRSDHu4zLq8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AzBoOuRSDHu4zLq8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AzBoOuRSDHu4zLq8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AzBoOuRSDHu4zLq8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
应用 SetDCPenColor
gdi32 objects/dc.c
写 pdcattr->ulPenClr + DIRTY_LINE
ntuser 内核绘制
IntSetDCPenColor
dcutil.c
锁 DC 写 ulPenClr + DIRTY_LINE
DC_vUpdateLineBrush
dcobjs.c
EBRUSHOBJ_vUpdateFromDC
重实现 eboLine
hpen == DC_PEN?
EBRUSHOBJ_vSetSolidRGBColor
eboLine 颜色 = crPenClr
使用画笔自身 BrushAttr.lbColor
IntEngLineTo / PATH_StrokePath ...
6. 对象查询:PEN_GetObject(pen.c:288-350)
c
INT
APIENTRY
PEN_GetObject(PBRUSH pbrushPen, INT cbCount, PLOGPEN pBuffer)
{
PLOGPEN pLogPen;
PEXTLOGPEN pExtLogPen;
INT cbRetCount;
if (pbrushPen->flAttrs & BR_IS_OLDSTYLEPEN)
{
/* ---------- 老式(简单)笔 ---------- */
cbRetCount = sizeof(LOGPEN);
if (pBuffer)
{
if (cbCount < cbRetCount) return 0;
if (((pbrushPen->ulPenStyle & PS_STYLE_MASK) == PS_NULL) &&
(cbCount == sizeof(EXTLOGPEN)))
{
/* 特例:空笔 + 缓冲区恰好是 EXTLOGPEN 大小 → 填 EXTLOGPEN */
pExtLogPen = (PEXTLOGPEN)pBuffer;
pExtLogPen->elpPenStyle = pbrushPen->ulPenStyle;
pExtLogPen->elpWidth = 0;
pExtLogPen->elpBrushStyle = pbrushPen->iBrushStyle;
pExtLogPen->elpColor = pbrushPen->BrushAttr.lbColor;
pExtLogPen->elpHatch = 0;
pExtLogPen->elpNumEntries = 0;
cbRetCount = sizeof(EXTLOGPEN);
}
else
{
pLogPen = (PLOGPEN)pBuffer;
pLogPen->lopnWidth.x = pbrushPen->lWidth;
pLogPen->lopnWidth.y = 0;
pLogPen->lopnStyle = pbrushPen->ulPenStyle;
pLogPen->lopnColor = pbrushPen->BrushAttr.lbColor;
}
}
}
else
{
DWORD dwStyleCount = (pbrushPen->flAttrs & BR_IS_DEFAULTSTYLE) ?
0 : pbrushPen->dwStyleCount;
cbRetCount = sizeof(EXTLOGPEN) - sizeof(DWORD) + dwStyleCount * sizeof(DWORD);
if (pBuffer)
{
ULONG i;
if (cbCount < cbRetCount) return 0;
pExtLogPen = (PEXTLOGPEN)pBuffer;
pExtLogPen->elpPenStyle = pbrushPen->ulPenStyle;
pExtLogPen->elpWidth = pbrushPen->lWidth;
pExtLogPen->elpBrushStyle = pbrushPen->iBrushStyle;
pExtLogPen->elpColor = pbrushPen->BrushAttr.lbColor;
pExtLogPen->elpHatch = (ULONG_PTR)pbrushPen->hbmClient;
pExtLogPen->elpNumEntries = dwStyleCount;
for (i = 0; i < dwStyleCount; i++)
{
pExtLogPen->elpStyleEntry[i] = pbrushPen->pStyle[i];
}
}
}
return cbRetCount;
}
6.1 函数分析
- 签名 :
INT APIENTRY PEN_GetObject(PBRUSH pbrushPen, INT cbCount, PLOGPEN pBuffer)。
注意入参是已锁定的对象指针 而非句柄------由上层GreGetObject按对象类型分发后调用
(画笔对象 → 本函数;画刷对象 →BRUSH_GetObject)。 - 返回语义 :返回需要/写入的字节数;
pBuffer == NULL时只返回字节数(这是
GetObject(hPen, 0, NULL)的两步查询惯例,PATH_WidenPathEx正是先这样取大小再分配缓冲);
缓冲区过小(cbCount < cbRetCount)返回 0。
分支一:老式笔(flAttrs & BR_IS_OLDSTYLEPEN)
- 默认返回
sizeof(LOGPEN),填充LOGPEN:lopnWidth.x = lWidth(宽度),lopnWidth.y = 0;lopnStyle = ulPenStyle(完整样式,含高位的类型位);lopnColor = BrushAttr.lbColor。
- 特例 :当笔为
PS_NULL且调用方给的缓冲区恰好是sizeof(EXTLOGPEN)时,
填充EXTLOGPEN并返回sizeof(EXTLOGPEN):
elpPenStyle = ulPenStyle、elpWidth = 0、elpBrushStyle = iBrushStyle、
elpColor = lbColor、elpHatch = 0、elpNumEntries = 0。
这是为了兼容"用ExtCreatePen风格代码查询库存空笔"的场景
(GetStockObject(NULL_PEN)是老式库存笔,但调用方可能按扩展笔查询)。
分支二:扩展笔(else)
dwStyleCount的实际输出值:BR_IS_DEFAULTSTYLE时记作 0 (内建样式不导出
到elpStyleEntry),否则取pbrushPen->dwStyleCount(用户样式);cbRetCount = sizeof(EXTLOGPEN) - sizeof(DWORD) + dwStyleCount * sizeof(DWORD):
减去elpStyleEntry[1]内置的一个 DWORD,再加上实际样式个数对应的空间;- 填充
EXTLOGPEN:elpPenStyle = ulPenStyle、elpWidth = lWidth、elpBrushStyle = iBrushStyle、
elpColor = lbColor;elpHatch = (ULONG_PTR)hbmClient------把创建时暂存的用户 hatch 原样返回;elpNumEntries = dwStyleCount,随后逐项拷贝pStyle[i]到elpStyleEntry[i]。
6.2 使用场景
| 场景 | 调用链 |
|---|---|
应用 GetObject(hPen, sizeof(LOGPEN), &lp) |
gdi32 GetObject → NtGdiGetObject → 按句柄类型分发到 PEN_GetObject |
| 宽化路径取笔参数 | PATH_WidenPathEx:GreGetObject(pdcattr->hpen, 0, NULL) 取大小 → 分配 → 再次调用取数据 → 按 GDI_OBJECT_TYPE_PEN/EXTPEN 读样式与宽度(见 7.5) |
| 查询库存空笔 | 命中老式分支的 EXTLOGPEN 特例 |
7. 绘制链路与宽笔判定
7.1 宽笔判定宏 IntIsEffectiveWidePen(pen.h:33-37)
c
#define IntIsEffectiveWidePen(pbrLine) ( \
(pbrLine)->lWidth > 1 && \
((pbrLine->flAttrs & BR_IS_OLDSTYLEPEN) || \
((pbrLine)->ulPenStyle & PS_TYPE_MASK) == PS_GEOMETRIC) \
)
语义逐项拆解:
lWidth > 1:宽度必须大于 1。宽度 0/1 一律按细线处理
(即使几何笔,若宽度为 1 也走细线快路径);- 老式笔(
BR_IS_OLDSTYLEPEN) :CreatePen创建的笔宽度 > 1 即视为宽笔; - 几何笔(
PS_TYPE_MASK == PS_GEOMETRIC):扩展几何笔宽度 > 1 同样视为宽笔; - 美容笔 :恒为细线(
PS_COSMETIC要求宽度恒 1,本就无法满足lWidth > 1)。
也就是说:"宽笔" = 宽度 > 1 且(老式笔或几何笔)。这个判定结果直接决定绘制路径。
使用位置(全树):
| 位置 | 作用 |
|---|---|
line.c:208(IntGdiLineTo) |
宽笔 → 建临时路径描边;否则 IntEngLineTo |
line.c:371(IntGdiPolyline) |
宽笔 → 建临时路径描边;否则 IntEngPolyline |
fillshap.c:109(IntGdiEllipse 等) |
边框绘制分流 |
fillshap.c:680(矩形/圆角矩形边框) |
同上 |
path.c:1628(PATH_StrokePath) |
宽笔 → PATH_WidenPathEx 展开轮廓填充;否则折线化 |
7.2 line.c 中的分流实现
IntGdiLineTo(line.c:206-246,节选关键逻辑):
c
if (!(pbrLine->flAttrs & BR_IS_NULL))
{
if (IntIsEffectiveWidePen(pbrLine))
{
/* Clear the path */
PATH_Delete(dc->dclevel.hPath);
dc->dclevel.hPath = NULL;
/* Begin a path */
pPath = PATH_CreatePath(2);
dc->dclevel.flPath |= DCPATH_ACTIVE;
dc->dclevel.hPath = pPath->BaseObject.hHmgr;
IntGetCurrentPositionEx(dc, &pPath->pos);
IntLPtoDP(dc, &pPath->pos, 1);
PATH_MoveTo(dc, pPath);
PATH_LineTo(dc, XEnd, YEnd);
/* Close the path */
pPath->state = PATH_Closed;
dc->dclevel.flPath &= ~DCPATH_ACTIVE;
/* Actually stroke a path */
Ret = PATH_StrokePath(dc, pPath);
...
}
else
{
Ret = IntEngLineTo(&psurf->SurfObj, (CLIPOBJ *)&dc->co,
&dc->eboLine.BrushObject,
Points[0].x, Points[0].y,
Points[1].x, Points[1].y,
&Bounds, ROP2_TO_MIX(pdcattr->jROP2));
}
}
要点:
- 细线路径 :直接调用
IntEngLineTo/IntEngPolyline,画刷用&dc->eboLine.BrushObject
(即DC_vUpdateLineBrush维护的线条画刷)------单像素,样式数组由引擎推进; - 宽笔路径 :临时创建一个 2 点路径(
PATH_CreatePath(2)),MoveTo起点 +
LineTo终点,置PATH_Closed后交给PATH_StrokePath。注意这里手动置 Closed ,
因为PATH_StrokePath只接受闭合路径(PATH_WidenPathEx开头有
if (pPath->state != PATH_Closed)检查); - 每次宽笔绘制都要新建、删除临时路径,性能上不如细线快路径------这是宽笔的固有代价;
- 绘制前还有
AddPenLinesBounds(累积模式边界合并)与DC_vPrepareDCsForBlit。
IntGdiPolyline(line.c:371-401)完全相同的模式,只是把 N 个点逐段
PATH_LineTo 进路径后再 PATH_StrokePath。IntGdiPolyBezier 则先把贝塞尔
GDI_Bezier 展平为折线再走 IntGdiPolyline。
7.3 fillshap.c 中的使用
IntGdiEllipse、IntGdiRectangle、IntGdiRoundRect 等在画边框时同样调用
DC_vUpdateLineBrush 后用 IntIsEffectiveWidePen 分流(fillshap.c:109、680):
宽笔 → PATH_StrokePath;细线 → IntEngEllipse/IntEngRectangle 等引擎函数。
所以"椭圆/矩形边框 + 宽笔"同样会走宽化轮廓。
7.4 PATH_StrokePath(path.c:1608-1642)
c
BOOL FASTCALL
PATH_StrokePath(DC *dc, PPATH pPath)
{
...
pbrLine = dc->dclevel.pbrLine;
if (IntIsEffectiveWidePen(pbrLine))
{
pNewPath = PATH_WidenPathEx(dc, pPath);
if (pNewPath)
{
/* Fill the path with the WINDING fill mode */
jOldFillMode = pdcattr->jFillMode;
pdcattr->jFillMode = WINDING;
PATH_FillPathEx(dc, pNewPath, pbrLine);
pdcattr->jFillMode = jOldFillMode;
PATH_Delete(pNewPath->BaseObject.hHmgr);
return TRUE;
}
}
/* ... 否则:把路径展平为折线,逐段 IntGdiPolyline ... */
}
- 宽笔 :
PATH_WidenPathEx用当前笔参数把路径展开为闭合轮廓路径
(新路径),然后临时切到WINDING填充模式 并PATH_FillPathEx填充
(画笔此时作为填充画刷pbrLine使用)------这正印证了 1.2 节"线条=笔刷扫掠区域"的论断; - 细笔 :把路径按
PT_MOVETO分段展平成折线,逐段IntGdiPolyline
(此时不再宽笔,走IntEngPolyline细线快路径)。
7.5 PATH_WidenPathEx(path.c:2195-2260)------宽化前的笔参数提取
c
PPATH FASTCALL
PATH_WidenPathEx(DC *dc, PPATH pPath)
{
INT size;
UINT penWidth, penStyle;
DWORD obj_type;
LPEXTLOGPEN elp;
if (pPath->state != PATH_Closed) // 只接受闭合路径
{ EngSetLastError(ERROR_CAN_NOT_COMPLETE); return NULL; }
size = GreGetObject(pdcattr->hpen, 0, NULL); // 两步查询:先取大小
if (!size) { ...; return NULL; }
elp = ExAllocatePoolWithTag(PagedPool, size, TAG_PATH);
GreGetObject(pdcattr->hpen, size, elp); // 再取内容
obj_type = GDI_HANDLE_GET_TYPE(pdcattr->hpen);
if (obj_type == GDI_OBJECT_TYPE_PEN)
penStyle = ((LOGPEN*)elp)->lopnStyle; // 老式笔
else if (obj_type == GDI_OBJECT_TYPE_EXTPEN)
penStyle = elp->elpPenStyle; // 扩展笔
else { ...; return NULL; }
penWidth = elp->elpWidth;
ExFreePoolWithTag(elp, TAG_PATH);
/* The function cannot apply to cosmetic pens */
if (obj_type == GDI_OBJECT_TYPE_EXTPEN &&
(PS_TYPE_MASK & penStyle) == PS_COSMETIC)
{ EngSetLastError(ERROR_CAN_NOT_COMPLETE); return FALSE; }
return IntGdiWidenPath(pPath, penWidth, penStyle,
dc->dclevel.laPath.eMiterLimit);
}
- 用
GreGetObject两次查询 (先 0 大小取字节数,再分配缓冲取内容),
与 6.2 节呼应; - 按句柄类型决定从
LOGPEN.lopnStyle还是EXTLOGPEN.elpPenStyle读样式,
宽度统一取elp->elpWidth(注意:LOGPEN与EXTLOGPEN中宽度字段偏移一致
------LOGPEN.lopnWidth.x前有lopnStyle(4B),EXTLOGPEN.elpWidth前也有
elpPenStyle(4B),两者低 32 位宽度正好同偏移,代码直接复用); - 美容扩展笔被拒绝 (
ERROR_CAN_NOT_COMPLETE)------美容笔不该宽化; eMiterLimit来自 DC 路径属性的laPath.eMiterLimit,用于斜接限制。
7.6 IntGdiWidenPath(path.c:1801-2174)------宽化核心算法
c
#define round(x) ((int)((x)>0?(x)+0.5:(x)-0.5))
PPATH FASTCALL
IntGdiWidenPath(PPATH pPath, UINT penWidth, UINT penStyle, FLOAT eMiterLimit)
{
INT i, j, numStrokes, numOldStrokes, penWidthIn, penWidthOut;
PPATH flat_path, pNewPath = NULL, *pStrokes = NULL, ...;
DWORD joint, endcap;
KFLOATING_SAVE fpsave;
endcap = (PS_ENDCAP_MASK & penStyle);
joint = (PS_JOIN_MASK & penStyle);
if (!(flat_path = PATH_FlattenPath(pPath))) return NULL;
penWidthIn = penWidth / 2; // 内半宽(奇数宽度时右侧/内侧少 1)
penWidthOut = penWidth / 2;
if (penWidthIn + penWidthOut < penWidth) penWidthOut++; // 奇数宽度外扩
numStrokes = 0;
...(按 PT_MOVETO 把扁平路径拆成若干独立子路径 pStrokes[])...
pNewPath = PATH_CreatePath(flat_path->numEntriesUsed);
KeSaveFloatingPointState(&fpsave); // 全程使用浮点三角函数
for (i = 0; i < numStrokes; i++)
{
...(对每条子路径生成 pUpPath(外侧/左侧边)与 pDownPath(内侧/右侧边))...
}
...
KeRestoreFloatingPointState(&fpsave);
...
return pNewPath;
}
算法分步:
- 展平 :
PATH_FlattenPath把贝塞尔曲线离散成折线段(后续只出现
PT_MOVETO/PT_LINETO;PT_BEZIERTO分支打了 "Should never happen" 的错误日志); - 半宽分配 :
penWidthIn = penWidthOut = penWidth / 2;若二者之和小于总宽
(即奇数宽度),把多出的 1 像素给外侧penWidthOut。外缘总是 ≥ 内缘; - 拆分子路径 :遍历扁平路径,遇到
PT_MOVETO开启新子路径(pStrokes[]动态扩容),
并把前一条置PATH_Closed;PT_LINETO/PT_LINETO|PT_CLOSEFIGURE追加到当前子路径; - 逐子路径生成上下两条边 (核心几何,见 7.7):
- 子路径的首尾端点 按
endcap处理:PS_ENDCAP_SQUARE:沿线段方向-halfWidth延伸出方形帽(两个角点);PS_ENDCAP_FLAT:不延伸,仅垂直方向的两个边点;PS_ENDCAP_ROUND(默认):用PATH_DoArcPart画四段 90° 圆弧拼成半圆帽;
- 子路径的内部顶点 按
joint处理(见 7.7); - 对"单线段开放路径"(恰好 2 个点、未闭合)特殊补充上下边体端点,保证
帽与边体正确衔接(path.c:2107-2132 的注释与代码);
- 子路径的首尾端点 按
- 组装闭合轮廓 :
pUpPath点序正序拷入pNewPath;pDownPath经
reverse_points反序后拷入------这样两条边首尾相接形成逆/顺时针闭合轮廓 ,
内部自交由 WINDING 填充模式处理;最后pNewPath->state = PATH_Closed。
7.7 连接样式(join)的几何处理
对每个内部顶点,先计算前后两段的夹角 alpha(atan2 差值归一化到 [-π, π]),
再按 joint 生成外侧点:
c
theta = atan2(yo - ya, xo - xa);
alpha = atan2(yb - yo, xb - xo) - theta;
if (alpha > 0) alpha -= M_PI;
else alpha += M_PI;
if (_joint == PS_JOIN_MITER && eMiterLimit < fabs(1 / sin(alpha / 2)))
{
_joint = PS_JOIN_BEVEL; // 超出斜接极限 → 降级为斜切
}
PS_JOIN_MITER(斜接) :外侧点沿角平分线延伸到
miterWidth = |penWidthOut / cos(π/2 - |α|/2)|。若
eMiterLimit < |1/sin(α/2)|(角度太尖、尖角太长)则自动降级为 BEVEL
(这正是eMiterLimit的用途);PS_JOIN_BEVEL(斜切):外侧直接连两个边端点(切掉尖角);PS_JOIN_ROUND(圆角,默认) :外侧用三段PT_BEZIERTO模拟圆弧。- 内侧(
pInsidePath)始终是简单折线:alpha > 0时顶点在
+penWidthIn一侧,alpha < 0时在-penWidthIn一侧(两侧分别收集到
pUpPath/pDownPath,随 α 符号交换"内/外"归属)。
7.8 宽笔/描边完整调用链(mermaid)
渲染错误: Mermaid 渲染失败: Parse error on line 3: ...en?
lWidth>1 && (老式笔 || PS_GEOMETRIC -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'
8. 源码索引
8.1 函数索引(pen.c 全部函数)
| 函数 | 位置 | 类型 | 一句话作用 |
|---|---|---|---|
PEN_vInit |
pen.c:16-23 | static | 把 pBrushAttr 指向对象内嵌 BrushAttr |
PEN_AllocPenWithHandle |
pen.c:25-40 | 内部 API | 分配老式笔对象(LO_PEN_TYPE) |
PEN_AllocExtPenWithHandle |
pen.c:42-57 | 内部 API | 分配扩展笔对象(LO_EXTPEN_TYPE) |
PEN_ShareLockPen |
pen.c:59-70 | 内部 API | 校验句柄类型并按 BRUSH 类型共享引用 |
IntGdiExtCreatePen |
pen.c:72-269 | 内部 API | 创建中枢:校验/初始化/样式分发 |
IntGdiSetSolidPenColor |
pen.c:271-286 | 内部 API | 改实心笔颜色(当前无调用方) |
PEN_GetObject |
pen.c:288-350 | 内部 API | 填 LOGPEN/EXTLOGPEN |
NtGdiCreatePen |
pen.c:355-380 | 系统调用 | 简单笔入口 |
NtGdiExtCreatePen |
pen.c:382-514 | 系统调用 | 扩展笔入口(SEH 安全复制) |
8.2 pen.h 宏与接口
| 名称 | 位置 | 说明 |
|---|---|---|
typedef BRUSH PEN, *PPEN |
pen.h:5 | PEN 复用 BRUSH 布局 |
PEN_UnlockPen |
pen.h:17 | 解锁对象(GDIOBJ_vUnlockObject) |
PEN_ShareUnlockPen |
pen.h:18 | 释放共享引用(GDIOBJ_vDereferenceObject) |
AddPenLinesBounds |
pen.h:31 | 累积模式笔线边界(实现在 line.c:19) |
IntIsEffectiveWidePen |
pen.h:33-37 | 宽笔判定宏 |
8.3 关联源码位置速查
| 主题 | 文件:行 |
|---|---|
| 宽笔分流(LineTo) | line.c:208 |
| 宽笔分流(Polyline) | line.c:371 |
| 宽笔分流(椭圆/矩形边框) | fillshap.c:109, 680 |
| 描边入口 | path.c:1610(PATH_StrokePath) |
| 宽化准备 | path.c:2195(PATH_WidenPathEx) |
| 宽化算法 | path.c:1801(IntGdiWidenPath) |
| 线条画刷更新 | dcobjs.c:60(DC_vUpdateLineBrush) |
| 换笔 | dcobjs.c:305(IntGdiSelectBrush) |
| 样式数组释放 | brush.cpp:73-76(BRUSH::~BRUSH) |
| 用户态 CreatePen | gdi32/objects/pen.c:21 |
| 用户态 ExtCreatePen | gdi32/objects/brush.c:13 |
| 用户态 SetDCPenColor | gdi32/objects/dc.c:941 |
| 内核态 IntSetDCPenColor | dcutil.c:259 |
| BRUSH_ATTR | ntgdihdl.h:353 |
9. 总结
pen.c 虽然只有约 516 行,却是整个 GDI 线条绘制的起点:
- 统一模型 :
PEN == BRUSH,画笔就是"用于画线的画刷"。颜色放在BrushAttr.lbColor,
样式放在pStyle/dwStyleCount/ulPenStyle,宽窄由lWidth决定; - 双入口 :
NtGdiCreatePen(简单笔)与NtGdiExtCreatePen(扩展笔)最终都汇聚到
IntGdiExtCreatePen,后者完成对象分配(老式/扩展两种 GDI 对象类型)、三层参数校验
(端帽/连接/类型)、线型样式分发(内建静态数组或用户数组)与flAttrs标记; - 安全边界 :用户样式数组在内核用
ProbeForRead+ SEH 复制到非分页池
(GDITAG_PENSTYLE),所有权随对象移交、析构时按BR_IS_DEFAULTSTYLE区分释放; - 查询对称 :
PEN_GetObject按BR_IS_OLDSTYLEPEN返回LOGPEN/EXTLOGPEN,
并被PATH_WidenPathEx的"两步查询"复用; - 绘制挂钩 :
IntIsEffectiveWidePen把"宽笔"精确限定为
"宽度>1 且(老式笔或几何笔)"。细线走IntEngLineTo/IntEngPolyline快路径;
宽笔转临时路径 →PATH_StrokePath→PATH_WidenPathEx→IntGdiWidenPath
展开为轮廓后按 WINDING 填充------端帽(SQUARE/FLAT/ROUND)与连接
(MITER/BEVEL/ROUND,含斜接极限降级)全部在这一步由几何算法统一实现; - 颜色链路 :
SetDCPenColor(gdi32/ntuser 两条路径)只改 DC 属性并置DIRTY_LINE,
真正的落笔在DC_vUpdateLineBrush(DC_PEN特例用crPenClr覆盖eboLine)。
遗留问题(源码注释) :IntGdiExtCreatePen 中 // FIXME: Copy the bitmap first ?
说明 BS_PATTERN 类画笔的 DIB 目前只探测未复制;/* FIXME: What style here? */
说明 USERSTYLE 笔被统一标记 BR_IS_SOLID;IntGdiSetSolidPenColor 当前无调用方。
本文档基于 ReactOS 源代码
win32ss/gdi/ntgdi/pen.c及关联模块(pen.h / brush.h / line.c / path.c / dcobjs.c / dcutil.c / gdi32 objects)
分析(2026 年 8 月)。所有行号均对应分析时的源码版本。