ReactOS 图形系统分析(53):系统库存对象 --- stockobj.c
本文基于 ReactOS 源码
win32ss/gdi/ntgdi/stockobj.c(351 行,截至 2026 年 8 月)及关联模块
gdiobj.c、brush.cpp、font.c、freetype.c、main.c、dclife.c、defwnd.c深入分析。
1. 概述
1.1 什么是系统库存对象(Stock Objects)
系统库存对象 是 GDI 在会话启动(win32k.sys 加载)时 预先创建好的一组"全局共享、永不删除"的
基础 GDI 对象------包括标准画刷(WHITE_BRUSH、BLACK_BRUSH......)、标准画笔(WHITE_PEN、
BLACK_PEN......)、系统字体(SYSTEM_FONT、DEFAULT_GUI_FONT......)、默认位图(DEFAULT_BITMAP)
与默认调色板(DEFAULT_PALETTE)。
这些对象的核心特性:
| 特性 | 说明 |
|---|---|
| 全局共享 | 所有进程、所有线程共用同一份对象,对象属主为 PUBLIC(GDI_OBJ_HMGR_PUBLIC) |
| 零成本获取 | 应用程序调用 GetStockObject(索引) 直接返回句柄,无需创建 |
| 永不释放 | 句柄带 GDI_HANDLE_IS_STOCKOBJ 高位标记,DeleteObject 对它们无效(直接返回 TRUE) |
| 会话级生命周期 | 随 win32k.sys 驱动加载而创建,随系统会话结束而消亡,绝无中途删除 |
| 内部/外部两套 | StockObjects[] 表(22 槽)供 GetStockObject 使用;gpsi->ahbrSystem[](31 个系统颜色画刷)供 GetSysColorBrush / IntGetSysColorBrush 使用 |
stockobj.c 正是这些库存对象的创建工厂与查询门户 :它定义静态的 LOGPEN / LOGFONTW
模板、SysColors[] 默认系统颜色表,实现 CreateStockObjects()、CreateSysColorObjects()、
NtGdiGetStockObject()、IntGetSysColorBrush()、IntGetSysColor()、IntSetSysColors()、
IntCreateStockPen()、CreateStockFonts() 共 8 个函数,并导出全局数组 StockObjects[]
与全局系统位图 DC 句柄 hSystemBM。
1.2 总体架构图
┌───────────────────────────── 用户态 (Ring 3) ─────────────────────────────┐
│ │
│ gdi32.dll user32.dll │
│ ┌──────────────────┐ ┌───────────────────────┐ │
│ │ GetStockObject(i)│ ── syscall ──► │ GetSysColorBrush(i) │ │
│ │ (带本地缓存) │ │ GetSysColor(i) │ │
│ └──────────────────┘ │ └─ 直接读 gpsi 共享内存│ │
│ │ │ SetSysColors(...) │ │
│ │ NtGdiGetStockObject │ └─ NtUserSetSysColors │ │
│ ▼ └───────────────────────┘ │
└────────┼──────────────────────────────────────────────────────────────────┘
│ │ 共享映射 (gSharedInfo)
▼ ▼
┌───────────────────────────── 内核态 (Ring 0) win32k.sys ──────────────────┐
│ │
│ ntgdi/stockobj.c ntuser/ │
│ ┌───────────────────────────────┐ ┌───────────────────────────┐ │
│ │ StockObjects[22] │ │ gpsi (PERUSERSERVERINFO) │ │
│ │ ├─ 6 画刷 (IntGdiCreateSolid │ │ ├─ argbSystem[31] 颜色 │ │
│ │ │ Brush / NullBrush) │ │ └─ ahbrSystem[31] 画刷 │ │
│ │ ├─ 4 画笔 (IntCreateStockPen)│ └───────────────────────────┘ │
│ │ ├─ 7 字体 (CreateStockFonts │ ▲ │
│ │ │ → TextIntCreateFont...) │ │ IntGetSysColorBrush / │
│ │ ├─ DEFAULT_BITMAP (1×1) │ │ IntGetSysColor / │
│ │ └─ DEFAULT_PALETTE │ │ IntSetSysColors │
│ │ │ │ └── 供 ntuser 绘制 (draw.c, │
│ │ │ GDIOBJ_ConvertToStockObj menu.c, nonclient.c...) │
│ │ ▼ │ │
│ │ 句柄 |= GDI_HANDLE_STOCK_MASK │ │
│ └───────────────────────────────┘ │
│ │
│ DriverEntry (ntuser/main.c:1070) │
│ → CreateStockObjects() + CreateSysColorObjects() │
│ → InitGdiHandleTable() → InitPaletteImpl() (gppalDefault) │
└───────────────────────────────────────────────────────────────────────────┘
1.3 设计动机
1)性能:免去高频对象的反复创建/销毁
WHITE_BRUSH、BLACK_PEN、SYSTEM_FONT 这类对象是所有 GUI 程序最常用的资源。若每个
进程、每次绘图都重新创建再删除,将是巨大的系统调用开销。库存对象让它们成为进程间共享的
常量 ,GetStockObject 只需一次数组下标访问即可拿到句柄。
2)一致性:保证全系统使用同一套"系统外观"
系统颜色画刷(COLOR_WINDOW、COLOR_BTNFACE......)必须全局唯一------所有窗口的背景色、
按钮色必须一致。若各进程各建各的,就无法做到 SetSysColors() 一次修改、全局生效。
因此它们被集中存放在共享的 gpsi(SERVERINFO)结构中,修改时就地改色 (IntGdiSetSolidBrushColor),
所有引用同一画刷的地方立即看到新颜色。
3)用户/内核共享:GetSysColor 零系统调用
gpsi 结构被映射到每个 GUI 进程的用户态地址空间,因此 user32.dll 的 GetSysColor() /
GetSysColorBrush() 可以直接读内存 (gpsi->argbSystem[nIndex] / gpsi->ahbrSystem[nIndex])
而无需任何 syscall------这在控件绘制(listbox、scrollbar、static......)中被高频调用,收益巨大。
4)固定语义:库存对象不可删除、不可改属主
DeleteObject(GetStockObject(...)) 永远返回 TRUE 但什么都不做 ,防止应用误删全局共享
对象导致整个图形系统崩溃。这是 Windows GDI 的既定契约,ReactOS 通过句柄高位标记
(GDI_HANDLE_STOCK_MASK = 0x00800000)来识别。
2. 核心数据结构
2.1 StockObjectsNB_STOCK_OBJECTS --- 库存对象表
c
// stockobj.c:100
HGDIOBJ StockObjects[NB_STOCK_OBJECTS];
- 全局数组(非 static,供 win32k 内核其他模块直接引用,例如 winsta.c 中
NtGdiGetStockObject(SYSTEM_FONT)取系统字体)。 - 槽位数量
NB_STOCK_OBJECTS定义于 ntgdityp.h:192(file:///d:/reactos/win32ss/include/ntgdityp.h):
c
/* Public LAST_STOCK = 19, plus 2 more internal entries */
#define NB_STOCK_OBJECTS 22
注意:Windows 公开的库存对象索引最大为
STOCK_LAST = 19(wingdi.h)。ReactOS 的
NB_STOCK_OBJECTS = 22比 20 多出 2 个槽位,其中:
- 槽位 20:
StockObjects[20] = NULL(源码注释/* TODO: Unknown internal stock object */);- 槽位 21:
DEFAULT_BITMAP(ReactOS 内部扩展,见 ntgdityp.h:195(file:///d:/reactos/win32ss/include/ntgdityp.h))。
2.2 库存对象索引常量(wingdi.h)
应用层通过 GetStockObject(i) 传入的索引定义在 wingdi.h:896-917(file:///d:/reactos/sdk/include/psdk/wingdi.h):
c
#define WHITE_BRUSH 0
#define LTGRAY_BRUSH 1
#define GRAY_BRUSH 2
#define DKGRAY_BRUSH 3
#define BLACK_BRUSH 4
#define NULL_BRUSH 5 /* 别名 HOLLOW_BRUSH */
#define WHITE_PEN 6
#define BLACK_PEN 7
#define NULL_PEN 8
#define OEM_FIXED_FONT 10
#define ANSI_FIXED_FONT 11
#define ANSI_VAR_FONT 12
#define SYSTEM_FONT 13
#define DEVICE_DEFAULT_FONT 14
#define DEFAULT_PALETTE 15
#define SYSTEM_FIXED_FONT 16
#define DEFAULT_GUI_FONT 17
#define DC_BRUSH 18 /* Win2000+ */
#define DC_PEN 19 /* Win2000+ */
#define STOCK_LAST 19
对应关系速记:0~5 画刷、6~8 画笔、10~17 字体/调色板、18/19 DC 画刷/画笔 ,
索引 9 为保留空位,索引 20 为 ReactOS 内部未知对象(置 NULL),索引 21 为
DEFAULT_BITMAP(ReactOS 内部扩展,见 ntgdityp.h)。
2.3 库存句柄标记 GDI_HANDLE_IS_STOCKOBJ(ntgdihdl.h)
GDI 句柄是 32 位整数的"包装",其各比特位有固定语义。相关掩码定义在
ntgdihdl.h(file:///d:/reactos/win32ss/include/ntgdihdl.h):
c
// ntgdihdl.h:23-30
#define GDI_HANDLE_INDEX_MASK (GDI_HANDLE_COUNT - 1) /* 低 16 位:句柄表索引 0x0000FFFF */
#define GDI_HANDLE_TYPE_MASK 0x007f0000 /* 位 16~22:对象类型 */
#define GDI_HANDLE_BASETYPE_MASK 0x001f0000
#define GDI_HANDLE_EXTYPE_MASK 0x00600000
#define GDI_HANDLE_STOCK_MASK 0x00800000 /* 位 23:库存对象标记! */
#define GDI_HANDLE_REUSE_MASK 0xff000000 /* 高 8 位:复用计数 */
// ntgdihdl.h:99-103 ------ 判定/设置宏
#define GDI_HANDLE_IS_STOCKOBJ(h) \
(0 != (((ULONG_PTR)(h)) & GDI_HANDLE_STOCK_MASK))
#define GDI_HANDLE_SET_STOCKOBJ(h) \
((h) = (HANDLE)(((ULONG_PTR)(h)) | GDI_HANDLE_STOCK_MASK))
同时,句柄表条目(GDI_TABLE_ENTRY)里也有对应的库存标记位:
c
// ntgdihdl.h:33
#define GDI_ENTRY_STOCK_MASK 0x00000080
标记的作用点(源码佐证):
| 位置 | 文件:行 | 行为 |
|---|---|---|
NtGdiDeleteObjectApp |
dclife.c:952 | if (GDI_HANDLE_IS_STOCKOBJ(hobj)) return TRUE; ------ 库存对象不可删 |
GDIOBJ_ConvertToStockObj |
gdiobj.c:1482-1489 | `pentry->FullUnique |
GDIOBJ_ConvertFromStockObj |
gdiobj.c:1516-1523 | 反向清除标记,并把属主改为当前进程 |
gdi32 用户态 DeleteObject |
gdi32/objects/gdiobj.c:324 | 亦先判 GDI_HANDLE_IS_STOCKOBJ |
句柄位 23(
0x00800000)置位即表示"这是一个系统库存对象"。因为库存对象一经创建永不删除,所以复用计数、属主检查等常规对象保护机制对它们全部失效------这正是
NtGdiDeleteObjectApp直接放行的原因。
2.4 系统颜色索引 COLOR_*(winuser.h)
系统颜色画刷的索引(即 GetSysColorBrush(i) / GetSysColor(i) 的参数)定义在
winuser.h:922-958(file:///d:/reactos/sdk/include/psdk/winuser.h),共 0~30 共 31 个
(NUM_SYSCOLORS = 31,见 ntuser.h:954):
c
#define COLOR_SCROLLBAR 0
#define COLOR_BACKGROUND 1 /* 别名 COLOR_DESKTOP */
#define COLOR_ACTIVECAPTION 2
#define COLOR_INACTIVECAPTION 3
#define COLOR_MENU 4
#define COLOR_WINDOW 5
#define COLOR_WINDOWFRAME 6
#define COLOR_MENUTEXT 7
#define COLOR_WINDOWTEXT 8
#define COLOR_CAPTIONTEXT 9
#define COLOR_ACTIVEBORDER 10
#define COLOR_INACTIVEBORDER 11
#define COLOR_APPWORKSPACE 12
#define COLOR_HIGHLIGHT 13
#define COLOR_HIGHLIGHTTEXT 14
#define COLOR_BTNFACE 15 /* 别名 COLOR_3DFACE */
#define COLOR_BTNSHADOW 16 /* 别名 COLOR_3DSHADOW */
#define COLOR_GRAYTEXT 17
#define COLOR_BTNTEXT 18
#define COLOR_INACTIVECAPTIONTEXT 19
#define COLOR_BTNHIGHLIGHT 20 /* 别名 COLOR_3DHIGHLIGHT/3DHILIGHT/BTNHILIGHT */
#define COLOR_3DDKSHADOW 21
#define COLOR_3DLIGHT 22
#define COLOR_INFOTEXT 23
#define COLOR_INFOBK 24
/* 索引 25 未使用 */
#define COLOR_HOTLIGHT 26
#define COLOR_GRADIENTACTIVECAPTION 27
#define COLOR_GRADIENTINACTIVECAPTION 28
#define COLOR_MENUHILIGHT 29 /* XP+ */
#define COLOR_MENUBAR 30 /* XP+ */
注意 :SysColors[] 表中 31 项与 winuser.h 一一对应,且第 31 项注释写的是
RGB(181, 181, 181), /* COLOR_UNKNOWN */------因为索引 25 没有公开名字,stockobj.c
用注释 COLOR_UNKNOWN 指代它,其值 RGB(181,181,181) 是一个保守的灰色。
2.5 SysColors\[\] --- 默认系统颜色表(stockobj.c:16-49)
c
static const COLORREF SysColors[] =
{
RGB(212, 208, 200), /* COLOR_SCROLLBAR */
RGB(58, 110, 165), /* COLOR_BACKGROUND */
RGB(10, 36, 106), /* COLOR_ACTIVECAPTION */
RGB(128, 128, 128), /* COLOR_INACTIVECAPTION */
RGB(212, 208, 200), /* COLOR_MENU */
RGB(255, 255, 255), /* COLOR_WINDOW */
RGB(0, 0, 0), /* COLOR_WINDOWFRAME */
RGB(0, 0, 0), /* COLOR_MENUTEXT */
RGB(0, 0, 0), /* COLOR_WINDOWTEXT */
RGB(255, 255, 255), /* COLOR_CAPTIONTEXT */
RGB(212, 208, 200), /* COLOR_ACTIVEBORDER */
RGB(212, 208, 200), /* COLOR_INACTIVEBORDER */
RGB(128, 128, 128), /* COLOR_APPWORKSPACE */
RGB(10, 36, 106), /* COLOR_HIGHLIGHT */
RGB(255, 255, 255), /* COLOR_HIGHLIGHTTEXT */
RGB(212, 208, 200), /* COLOR_BTNFACE */
RGB(128, 128, 128), /* COLOR_BTNSHADOW */
RGB(128, 128, 128), /* COLOR_GRAYTEXT */
RGB(0, 0, 0), /* COLOR_BTNTEXT */
RGB(212, 208, 200), /* COLOR_INACTIVECAPTIONTEXT */
RGB(255, 255, 255), /* COLOR_BTNHIGHLIGHT */
RGB(64, 64, 64), /* COLOR_3DDKSHADOW */
RGB(212, 208, 200), /* COLOR_3DLIGHT */
RGB(0, 0, 0), /* COLOR_INFOTEXT */
RGB(255, 255, 225), /* COLOR_INFOBK */
RGB(181, 181, 181), /* COLOR_UNKNOWN */
RGB(0, 0, 128), /* COLOR_HOTLIGHT */
RGB(166, 202, 240), /* COLOR_GRADIENTACTIVECAPTION */
RGB(192, 192, 192), /* COLOR_GRADIENTINACTIVECAPTION */
RGB(49, 106, 197), /* COLOR_MENUHILIGHT */
RGB(236, 233, 216) /* COLOR_MENUBAR */
};
要点:
- 这是一个静态 const 数组 ,
CreateSysColorObjects()启动时把它整体拷贝 到共享的
gpsi->argbSystem[]中。之后所有查询都走gpsi,本数组不再被读取(除非重新初始化)。 - 这是经典的 Windows 95/98 "经典蓝色"配色:活动标题栏深蓝(10,36,106)、窗口白色、
按钮面(212,208,200)等,与 Windows 默认外观一致。 - 注意
COLOR_BACKGROUND(桌面)是蓝色(58,110,165),而非现代 Windows 的壁纸色。
2.6 gpsi(PERUSERSERVERINFO)中的系统颜色字段
CreateSysColorObjects() / IntGetSysColorBrush() / IntGetSysColor() /
IntSetSysColors() 全部围绕共享结构 gpsi(全局指针,指向 PERUSERSERVERINFO)工作。
相关字段定义在 ntuser.h:1003-1008(file:///d:/reactos/win32ss/include/ntuser.h):
c
typedef struct _PERUSERSERVERINFO
{
INT aiSysMet[SM_CMETRICS];
ULONG argbSystemUnmatched[NUM_SYSCOLORS]; /* 未被用户覆盖的颜色(原始值) */
COLORREF argbSystem[NUM_SYSCOLORS]; /* 当前生效的系统颜色 */
HBRUSH ahbrSystem[NUM_SYSCOLORS]; /* 对应的系统颜色画刷 */
HBRUSH hbrGray;
...
} PERUSERSERVERINFO, *PPERUSERSERVERINFO;
gpsi本身在DriverEntry中通过UserHeapAlloc(sizeof(*gpsi))分配
(ntuser/main.c:1054),并RtlZeroMemory清零(main.c:1062)。- 该结构会被映射/暴露给用户态进程(user32 通过它直接读系统颜色与画刷),这就是
GetSysColor/GetSysColorBrush零系统调用的基础。 argbSystemUnmatched[]与argbSystem[]的区别:前者保留"用户未定制"的原始颜色,
后者是当前生效值(定制后二者分道扬镳)。stockobj.c只用argbSystem[]。
2.7 全局 hSystemBM --- 系统位图 DC
c
// stockobj.c:52
HDC hSystemBM;
- 声明于 stockobj.c,真正创建在 winsta.c:298-301(file:///d:/reactos/win32ss/user/ntuser/winsta.c):
c
hSystemBM = NtGdiCreateCompatibleDC(ScreenDeviceContext);
NtGdiSelectFont(hSystemBM, NtGdiGetStockObject(SYSTEM_FONT));
GreSetDCOwner(hSystemBM, GDI_OBJ_HMGR_PUBLIC);
- 用途:系统字体的度量基准 DC 。显示初始化后,
IntGetCharDimensions(hSystemBM, ...)
用它算出系统字体(SYSTEM_FONT)的平均字符宽高,写入gpsi->cxSysFontChar / cySysFontChar / tmSysFont(winsta.c:325-326、display.c:889),供整个系统计算控件尺寸。 - 由于它内部选中了库存字体
SYSTEM_FONT,且属主为 PUBLIC,任何进程都可以安全引用。
2.8 静态 LOGPEN / LOGFONTW 模板(stockobj.c:56-98)
创建库存画笔/字体前,先定义好逻辑对象模板。这些是编译期常量:
c
// ------ 画笔模板 ------
static LOGPEN WhitePen =
{ PS_SOLID, { 0, 0 }, RGB(255,255,255) }; // 实线、宽 0、白色
static LOGPEN BlackPen =
{ PS_SOLID, { 0, 0 }, RGB(0,0,0) }; // 实线、宽 0、黑色
static LOGPEN NullPen =
{ PS_NULL, { 0, 0 }, 0 }; // 无线条样式、宽 0、无色
// ------ 字体模板(LOGFONTW 字段顺序:高度/宽度/仰角/方向/字重/斜体/下划线/删除线/字符集/输出精度/裁剪精度/质量/字族|间距/字体名)------
static LOGFONTW OEMFixedFont =
{ 12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, OEM_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE | FIXED_PITCH, L"Terminal" };
static LOGFONTW AnsiFixedFont =
{ 12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_STROKE_PRECIS, PROOF_QUALITY, FF_DONTCARE | FIXED_PITCH, L"Courier" };
static LOGFONTW AnsiVarFont =
{ 12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_STROKE_PRECIS, PROOF_QUALITY, FF_DONTCARE | VARIABLE_PITCH, L"MS Sans Serif" };
static LOGFONTW SystemFont =
{ 16, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE | VARIABLE_PITCH, L"System" };
static LOGFONTW DeviceDefaultFont =
{ 16, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, ANSI_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_SWISS | VARIABLE_PITCH, L"System" };
static LOGFONTW SystemFixedFont =
{ 15, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE | FIXED_PITCH, L"Fixedsys" };
static LOGFONTW DefaultGuiFont =
{ -11, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"MS Shell Dlg" };
观察点:
- 高度为负数 (如 DefaultGuiFont 的 -11):负高度表示以"字符高度(em height)"为单位,
正数表示以"单元高度(cell height)"为单位。-11是 Windows 默认 GUI 字体
(MS Shell Dlg,即 Tahoma/宋体)的经典值。 - 字体名语义 :
System是设备无关的系统字体(实际映射到点阵/矢量系统字体);
MS Shell Dlg是"逻辑字体名",会按系统语言映射到真实字体(如中文环境的宋体)。 - 质量等级 :AnsiFixedFont / AnsiVarFont / DefaultGuiFont 用了
PROOF_QUALITY
(对屏幕输出有讲究的字体优先按轮廓精确缩放);其余用DEFAULT_QUALITY。 - AnsiFixedFont / AnsiVarFont 使用
CLIP_STROKE_PRECIS(裁剪时按笔画裁剪),
其余用CLIP_DEFAULT_PRECIS。 - 这些模板在
CreateStockFonts()中会被修改 (如按代码页替换字体名、调整高度),
因此它们是"可变初始值",而非全程常量。
3. 函数逐一展开
stockobj.c 共有 8 个函数 + 1 个全局数组 + 1 个全局 DC。本文按源码顺序逐一分析。
特别说明:早期资料(含本文旧版)提到的
InitStockObjects()在源码中并不存在 ,stockobj.c 实际没有该函数------基础初始化由
CreateStockObjects()+
CreateSysColorObjects()两个函数完成(见 §3.3、§3.8 及 §3.9 澄清)。
3.1 IntCreateStockPen --- 库存画笔工厂(static,stockobj.c:102-142)
c
static
HPEN
FASTCALL
IntCreateStockPen(DWORD dwPenStyle,
DWORD dwWidth,
ULONG ulBrushStyle,
ULONG ulColor)
{
HPEN hPen;
PBRUSH pbrushPen;
pbrushPen = PEN_AllocPenWithHandle();
if (pbrushPen == NULL) return NULL;
if ((dwPenStyle & PS_STYLE_MASK) == PS_NULL) dwWidth = 1;
pbrushPen->iHatch = 0;
pbrushPen->lWidth = abs(dwWidth);
FLOATOBJ_SetLong(&pbrushPen->eWidth, pbrushPen->lWidth);
pbrushPen->ulPenStyle = dwPenStyle;
pbrushPen->BrushAttr.lbColor = ulColor;
pbrushPen->iBrushStyle = ulBrushStyle;
pbrushPen->hbmClient = (HANDLE)NULL;
pbrushPen->dwStyleCount = 0;
pbrushPen->pStyle = 0;
pbrushPen->flAttrs = BR_IS_OLDSTYLEPEN;
switch (dwPenStyle & PS_STYLE_MASK)
{
case PS_NULL:
pbrushPen->flAttrs |= BR_IS_NULL;
break;
case PS_SOLID:
pbrushPen->flAttrs |= BR_IS_SOLID;
break;
}
hPen = pbrushPen->BaseObject.hHmgr;
PEN_UnlockPen(pbrushPen);
return hPen;
}
签名 :static HPEN FASTCALL IntCreateStockPen(DWORD, DWORD, ULONG, ULONG)
| 参数 | 含义 |
|---|---|
dwPenStyle |
画笔线型,如 PS_SOLID、PS_NULL(来自 LOGPEN.lopnStyle) |
dwWidth |
逻辑宽度(来自 LOGPEN.lopnWidth.x) |
ulBrushStyle |
底层画刷样式,统一传 BS_SOLID |
ulColor |
颜色(来自 LOGPEN.lopnColor) |
作用 :这是 ReactOS 内部专用的"库存画笔"快速创建路径,只处理最简单的情形
(实线/空线、零宽度),不经过完整的 NtGdiCreatePen 用户态路径。
实现流程(按源码逐条):
PEN_AllocPenWithHandle()分配并插入句柄表的PBRUSH对象(画笔在 ReactOS 内部
就是带笔属性的BRUSH对象------所以返回类型是PBRUSH、类型断言见 brush.cpp:95-97)。- 空笔宽度强制 1 :
if ((dwPenStyle & PS_STYLE_MASK) == PS_NULL) dwWidth = 1;
------ 空线无需宽度,但内部实现要求宽度字段有效。 - 填充各字段:
iHatch = 0(无影线);lWidth = abs(dwWidth),并用FLOATOBJ_SetLong同步浮点宽度eWidth;ulPenStyle = dwPenStyle(保存原始样式字,含端帽/连接符位);BrushAttr.lbColor = ulColor(颜色写入画刷属性,与画刷共用同一结构);iBrushStyle = ulBrushStyle(BS_SOLID);hbmClient = NULL、dwStyleCount = 0、pStyle = 0(无图案、无自定义线型数组);flAttrs = BR_IS_OLDSTYLEPEN(标记为传统 LOGPEN 画笔,非扩展画笔)。
- 按
PS_STYLE_MASK设置附加标记:PS_NULL → BR_IS_NULL、PS_SOLID → BR_IS_SOLID。 - 取出句柄
hHmgr、解锁并返回。
使用方式 :仅供 CreateStockObjects() 创建 WHITE_PEN / BLACK_PEN / DC_PEN /
NULL_PEN 四个库存画笔时调用(见 §3.3)。
注意事项:
- 该函数不设置属主 ------由调用方后续统一
GDIOBJ_ConvertToStockObj转为全局对象。 - 只处理
PS_NULL/PS_SOLID两种线型;其余线型(虚线等)走其他路径。 - 返回值是"尚未打库存标记"的普通句柄,必须由
CreateStockObjects末尾统一转换。
3.2 CreateStockFonts --- 库存字体工厂(static,stockobj.c:144-241)
c
static VOID FASTCALL
CreateStockFonts(void)
{
USHORT ActiveCodePage, OemCodePage;
BYTE bActiveCharSet, bOemCharSet;
BOOL bIsCJK;
static const WCHAR SimSun[] = { 0x5B8B, 0x4F53, 0 }; // "宋体"
static const WCHAR MingLiU[] = { 0x7D30, 0x660E, 0x9AD4, 0 }; // "細明體"
static const WCHAR Batang[] = { 0xBC14, 0xD0D5, 0 }; // "바탕"
...
}
签名 :static VOID FASTCALL CreateStockFonts(void),无参数无返回值。
作用 :根据系统默认代码页 (活动代码页 ActiveCodePage 与 OEM 代码页 OemCodePage)
动态调整 7 个字体模板的字体名、高度、字符集、字族,然后调用
TextIntCreateFontIndirect() 逐一创建真实字体对象并填入 StockObjects[] 对应槽位。
实现流程(按源码逐条):
-
取代码页 :
RtlGetDefaultCodePage(&ActiveCodePage, &OemCodePage)。 -
换算字符集 :
IntCharSetFromCodePage()(定义于 freetype.c:318,内部查表把Windows 代码页映射为
*_CHARSET值)得到bActiveCharSet、bOemCharSet;若 OEM 字符集换算结果为
DEFAULT_CHARSET,则强制改为OEM_CHARSET。 -
按活动代码页选择 GUI 字体名(switch):
代码页 语言 GUI 字体名 936 简体中文 SimSun(宋体,0x5B8B 0x4F53) 950 繁体中文 MingLiU(細明體,0x7D30 0x660E 0x9AD4) 932 日文 MS UI Gothic 949 / 1361 韩文 Batang(바탕,0xBC14 0xD0D5) 其他 西文等 MS Shell Dlg 其中
bIsCJK记录是否 CJK 环境。 -
按 CJK 与否调整高度与字族:
- CJK:OEMFixedFont/SystemFont/DeviceDefaultFont/SystemFixedFont 高度全部 18,
DefaultGuiFont 高度 -12; - 非 CJK:OEMFixedFont=12(FF_MODERN|FIXED_PITCH)、SystemFont=16(FF_SWISS)、
DeviceDefaultFont=16(FF_SWISS)、DefaultGuiFont=-11;
特殊地,若活动字符集是RUSSIAN_CHARSET(西里尔),SystemFixedFont 用
16 号FF_SWISS | FIXED_PITCH,否则 15 号FF_MODERN | FIXED_PITCH。
- CJK:OEMFixedFont/SystemFont/DeviceDefaultFont/SystemFixedFont 高度全部 18,
-
统一设置字符集 :
OEMFixedFont.lfCharSet = bOemCharSet(OEM 字体用 OEM 字符集),其余 6 个全部用
bActiveCharSet。 -
创建字体 :连续 7 次调用
TextIntCreateFontIndirect(&模板, &StockObjects[槽位]):
c
TextIntCreateFontIndirect(&OEMFixedFont, (HFONT*)&StockObjects[OEM_FIXED_FONT]);
TextIntCreateFontIndirect(&AnsiFixedFont, (HFONT*)&StockObjects[ANSI_FIXED_FONT]);
TextIntCreateFontIndirect(&AnsiVarFont, (HFONT*)&StockObjects[ANSI_VAR_FONT]);
TextIntCreateFontIndirect(&SystemFont, (HFONT*)&StockObjects[SYSTEM_FONT]);
TextIntCreateFontIndirect(&DeviceDefaultFont, (HFONT*)&StockObjects[DEVICE_DEFAULT_FONT]);
TextIntCreateFontIndirect(&SystemFixedFont, (HFONT*)&StockObjects[SYSTEM_FIXED_FONT]);
TextIntCreateFontIndirect(&DefaultGuiFont, (HFONT*)&StockObjects[DEFAULT_GUI_FONT]);
TextIntCreateFontIndirect 的实现(freetype.c:2611-2636):
c
NTSTATUS FASTCALL
TextIntCreateFontIndirect(CONST LPLOGFONTW lf, HFONT *NewFont)
{
PLFONT plfont;
LOGFONTW *plf;
ASSERT(lf);
plfont = LFONT_AllocFontWithHandle(); // 分配 LFONT 并插入句柄表
if (!plfont) return STATUS_NO_MEMORY;
ExInitializePushLock(&plfont->lock);
*NewFont = plfont->BaseObject.hHmgr; // 返回句柄
plf = &plfont->logfont.elfEnumLogfontEx.elfLogFont;
RtlCopyMemory(plf, lf, sizeof(LOGFONTW)); // 拷贝 LOGFONTW 模板
if (lf->lfEscapement != lf->lfOrientation)
plf->lfOrientation = plf->lfEscapement; // 保持方向与仰角一致
LFONT_UnlockFont(plfont);
return STATUS_SUCCESS;
}
即:分配 LFONT 对象(内部含 ENUMLOGFONTEX 结构),拷贝模板,得到"未实现的字体对象"
(字体实际由 FreeType 引擎在使用时通过 RealizeFont 实现/栅格化)。
使用方式 :仅由 CreateStockObjects() 调用(§3.3)。
注意事项:
- 字体模板是
static的,函数在 CJK 分支里直接wcscpy改写DefaultGuiFont.lfFaceName
等字段------因为本函数只在启动期执行一次,无并发问题。 - 若某次
TextIntCreateFontIndirect失败(内存不足),StockObjects[]对应槽位保持
该函数写回的句柄值(分配失败时为 0/NULL),后续GetStockObject会返回 NULL,
调用方需自行处理(GDI 内部调用者多不检查,属历史遗留风险点)。
3.3 CreateStockObjects --- 库存对象主工厂(stockobj.c:243-284)
c
/*!
* Creates a bunch of stock objects: brushes, pens, fonts.
*/
VOID FASTCALL
CreateStockObjects(void)
{
UINT Object;
DPRINT("Beginning creation of stock objects\n");
/* Create GDI Stock Objects from the logical structures we've defined */
StockObjects[WHITE_BRUSH] = IntGdiCreateSolidBrush(RGB(255,255,255));
StockObjects[DC_BRUSH] = IntGdiCreateSolidBrush(RGB(255,255,255));
StockObjects[LTGRAY_BRUSH] = IntGdiCreateSolidBrush(RGB(192,192,192));
StockObjects[GRAY_BRUSH] = IntGdiCreateSolidBrush(RGB(128,128,128));
StockObjects[DKGRAY_BRUSH] = IntGdiCreateSolidBrush(RGB(64,64,64));
StockObjects[BLACK_BRUSH] = IntGdiCreateSolidBrush(RGB(0,0,0));
StockObjects[NULL_BRUSH] = IntGdiCreateNullBrush();
StockObjects[WHITE_PEN] = IntCreateStockPen(WhitePen.lopnStyle, WhitePen.lopnWidth.x, BS_SOLID, WhitePen.lopnColor);
StockObjects[BLACK_PEN] = IntCreateStockPen(BlackPen.lopnStyle, BlackPen.lopnWidth.x, BS_SOLID, BlackPen.lopnColor);
StockObjects[DC_PEN] = IntCreateStockPen(BlackPen.lopnStyle, BlackPen.lopnWidth.x, BS_SOLID, BlackPen.lopnColor);
StockObjects[NULL_PEN] = IntCreateStockPen(NullPen.lopnStyle, NullPen.lopnWidth.x, BS_SOLID, NullPen.lopnColor);
StockObjects[20] = NULL; /* TODO: Unknown internal stock object */
StockObjects[DEFAULT_BITMAP] = GreCreateBitmap(1, 1, 1, 1, NULL);
CreateStockFonts();
StockObjects[DEFAULT_PALETTE] = (HGDIOBJ)gppalDefault->BaseObject.hHmgr;
for (Object = 0; Object < NB_STOCK_OBJECTS; Object++)
{
if (NULL != StockObjects[Object])
{
GDIOBJ_ConvertToStockObj(&StockObjects[Object]);
}
}
DPRINT("Completed creation of stock objects\n");
}
签名 :VOID FASTCALL CreateStockObjects(void),由 ntuser/main.c:1070 的
DriverEntry 直接调用。
作用 :一次性创建"20 个公开库存对象 + 1 个内部扩展对象(DEFAULT_BITMAP)",
并统一打上库存标记。它是 stockobj.c 的核心入口。
实现流程(按源码逐条):
-
6 个画刷 :全部用
IntGdiCreateSolidBrush()(brush.cpp:288-299)创建实心画刷,NULL_BRUSH用IntGdiCreateNullBrush()(brush.cpp:279-286):槽位 颜色 WHITE_BRUSH RGB(255,255,255) 白 DC_BRUSH RGB(255,255,255) 白(DC 默认画刷) LTGRAY_BRUSH RGB(192,192,192) 浅灰 GRAY_BRUSH RGB(128,128,128) 中灰 DKGRAY_BRUSH RGB(64,64,64) 深灰 BLACK_BRUSH RGB(0,0,0) 黑 NULL_BRUSH BS_NULL 空画刷 其中
IntGdiCreateSolidBrush走CreateBrushInternal(BR_IS_SOLID | BR_IS_GLOBAL, ...):new BRUSH(...)→(因 BR_IS_GLOBAL 跳过用户态属性分配)→hInsertObject(PUBLIC)插入句柄表 → 解锁返回。BR_IS_GLOBAL 意味着全局对象、跳过 brush attr 的用户态池分配。
-
4 个画笔 :调
IntCreateStockPen(§3.1)。注意 DC_PEN 用的是 BlackPen 模板(黑色),与 BLACK_PEN 完全相同;WHITE_PEN 白色、NULL_PEN 空线。
-
槽位 20 置 NULL :源码注释
/* TODO: Unknown internal stock object */------保留位,无实际对象。
-
DEFAULT_BITMAP :
GreCreateBitmap(1, 1, 1, 1, NULL)创建 1×1 单色 (1 平面 1 位)位图(bitmaps.c:172-188 → GreCreateBitmapEx,DDB_SURFACE)。它是内存 DC 的
默认画刷位图 :新建内存 DC 在未选入位图前,内部以该 1×1 位图兜底(dibobj.c:1747
有相关注释)。
-
7 个字体 :
CreateStockFonts()(§3.2)。 -
DEFAULT_PALETTE :直接把全局默认调色板对象句柄拿来用:
StockObjects[DEFAULT_PALETTE] = (HGDIOBJ)gppalDefault->BaseObject.hHmgr;------
gppalDefault由InitPaletteImpl()(palette.c:66-74)创建:20 个系统保留色的PAL_INDEXED调色板,并GDIOBJ_vReferenceObjectByPointer增引用。注意这里没有重新创建,而是共享同一个调色板对象(因此也不属于"本函数创建")。
-
统一打库存标记 :遍历 0...NB_STOCK_OBJECTS-1(22 槽),对非 NULL 槽调用
GDIOBJ_ConvertToStockObj(&StockObjects[Object])(§2.3,gdiobj.c:1466-1498):ENTRY_ReferenceEntryByHandle引用句柄表条目;pentry->FullUnique |= GDI_ENTRY_STOCK_MASK(表条目打标记);pentry->ObjectOwner.ulObj = 0(属主改 PUBLIC);pobj->hHmgr |= GDI_HANDLE_STOCK_MASK(句柄位 23 置位);- 返回新句柄。
使用方式/注意事项:
- 必须在
InitGdiHandleTable()(句柄表就绪)与InitPaletteImpl()(gppalDefault 就绪)
之后调用------DriverEntry 的顺序正是如此(main.c:1065-1071)。 - 本函数对每个失败未处理:若
IntGdiCreateSolidBrush返回 NULL,槽位为 NULL,循环里
跳过转换,GetStockObject后续返回 NULL------内存极度紧缺时的降级行为。 CreateSysColorObjects()必须紧随其后调用(main.c:1071),两者共同构成
"库存对象初始化"。
3.4 NtGdiGetStockObject --- 库存对象查询(syscall,stockobj.c:286-297)
c
/*!
* Return stock object.
* \param Object - stock object id.
* \return Handle to the object.
*/
HGDIOBJ APIENTRY
NtGdiGetStockObject(INT Object)
{
DPRINT("NtGdiGetStockObject index %d\n", Object);
return ((Object < 0) || (NB_STOCK_OBJECTS <= Object)) ? NULL : StockObjects[Object];
}
签名 :HGDIOBJ APIENTRY NtGdiGetStockObject(INT Object) ------ 系统调用,
gdi32 的 GetStockObject 在本地缓存未命中时转入(见下文调用链)。
作用 :按索引返回库存对象句柄。这是 stockobj.c 唯一的 syscall 导出。
实现流程:
- 参数校验:
Object < 0或Object >= NB_STOCK_OBJECTS(22)则返回 NULL; - 否则直接返回
StockObjects[Object](O(1) 数组下标访问)。
使用方式(用户态 GetStockObject,gdi32/objects/gdiobj.c:6-35):
c
HGDIOBJ stock_objects[NB_STOCK_OBJECTS]; // gdi32 本地缓存
HGDIOBJ WINAPI GetStockObject(INT fnObject)
{
HGDIOBJ hobj;
if ((fnObject < 0) || (fnObject >= NB_STOCK_OBJECTS))
return NULL;
hobj = stock_objects[fnObject];
if (hobj == NULL)
{
hobj = NtGdiGetStockObject(fnObject); // syscall → 内核数组
if (!GdiValidateHandle(hobj))
return NULL;
stock_objects[fnObject] = hobj; // 缓存,下次不再进内核
}
return hobj;
}
注意两点:
- gdi32 用进程内缓存 避免重复 syscall------库存对象全局共享,句柄在所有进程等价,
缓存安全; GdiValidateHandle(hobj)验证句柄在当前进程 的句柄表中有效(库存对象是全局
句柄,通常对所有进程有效)。
内部使用 :win32k 内核代码直接调用 NtGdiGetStockObject(SYSTEM_FONT) 等,
例如 winsta.c:300 为 hSystemBM 选入系统字体、draw.c 中绘制系统图标等。
3.5 IntGetSysColorBrush --- 取系统颜色画刷(内部,stockobj.c:316-320)
c
HGDIOBJ FASTCALL
IntGetSysColorBrush(INT Object)
{
return ((Object < 0) || (NUM_SYSCOLORS <= Object)) ? NULL : gpsi->ahbrSystem[Object];
}
签名 :HGDIOBJ FASTCALL IntGetSysColorBrush(INT Object)。
作用 :按 COLOR_* 索引返回对应的系统颜色画刷 (等价于用户态 GetSysColorBrush,
但供内核模块直接调用)。数据源是 gpsi->ahbrSystem[]。
实现流程 :越界(<0 或 >=31)返回 NULL;否则返回 gpsi->ahbrSystem[Object]。
使用方式(高频调用点,ntuser 内部绘制):
defwnd.c:36/61/64------ 默认窗口类背景画刷(COLOR_3DFACE / COLOR_WINDOW);draw.c:485/597/606/644------ DrawFrameControl / 按钮绘制;menu.c:2188/2601/2719------ 菜单背景、菜单栏;painting.c:1118------ 窗口擦除背景(COLOR_WINDOW);nonclient.c:922/989/1151------ 非客户区边框绘制;class.c:2407、defwnd.c:1155------ 窗口类 hbrBackground 中(HBRUSH)(COLOR_*+1)
约定的解码(HandleToUlong(hBrush)直接作为索引)。
注意事项 :返回的句柄同样带库存标记(CreateSysColorObjects 时已转换),
调用方绝不可 DeleteObject;越界返回 NULL 而不设 SetLastError(内部函数无此义务)。
3.6 IntGetSysColor --- 取系统颜色(内部,stockobj.c:322-326)
c
DWORD FASTCALL
IntGetSysColor(INT nIndex)
{
return (NUM_SYSCOLORS <= (UINT)nIndex) ? 0 : gpsi->argbSystem[nIndex];
}
签名 :DWORD FASTCALL IntGetSysColor(INT nIndex)。
作用 :按索引返回当前系统颜色(COLORREF),内核等价于用户态 GetSysColor。
数据源 gpsi->argbSystem[]。
实现流程 :索引 >= NUM_SYSCOLORS(31) 返回 0;否则返回颜色值。
注意这里只判了上界(把负索引转成无符号后必然 >=31,同样被拦截)。
使用方式(高频调用点):
draw.c中大量IntGdiSetTextColor(dc, IntGetSysColor(COLOR_*));menu.c:1442/2236/2252文字/背景颜色;painting.c:2199/2286标题栏文字与渐变颜色(DC_ACTIVE 区分);nonclient.c:1431标题栏颜色一致性判断;defwnd.c:37/45/52默认窗口绘制。
与用户态 GetSysColor 的关系 :user32 的 GetSysColor(defwnd.c:16-28)直接读
共享映射的 gpsi->argbSystem[nIndex],与内核 IntGetSysColor 读同一块内存 ,
因此两者永远一致。
3.7 IntSetSysColors --- 修改系统颜色(内部,stockobj.c:299-314)
c
VOID FASTCALL
IntSetSysColors(UINT nColors, CONST INT *Elements, CONST COLORREF *Colors)
{
UINT i;
for (i = 0; i < nColors; i++)
{
if ((UINT)(*Elements) < NUM_SYSCOLORS)
{
gpsi->argbSystem[*Elements] = *Colors;
IntGdiSetSolidBrushColor(gpsi->ahbrSystem[*Elements], *Colors);
}
Elements++;
Colors++;
}
}
签名 :VOID FASTCALL IntSetSysColors(UINT nColors, CONST INT *Elements, CONST COLORREF *Colors)。
| 参数 | 含义 |
|---|---|
nColors |
要修改的颜色数量 |
Elements |
指向 COLOR_* 索引数组(用户态传入,已被探测/拷贝) |
Colors |
指向新 COLORREF 值数组 |
作用 :实现 SetSysColors() 的内核语义:更新颜色值并就地更新对应画刷颜色 ,
使全系统立即呈现新外观,同时保持画刷句柄不变(因此所有引用方持有的句柄依然有效)。
实现流程:
- 遍历
nColors组(索引, 颜色); - 索引越界(>=31)则跳过(负索引经无符号转换同样越界);
- 写入颜色:
gpsi->argbSystem[索引] = 新颜色(共享内存,用户态 GetSysColor
立即可见); - 就地改色 :
IntGdiSetSolidBrushColor(gpsi->ahbrSystem[索引], 新颜色)。
IntGdiSetSolidBrushColor 的实现(brush.cpp:315-336):
c
VOID NTAPI IntGdiSetSolidBrushColor(HBRUSH hbr, COLORREF crColor)
{
PBRUSH pbr;
pbr = BRUSH::LockAny(hbr); // 锁定(无视属主,任何进程都能改)
if (pbr == NULL) { ERR(...); return; }
pbr->vSetSolidColor(crColor); // 成员函数
pbr->vUnlock();
}
而 BRUSH::vSetSolidColor(brush.cpp:126-136):
c
VOID BRUSH::vSetSolidColor(COLORREF crColor)
{
NT_ASSERT(this->flAttrs & BR_IS_SOLID);
this->pBrushAttr->lbColor = crColor & 0xFFFFFF; // 截断到 24 位 RGB
this->ulPalTime = -1; // 使调色板缓存失效
this->ulSurfTime = -1; // 使表面缓存失效
}
关键设计:画刷对象本身不销毁、不重建 ,只是改内部 BrushAttr.lbColor 并令
调色板/表面时间戳失效,迫使后续绘制重新计算颜色------这就是"就地改色"机制。
完整调用链(用户态发起) ------见 ntuser/ntstubs.c:287-331 NtUserSetSysColors:
c
BOOL APIENTRY NtUserSetSysColors(int cElements, CONST INT *lpaElements,
CONST COLORREF *lpaRgbValues, FLONG Flags)
{
...
if (cElements == 0) return TRUE;
if ((ULONG)cElements >= 0x40000000) { EngSetLastError(ERROR_NOACCESS); return FALSE; }
UserEnterExclusive();
_SEH2_TRY
{
ProbeForRead(lpaElements, cElements * sizeof(INT), 1);
ProbeForRead(lpaRgbValues, cElements * sizeof(COLORREF), 1);
IntSetSysColors(cElements, lpaElements, lpaRgbValues); // ← stockobj.c
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{ SetLastNtError(_SEH2_GetExceptionCode()); Ret = FALSE; }
_SEH2_END;
if (Ret)
{
UserSendNotifyMessage(HWND_BROADCAST, WM_SYSCOLORCHANGE, 0, 0); // 广播
UserRedrawDesktop(); // 重绘桌面
}
UserLeave();
return Ret;
}
而 user32 的 SetSysColors(defwnd.c:49-57)只是 NtUserSetSysColors(cElements, lpaElements, lpaRgbValues, 0) 的薄封装。
注意事项:
- 必须持有 UserLock (UserEnterExclusive)调用,防止与并发读取竞争------NtUserSetSysColors
已保证,但内部直接调用者需自行加锁; IntGdiSetSolidBrushColor用BRUSH::LockAny(不检查属主),因为系统画刷是
PUBLIC 全局对象;- 广播
WM_SYSCOLORCHANGE后由各窗口自行重绘,桌面则被强制重绘。
3.8 CreateSysColorObjects --- 系统颜色画刷工厂(stockobj.c:328-350)
c
VOID FASTCALL
CreateSysColorObjects(VOID)
{
UINT i;
for (i = 0; i < NUM_SYSCOLORS; i++)
{
gpsi->argbSystem[i] = SysColors[i];
}
/* Create the syscolor brushes */
for (i = 0; i < NUM_SYSCOLORS; i++)
{
if (gpsi->ahbrSystem[i] == NULL)
{
gpsi->ahbrSystem[i] = IntGdiCreateSolidBrush(SysColors[i]);
if (gpsi->ahbrSystem[i] != NULL)
{
GDIOBJ_ConvertToStockObj((HGDIOBJ*)&gpsi->ahbrSystem[i]);
}
}
}
}
签名 :VOID FASTCALL CreateSysColorObjects(VOID),由 ntuser/main.c:1071 的
DriverEntry 在 CreateStockObjects() 之后立即调用。
作用 :初始化共享的 gpsi->argbSystem[] 颜色表,并为 31 个系统颜色各创建一个
全局库存画刷 存入 gpsi->ahbrSystem[]。这是 GetSysColorBrush /
IntGetSysColorBrush 的数据来源。
实现流程(按源码逐条):
- 第一步循环 :
gpsi->argbSystem[i] = SysColors[i];------ 把默认颜色表整体拷入
共享结构。注意此前gpsi已被RtlZeroMemory清零,这里全部重写; - 第二步循环 :对每个 i,若
gpsi->ahbrSystem[i] == NULL(即尚未创建):IntGdiCreateSolidBrush(SysColors[i])创建实心画刷(brush.cpp:288-299,
BR_IS_SOLID | BR_IS_GLOBAL,属主 PUBLIC);- 成功则
GDIOBJ_ConvertToStockObj((HGDIOBJ*)&gpsi->ahbrSystem[i])打库存标记
(与 CreateStockObjects 末尾相同:hHmgr |= GDI_HANDLE_STOCK_MASK、
FullUnique |= GDI_ENTRY_STOCK_MASK、属主归 PUBLIC)。
关键设计:
- NULL 检查 使该函数可重入 (幂等):即使被调用两次,第二次因
ahbrSystem[i]
非 NULL 而跳过创建------这与 CreateStockObjects 的"无条件重建"形成对比。 - 画刷创建与
StockObjects[]中的 6 个库存画刷走同一条IntGdiCreateSolidBrush
路径,因此 37 个库存画刷的对象结构完全同构。 - 画刷没有 进入
StockObjects[]表,而是独立存于gpsi->ahbrSystem[]------这是两套
并行的库存体系(见 §6 对比)。
注意事项:
- 必须在
gpsi分配完成(main.c:1054)之后调用; - 若个别画刷创建失败(内存不足),
ahbrSystem[i]保持 NULL,IntGetSysColorBrush
会返回 NULL,调用方(如默认窗口背景)需能容忍 NULL 画刷------绘制代码通常对
NULL 画刷有降级处理,但极端情况下可能出现黑色背景; - 与
CreateStockObjects相同,本函数不检查画刷句柄表插入结果之外的错误。
3.9 关于 "InitStockObjects" 的澄清
早期版本的《分析_31》与部分二手资料中提及 stockobj.c 存在 InitStockObjects
("104 行内部")函数,经核对当前源码(351 行)并不存在该函数 。实际初始化由两个
函数完成:
DriverEntry(ntuser/main.c:1070-1071)
├─ CreateStockObjects() ------ 22 槽 StockObjects[]:画刷/画笔/字体/位图/调色板
└─ CreateSysColorObjects() ------ gpsi->argbSystem[] + gpsi->ahbrSystem[](31 画刷)
另外,若按"基础初始化(颜色表等)"的语义理解,则对应的是 CreateSysColorObjects()
的第一步循环(拷贝 SysColors\[\]),而非某个独立函数。本文以源码为准,特此更正。
3.10 函数清单总览
| 函数 | 位置(stockobj.c) | 可见性 | 职责 |
|---|---|---|---|
IntCreateStockPen |
102-142 | static | 库存画笔创建(PS_SOLID/PS_NULL) |
CreateStockFonts |
144-241 | static | 按代码页创建 7 个库存字体 |
CreateStockObjects |
246-284 | 全局 | 库存对象主工厂(20 公开 + DEFAULT_BITMAP) |
NtGdiGetStockObject |
291-297 | 全局(syscall) | 按索引查库存对象 |
IntSetSysColors |
299-314 | 全局(内部) | 更新系统颜色 + 就地改画刷色 |
IntGetSysColorBrush |
316-320 | 全局(内部) | 取系统颜色画刷 |
IntGetSysColor |
322-326 | 全局(内部) | 取系统颜色 |
CreateSysColorObjects |
328-350 | 全局 | 系统颜色表 + 31 个系统画刷工厂 |
StockObjects[] |
100 | 全局数据 | 22 槽库存对象表 |
hSystemBM |
52 | 全局数据 | 系统位图 DC(度量基准) |
调用关系:
DriverEntry
├─ CreateStockObjects ──┬─ IntGdiCreateSolidBrush ×6(画刷)
│ ├─ IntGdiCreateNullBrush ×1
│ ├─ IntCreateStockPen ×4(画笔)
│ ├─ GreCreateBitmap ×1(DEFAULT_BITMAP)
│ ├─ CreateStockFonts ── TextIntCreateFontIndirect ×7
│ ├─ gppalDefault(DEFAULT_PALETTE 复用)
│ └─ GDIOBJ_ConvertToStockObj ×(非空槽)
└─ CreateSysColorObjects ── IntGdiCreateSolidBrush ×31 + ConvertToStockObj ×31
4. 21 个库存对象清单
CreateStockObjects() 共创建 21 个 库存对象(20 个公开对象 + DEFAULT_BITMAP),
加上复用的 DEFAULT_PALETTE 共 22 槽。下表逐项列出创建方式 与关键属性
(全部依据源码 stockobj.c:255-273):
| 槽位 | 索引常量 | 对象类型 | 创建方式 | 关键属性 |
|---|---|---|---|---|
| 0 | WHITE_BRUSH | 画刷 | IntGdiCreateSolidBrush(RGB(255,255,255)) |
BS_SOLID 白色 |
| 1 | LTGRAY_BRUSH | 画刷 | IntGdiCreateSolidBrush(RGB(192,192,192)) |
BS_SOLID 浅灰 |
| 2 | GRAY_BRUSH | 画刷 | IntGdiCreateSolidBrush(RGB(128,128,128)) |
BS_SOLID 中灰 |
| 3 | DKGRAY_BRUSH | 画刷 | IntGdiCreateSolidBrush(RGB(64,64,64)) |
BS_SOLID 深灰 |
| 4 | BLACK_BRUSH | 画刷 | IntGdiCreateSolidBrush(RGB(0,0,0)) |
BS_SOLID 黑色 |
| 5 | NULL_BRUSH(=HOLLOW_BRUSH) | 画刷 | IntGdiCreateNullBrush() |
BS_NULL 空刷 |
| 6 | WHITE_PEN | 画笔 | IntCreateStockPen(WhitePen...) |
PS_SOLID 白色 |
| 7 | BLACK_PEN | 画笔 | IntCreateStockPen(BlackPen...) |
PS_SOLID 黑色 |
| 8 | NULL_PEN | 画笔 | IntCreateStockPen(NullPen...) |
PS_NULL 空笔 |
| 9 | (保留空位) | --- | 未创建 | NULL |
| 10 | OEM_FIXED_FONT | 字体 | TextIntCreateFontIndirect(&OEMFixedFont) |
"Terminal" 12/18 OEM_CHARSET FIXED |
| 11 | ANSI_FIXED_FONT | 字体 | TextIntCreateFontIndirect(&AnsiFixedFont) |
"Courier" 12 ANSI FIXED |
| 12 | ANSI_VAR_FONT | 字体 | TextIntCreateFontIndirect(&AnsiVarFont) |
"MS Sans Serif" 12 VARIABLE |
| 13 | SYSTEM_FONT | 字体 | TextIntCreateFontIndirect(&SystemFont) |
"System" 16/18 粗体 VARIABLE |
| 14 | DEVICE_DEFAULT_FONT | 字体 | TextIntCreateFontIndirect(&DeviceDefaultFont) |
"System" 16/18 粗体 FF_SWISS |
| 15 | DEFAULT_PALETTE | 调色板 | 复用 gppalDefault->BaseObject.hHmgr |
20 系统保留色 PAL_INDEXED |
| 16 | SYSTEM_FIXED_FONT | 字体 | TextIntCreateFontIndirect(&SystemFixedFont) |
"Fixedsys" 15/16/18 FIXED |
| 17 | DEFAULT_GUI_FONT | 字体 | TextIntCreateFontIndirect(&DefaultGuiFont) |
"MS Shell Dlg" -11/-12 |
| 18 | DC_BRUSH | 画刷 | IntGdiCreateSolidBrush(RGB(255,255,255)) |
DC 默认画刷(白色) |
| 19 | DC_PEN | 画笔 | IntCreateStockPen(BlackPen...) |
DC 默认画笔(黑色) |
| 20 | (内部未知) | --- | StockObjects[20] = NULL |
TODO 保留位 |
| 21 | DEFAULT_BITMAP | 位图 | GreCreateBitmap(1,1,1,1,NULL) |
1×1 单色位图 |
4.1 各对象类别创建要点
画刷(6+1 个:索引 0-5 及 18)
- 实心画刷全部走
IntGdiCreateSolidBrush→CreateBrushInternal(BR_IS_SOLID | BR_IS_GLOBAL, crColor, 0, NULL, NULL):new BRUSH(flAttrs, crColor, 0, NULL, NULL)------ 构造函数(brush.cpp:14-53)
分配全局唯一ulBrushUnique(InterlockedIncrementUL)、pBrushAttr指向内核
属性、ptOrigin=0、crBack/crFore=0、ulPalTime/ulSurfTime=0、pvRBrush=NULL、
hdev=NULL、dwStyleCount=0、pStyle=NULL;- 因
BR_IS_GLOBAL,跳过bAllocateBrushAttr()(不为每个进程分配用户态属性池); - 属主
BASEOBJECT::OWNER::PUBLIC,hInsertObject(PUBLIC)插入句柄表。
NULL_BRUSH走IntGdiCreateNullBrush→CreateBrushInternal(BR_IS_NULL | BR_IS_GLOBAL, 0, 0, NULL, NULL)。- 这些画刷的
flAttrs同时带BR_IS_GLOBAL | BR_IS_SOLID/NULL,GetObject时
cjGetObject依据标记填 LOGBRUSH(brush.cpp:150-198)。
画笔(3+1 个:索引 6-8 及 19)
- 全部经
IntCreateStockPen(§3.1),内部是带笔属性的BRUSH对象
(flAttrs = BR_IS_OLDSTYLEPEN | BR_IS_SOLID/NULL)。 DC_PEN复用 BlackPen 模板:黑色实线------与BLACK_PEN属性一致,但语义不同
(DC_PEN 是"当前 DC 画笔"的默认值,可被SetDCPen覆盖;BLACK_PEN 是普通
库存画笔)。
字体(7 个:索引 10-14、16、17)
- 全部经
TextIntCreateFontIndirect(freetype.c:2612)创建LFONT对象,
只保存 LOGFONTW 模板,实际字形由 FreeType 引擎在实现(Realize)时解析。 - 字体名/高度随代码页动态调整(§3.2)。
位图(1 个:索引 21 DEFAULT_BITMAP)
GreCreateBitmap(1, 1, 1, 1, NULL)------ 1×1、1 平面、1 bpp 的单色 DDB
(bitmaps.c:172-188 →GreCreateBitmapEx(..., DDB_SURFACE))。- 用途:内存 DC 的兜底表面。
CreateCompatibleDC后未选入位图时,DC 内部表面
就是它;SelectObject位图时才替换。注释见 dibobj.c:1747。
调色板(1 个:索引 15 DEFAULT_PALETTE)
- 非本文件创建 ,直接共享
gppalDefault(palette.c:66-74 的
InitPaletteImpl创建,20 个系统保留色、PAL_INDEXED,已
GDIOBJ_vReferenceObjectByPointer增引用)。 DEFAULT_PALETTE是所有系统调色板设备的默认调色板,应用可用
SelectPalette(dc, GetStockObject(DEFAULT_PALETTE), FALSE)恢复系统默认。
4.2 与 Windows 的对照
| 项 | Windows | ReactOS |
|---|---|---|
| 公开索引范围 | 0~19(STOCK_LAST=19,Win2000+ 含 DC_BRUSH/DC_PEN) | 相同(wingdi.h 同值) |
| 库存字体名 | 由系统决定(如 Tahoma/宋体) | 按代码页映射(936→宋体等) |
| DEFAULT_GUI_FONT | -11 点 MS Shell Dlg | 同(CJK 时 -12) |
| 额外内部槽 | 无公开定义 | 20 保留位 + 21 DEFAULT_BITMAP(内部) |
| 系统颜色画刷 | 31 个(COLOR_MENUBAR=30) | 同(NUM_SYSCOLORS=31) |
5. 库存对象生命周期
5.1 创建时机(会话启动期)
win32k.sys DriverEntry(ntuser/main.c:981 起)
│
├─ KeAddSystemServiceTable(Win32kSSDT, ...) // 注册系统服务表(索引 1)
├─ MmPageEntireDriver(DriverEntry) // 锁定 win32k 代码
├─ PsEstablishWin32Callouts(&CalloutData) // 注册进程/线程回调
├─ UserCreateHeap(&GlobalUserHeap, ...) // 全局 USER 堆
├─ ExInitializeResourceLite(&UserLock) // 全局用户锁
├─ gpsi = UserHeapAlloc(sizeof(*gpsi)) // SERVERINFO 共享结构
├─ RtlZeroMemory(gpsi, ...)
├─ InitGdiHandleTable() // GDI 句柄表(0x10000 句柄)
├─ InitPaletteImpl() // gppalDefault 等系统调色板
├─ CreateStockObjects() // ★ stockobj.c:21 个库存对象
├─ CreateSysColorObjects() // ★ stockobj.c:31 个系统颜色画刷
├─ InitBrushImpl() / InitPDEVImpl() / InitDcImpl()
├─ InitUserImpl() / InitWindowStationImpl() / InitDesktopImpl()
└─ ...(后续各子系统初始化)
要点:
- 顺序敏感 :
CreateStockObjects依赖InitGdiHandleTable(句柄表可插入)与
InitPaletteImpl(gppalDefault);CreateSysColorObjects依赖gpsi已分配。 - 显示设备初始化(
InitDisplayImpl/ winsta.c 的桌面创建)发生在其后 ,
hSystemBM直到那时才真正创建(winsta.c:298-301),并选入SYSTEM_FONT
库存字体。 - 上述过程在**每个会话(每个登录的 GUI 会话)**只执行一次;TS 多会话下
各会话有独立的StockObjects[]与gpsi。
5.2 存活期特性:为何 DeleteObject 无效
对象被 GDIOBJ_ConvertToStockObj 转换后具备三重"不可删除"保证:
-
句柄位标记 :
hHmgr |= GDI_HANDLE_STOCK_MASK (0x00800000),任何调用方
传入该句柄时,NtGdiDeleteObjectApp(dclife.c:950-965)第一行就拦截:cif (GDI_HANDLE_IS_STOCKOBJ(hobj)) return TRUE; // 什么都不做,假装成功 -
表条目标记 :
pentry->FullUnique |= GDI_ENTRY_STOCK_MASK,句柄表层面
该条目被标记为不可释放。 -
属主置 PUBLIC :
pentry->ObjectOwner.ulObj = 0,即便绕过上述检查,
属主检查(须GDI_OBJ_HMGR_POWNED)也会失败,返回
ERROR_INVALID_HANDLE(dclife.c:954-958)。
gdi32 用户态 DeleteObject(gdi32/objects/gdiobj.c:316-366)同样先判
GDI_HANDLE_IS_STOCKOBJ 再进 syscall(gdiobj.c:324)。
因此 DeleteObject(GetStockObject(WHITE_BRUSH)) 的语义是:成功但无效 ------
符合 Windows 契约。
5.3 反转操作:GDIOBJ_ConvertFromStockObj
c
// gdiobj.c:1500-1532
BOOL NTAPI GDIOBJ_ConvertFromStockObj(HGDIOBJ *phObj)
{
PENTRY pentry = ENTRY_ReferenceEntryByHandle(*phObj, 0);
if (!pentry) { DPRINT1(...); return FALSE; }
pentry->FullUnique &= ~GDI_ENTRY_STOCK_MASK; // 清除表条目标记
pentry->ObjectOwner.ulObj = PtrToUlong(PsGetCurrentProcessId()); // 属主改为当前进程
pobj = pentry->einfo.pobj;
pobj->hHmgr = (HGDIOBJ)((ULONG_PTR)pobj->hHmgr & ~GDI_HANDLE_STOCK_MASK); // 清句柄位
*phObj = pobj->hHmgr;
GDIOBJ_vDereferenceObject(pobj);
return TRUE;
}
- 反向转换把库存对象变成"当前进程私有对象",之后可正常
DeleteObject。 - 使用场景:
NtGdiClearBrushAttributes(brush.cpp:579-603)在
SC_BB_STOCKOBJ标志下先把画刷转出库存,再为它分配独立的用户态 brush attr
(bAllocateBrushAttr),使该进程能独立修改画刷属性而不影响其他进程。 - 对称地,
NtGdiSetBrushAttributes(brush.cpp:550-574)用
GDIOBJ_ConvertToStockObj把进程私有画刷提升为全局库存对象(用于 DC_BRUSH/
DC_PEN 的"设为系统画刷"语义)。
5.4 消亡时机
库存对象没有显式销毁路径 :不存在 DeleteStockObjects 之类函数,也不会因
进程退出而释放(属主是 PUBLIC 而非进程)。它们的生命周期与 win32k.sys 驱动
本身绑定:
- 会话结束 → 内核卸载/会话终止 → 系统回收 win32k 会话内存 → 对象随之消亡;
DriverUnload(main.c:938-947)只做FreeFontSupport等收尾,不逐个释放
库存对象(由内存管理层整体回收)。
6. 系统颜色画刷更新机制
6.1 两套库存体系的对比
| 维度 | StockObjects\[\](22 槽) | gpsi->ahbrSystem\[\](31 槽) |
|---|---|---|
| 存放位置 | stockobj.c 全局数组 | 共享结构 PERUSERSERVERINFO |
| 创建函数 | CreateStockObjects | CreateSysColorObjects |
| 查询 API(用户) | GetStockObject → NtGdiGetStockObject | GetSysColorBrush(直接读 gpsi) |
| 查询 API(内核) | NtGdiGetStockObject / 数组直访 | IntGetSysColorBrush / IntGetSysColor |
| 修改方式 | 不可改(常量对象) | SetSysColors → IntSetSysColors 就地改色 |
| 是否可删 | 否(库存标记) | 否(库存标记) |
| 典型对象 | WHITE_BRUSH、SYSTEM_FONT、DEFAULT_BITMAP | COLOR_WINDOW、COLOR_BTNFACE...... |
6.2 就地改色的完整数据流
user32 SetSysColors(cElements, lpaElements, lpaRgbValues) // defwnd.c:49-57
└─ NtUserSetSysColors(...) // ntstubs.c:287
├─ ProbeForRead 两个数组(SEH 保护)
├─ IntSetSysColors(nColors, Elements, Colors) // stockobj.c:299 ★
│ ├─ gpsi->argbSystem[i] = 新颜色 // 共享内存,用户态立即可见
│ └─ IntGdiSetSolidBrushColor(hbr, 新颜色) // brush.cpp:315
│ └─ BRUSH::vSetSolidColor // brush.cpp:126
│ ├─ pBrushAttr->lbColor = crColor & 0xFFFFFF
│ ├─ ulPalTime = -1 (调色板缓存失效)
│ └─ ulSurfTime = -1 (表面缓存失效)
├─ UserSendNotifyMessage(HWND_BROADCAST, WM_SYSCOLORCHANGE)
└─ UserRedrawDesktop()
6.3 为什么改色后所有窗口"自动"变新
- 共享颜色值 :
gpsi->argbSystem[i]是映射到所有 GUI 进程的共享内存,
GetSysColor(i)(user32,defwnd.c:16-28)直接读它------零系统调用、零延迟; - 共享画刷对象 :
gpsi->ahbrSystem[i]是全局唯一画刷,所有窗口类
hbrBackground、所有IntGetSysColorBrush(i)调用拿到的都是同一个句柄; - 就地改色 :
vSetSolidColor只改lbColor并令时间戳失效,不改句柄------
所有已持有该句柄的进程无需重新获取,下一次绘制自然使用新颜色; - 通知重绘 :
WM_SYSCOLORCHANGE广播给所有顶层窗口 +UserRedrawDesktop
强制桌面重绘,让新颜色立即上屏。
6.4 缓存失效语义(ulPalTime / ulSurfTime = -1)
BRUSH 对象中有两个"时间戳"字段:
ulPalTime:上次按调色板计算画刷颜色的时间;ulSurfTime:上次在表面上实现画刷的时间。
置为 -1(全 1)使它们必然不等于任何当前时间,从而强制 GDI 在下次绘制该画刷时
重新做调色板匹配与表面实现 (把 RGB 换算为设备色),确保新颜色真正生效,
而非沿用旧的缓存颜色。
7. 调用链(mermaid)
7.1 初始化调用链
渲染错误: Mermaid 渲染失败: Parse error on line 16: ...gbSystem = SysColors\[\]] G --> G2[Int -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'SQS'
7.2 运行期查询与修改调用链
#mermaid-svg-94QI3zvnDJ6KXxcw{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-94QI3zvnDJ6KXxcw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-94QI3zvnDJ6KXxcw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-94QI3zvnDJ6KXxcw .error-icon{fill:#552222;}#mermaid-svg-94QI3zvnDJ6KXxcw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-94QI3zvnDJ6KXxcw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-94QI3zvnDJ6KXxcw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-94QI3zvnDJ6KXxcw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-94QI3zvnDJ6KXxcw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-94QI3zvnDJ6KXxcw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-94QI3zvnDJ6KXxcw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-94QI3zvnDJ6KXxcw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-94QI3zvnDJ6KXxcw .marker.cross{stroke:#333333;}#mermaid-svg-94QI3zvnDJ6KXxcw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-94QI3zvnDJ6KXxcw p{margin:0;}#mermaid-svg-94QI3zvnDJ6KXxcw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-94QI3zvnDJ6KXxcw .cluster-label text{fill:#333;}#mermaid-svg-94QI3zvnDJ6KXxcw .cluster-label span{color:#333;}#mermaid-svg-94QI3zvnDJ6KXxcw .cluster-label span p{background-color:transparent;}#mermaid-svg-94QI3zvnDJ6KXxcw .label text,#mermaid-svg-94QI3zvnDJ6KXxcw span{fill:#333;color:#333;}#mermaid-svg-94QI3zvnDJ6KXxcw .node rect,#mermaid-svg-94QI3zvnDJ6KXxcw .node circle,#mermaid-svg-94QI3zvnDJ6KXxcw .node ellipse,#mermaid-svg-94QI3zvnDJ6KXxcw .node polygon,#mermaid-svg-94QI3zvnDJ6KXxcw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-94QI3zvnDJ6KXxcw .rough-node .label text,#mermaid-svg-94QI3zvnDJ6KXxcw .node .label text,#mermaid-svg-94QI3zvnDJ6KXxcw .image-shape .label,#mermaid-svg-94QI3zvnDJ6KXxcw .icon-shape .label{text-anchor:middle;}#mermaid-svg-94QI3zvnDJ6KXxcw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-94QI3zvnDJ6KXxcw .rough-node .label,#mermaid-svg-94QI3zvnDJ6KXxcw .node .label,#mermaid-svg-94QI3zvnDJ6KXxcw .image-shape .label,#mermaid-svg-94QI3zvnDJ6KXxcw .icon-shape .label{text-align:center;}#mermaid-svg-94QI3zvnDJ6KXxcw .node.clickable{cursor:pointer;}#mermaid-svg-94QI3zvnDJ6KXxcw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-94QI3zvnDJ6KXxcw .arrowheadPath{fill:#333333;}#mermaid-svg-94QI3zvnDJ6KXxcw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-94QI3zvnDJ6KXxcw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-94QI3zvnDJ6KXxcw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-94QI3zvnDJ6KXxcw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-94QI3zvnDJ6KXxcw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-94QI3zvnDJ6KXxcw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-94QI3zvnDJ6KXxcw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-94QI3zvnDJ6KXxcw .cluster text{fill:#333;}#mermaid-svg-94QI3zvnDJ6KXxcw .cluster span{color:#333;}#mermaid-svg-94QI3zvnDJ6KXxcw 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-94QI3zvnDJ6KXxcw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-94QI3zvnDJ6KXxcw rect.text{fill:none;stroke-width:0;}#mermaid-svg-94QI3zvnDJ6KXxcw .icon-shape,#mermaid-svg-94QI3zvnDJ6KXxcw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-94QI3zvnDJ6KXxcw .icon-shape p,#mermaid-svg-94QI3zvnDJ6KXxcw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-94QI3zvnDJ6KXxcw .icon-shape .label rect,#mermaid-svg-94QI3zvnDJ6KXxcw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-94QI3zvnDJ6KXxcw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-94QI3zvnDJ6KXxcw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-94QI3zvnDJ6KXxcw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 修改系统颜色
SetSysColors
user32/defwnd.c:49
NtUserSetSysColors
ntstubs.c:287
IntSetSysColors
stockobj.c:299
gpsi->argbSystem i = 新值
IntGdiSetSolidBrushColor
brush.cpp:315
vSetSolidColor
lbColor 更新 + 缓存失效
WM_SYSCOLORCHANGE 广播
- UserRedrawDesktop
查询系统颜色/画刷
GetSysColor i
user32/defwnd.c:19
直接读 gpsi->argbSystem i
共享内存 零 syscall
GetSysColorBrush i
user32/defwnd.c:36
直接读 gpsi->ahbrSystem i
ntuser 内部绘制
draw/menu/nonclient...
IntGetSysColor / IntGetSysColorBrush
stockobj.c:316/322
查询库存对象
未命中
GetStockObject i
gdi32/gdiobj.c:11
NtGdiGetStockObject
本地缓存命中?
NtGdiGetStockObject
stockobj.c:291
StockObjects i
22 槽数组
7.3 删除保护链
#mermaid-svg-vcfd7NgYwgAD89qx{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-vcfd7NgYwgAD89qx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vcfd7NgYwgAD89qx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vcfd7NgYwgAD89qx .error-icon{fill:#552222;}#mermaid-svg-vcfd7NgYwgAD89qx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vcfd7NgYwgAD89qx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vcfd7NgYwgAD89qx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vcfd7NgYwgAD89qx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vcfd7NgYwgAD89qx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vcfd7NgYwgAD89qx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vcfd7NgYwgAD89qx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vcfd7NgYwgAD89qx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vcfd7NgYwgAD89qx .marker.cross{stroke:#333333;}#mermaid-svg-vcfd7NgYwgAD89qx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vcfd7NgYwgAD89qx p{margin:0;}#mermaid-svg-vcfd7NgYwgAD89qx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vcfd7NgYwgAD89qx .cluster-label text{fill:#333;}#mermaid-svg-vcfd7NgYwgAD89qx .cluster-label span{color:#333;}#mermaid-svg-vcfd7NgYwgAD89qx .cluster-label span p{background-color:transparent;}#mermaid-svg-vcfd7NgYwgAD89qx .label text,#mermaid-svg-vcfd7NgYwgAD89qx span{fill:#333;color:#333;}#mermaid-svg-vcfd7NgYwgAD89qx .node rect,#mermaid-svg-vcfd7NgYwgAD89qx .node circle,#mermaid-svg-vcfd7NgYwgAD89qx .node ellipse,#mermaid-svg-vcfd7NgYwgAD89qx .node polygon,#mermaid-svg-vcfd7NgYwgAD89qx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vcfd7NgYwgAD89qx .rough-node .label text,#mermaid-svg-vcfd7NgYwgAD89qx .node .label text,#mermaid-svg-vcfd7NgYwgAD89qx .image-shape .label,#mermaid-svg-vcfd7NgYwgAD89qx .icon-shape .label{text-anchor:middle;}#mermaid-svg-vcfd7NgYwgAD89qx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vcfd7NgYwgAD89qx .rough-node .label,#mermaid-svg-vcfd7NgYwgAD89qx .node .label,#mermaid-svg-vcfd7NgYwgAD89qx .image-shape .label,#mermaid-svg-vcfd7NgYwgAD89qx .icon-shape .label{text-align:center;}#mermaid-svg-vcfd7NgYwgAD89qx .node.clickable{cursor:pointer;}#mermaid-svg-vcfd7NgYwgAD89qx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vcfd7NgYwgAD89qx .arrowheadPath{fill:#333333;}#mermaid-svg-vcfd7NgYwgAD89qx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vcfd7NgYwgAD89qx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vcfd7NgYwgAD89qx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vcfd7NgYwgAD89qx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vcfd7NgYwgAD89qx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vcfd7NgYwgAD89qx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vcfd7NgYwgAD89qx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vcfd7NgYwgAD89qx .cluster text{fill:#333;}#mermaid-svg-vcfd7NgYwgAD89qx .cluster span{color:#333;}#mermaid-svg-vcfd7NgYwgAD89qx 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-vcfd7NgYwgAD89qx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vcfd7NgYwgAD89qx rect.text{fill:none;stroke-width:0;}#mermaid-svg-vcfd7NgYwgAD89qx .icon-shape,#mermaid-svg-vcfd7NgYwgAD89qx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vcfd7NgYwgAD89qx .icon-shape p,#mermaid-svg-vcfd7NgYwgAD89qx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vcfd7NgYwgAD89qx .icon-shape .label rect,#mermaid-svg-vcfd7NgYwgAD89qx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vcfd7NgYwgAD89qx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vcfd7NgYwgAD89qx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vcfd7NgYwgAD89qx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是(库存对象)
否
是
否
否
是
DeleteObject h
gdi32/gdiobj.c:316
GDI_HANDLE_IS_STOCKOBJ h?
直接返回 TRUE
不进入内核
NtGdiDeleteObjectApp
dclife.c:950
GDI_HANDLE_IS_STOCKOBJ?
return TRUE 什么都不做
属主 == GDI_OBJ_HMGR_POWNED?
ERROR_INVALID_HANDLE
return FALSE
GreDeleteObject / IntGdiDeleteDC
8. 源码索引
8.1 本文涉及文件
| 文件 | 关键内容 | 说明 |
|---|---|---|
| stockobj.c(file:///d:/reactos/win32ss/gdi/ntgdi/stockobj.c) | 全部库存对象逻辑(351 行) | 本文主线 |
| gdiobj.h(file:///d:/reactos/win32ss/gdi/ntgdi/gdiobj.h) | GDIOBJ API 声明(ConvertToStockObj 等) | 197-198 行 |
| gdiobj.c(file:///d:/reactos/win32ss/gdi/ntgdi/gdiobj.c) | ConvertToStockObj(1466)/ConvertFromStockObj(1500) | 库存标记核心 |
| brush.cpp(file:///d:/reactos/win32ss/gdi/ntgdi/brush.cpp) | IntGdiCreateSolidBrush(288)/NullBrush(279)/SetSolidBrushColor(315)/BRUSH 类 | 画刷实现 |
| font.c(file:///d:/reactos/win32ss/gdi/ntgdi/font.c) | HfontCreate/GreCreateFontIndirectW 等字体管理 | 系统字体背景 |
| freetype.c(file:///d:/reactos/win32ss/gdi/ntgdi/freetype.c) | TextIntCreateFontIndirect(2612)/IntCharSetFromCodePage(318) | 库存字体创建 |
| main.c(file:///d:/reactos/win32ss/user/ntuser/main.c) | DriverEntry → CreateStockObjects(1070)/CreateSysColorObjects(1071) | 初始化调用点 |
| dclife.c(file:///d:/reactos/win32ss/gdi/ntgdi/dclife.c) | NtGdiDeleteObjectApp(950) 库存对象放行 | 删除保护 |
| defwnd.c(file:///d:/reactos/win32ss/user/user32/windows/defwnd.c) | GetSysColor(19)/GetSysColorBrush(36)/SetSysColors(49) | 用户态 API |
| ntstubs.c(file:///d:/reactos/win32ss/user/ntuser/ntstubs.c) | NtUserSetSysColors(287) → IntSetSysColors | syscall 封装 |
| winsta.c(file:///d:/reactos/win32ss/user/ntuser/winsta.c) | hSystemBM 创建(298) + SYSTEM_FONT 选中(300) | 系统位图 DC |
| bitmaps.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitmaps.c) | GreCreateBitmap(172) | DEFAULT_BITMAP |
| palette.c(file:///d:/reactos/win32ss/gdi/ntgdi/palette.c) | InitPaletteImpl(66) → gppalDefault | DEFAULT_PALETTE |
8.2 头文件
| 头文件 | 关键宏/结构 |
|---|---|
| wingdi.h(file:///d:/reactos/sdk/include/psdk/wingdi.h) | WHITE_BRUSH=0 ... DC_PEN=19、STOCK_LAST=19(896-924 行) |
| winuser.h(file:///d:/reactos/sdk/include/psdk/winuser.h) | COLOR_SCROLLBAR=0 ... COLOR_MENUBAR=30(922-958 行) |
| ntgdihdl.h(file:///d:/reactos/win32ss/include/ntgdihdl.h) | GDI_HANDLE_STOCK_MASK=0x00800000、GDI_HANDLE_IS_STOCKOBJ、GDI_ENTRY_STOCK_MASK |
| ntgdityp.h(file:///d:/reactos/win32ss/include/ntgdityp.h) | NB_STOCK_OBJECTS=22(192)、DEFAULT_BITMAP=21(195) |
| ntuser.h(file:///d:/reactos/win32ss/include/ntuser.h) | NUM_SYSCOLORS=31(954)、PERUSERSERVERINFO.argbSystem/ahbrSystem(1006-1008) |
8.3 关键行号速查(stockobj.c)
| 行号 | 内容 |
|---|---|
| 16-49 | SysColors\[\] 默认系统颜色表 |
| 52 | hSystemBM 全局 DC |
| 56-98 | WhitePen/BlackPen/NullPen + 7 个 LOGFONTW 模板 |
| 100 | StockObjectsNB_STOCK_OBJECTS |
| 102-142 | IntCreateStockPen |
| 144-241 | CreateStockFonts |
| 246-284 | CreateStockObjects |
| 291-297 | NtGdiGetStockObject(syscall) |
| 299-314 | IntSetSysColors |
| 316-320 | IntGetSysColorBrush |
| 322-326 | IntGetSysColor |
| 328-350 | CreateSysColorObjects |
8.4 关联主题
| 主题 | 关联分析 | 说明 |
|---|---|---|
| GDI 句柄表与对象管理 | 《分析_31》3.1(gdiobj.c) | GDIOBJ 句柄/库存标记 |
| 画刷对象实现 | brush.cpp | BRUSH 类、CreateBrushInternal |
| 系统颜色与窗口绘制 | ntuser draw.c/menu.c/nonclient.c | IntGetSysColor 高频使用 |
| 字体实现与栅格化 | freetype.c / font.c | TextIntCreateFontIndirect、LFONT |
本文档基于 ReactOS 源代码
win32ss/gdi/ntgdi/stockobj.c及关联模块分析(2026 年 8 月)更正说明:stockobj.c 中不存在
InitStockObjects函数(详见 §3.9);初始化由
CreateStockObjects+CreateSysColorObjects完成。