ReactOS 图形系统分析(53):系统库存对象 — stockobj.c

ReactOS 图形系统分析(53):系统库存对象 --- stockobj.c

本文基于 ReactOS 源码 win32ss/gdi/ntgdi/stockobj.c(351 行,截至 2026 年 8 月)

及关联模块 gdiobj.cbrush.cppfont.cfreetype.cmain.cdclife.cdefwnd.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)。

这些对象的核心特性:

特性 说明
全局共享 所有进程、所有线程共用同一份对象,对象属主为 PUBLICGDI_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_BRUSHBLACK_PENSYSTEM_FONT 这类对象是所有 GUI 程序最常用的资源。若每个

进程、每次绘图都重新创建再删除,将是巨大的系统调用开销。库存对象让它们成为进程间共享的
常量
GetStockObject 只需一次数组下标访问即可拿到句柄。

2)一致性:保证全系统使用同一套"系统外观"

系统颜色画刷(COLOR_WINDOWCOLOR_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_SOLIDPS_NULL(来自 LOGPEN.lopnStyle)
dwWidth 逻辑宽度(来自 LOGPEN.lopnWidth.x)
ulBrushStyle 底层画刷样式,统一传 BS_SOLID
ulColor 颜色(来自 LOGPEN.lopnColor)

作用 :这是 ReactOS 内部专用的"库存画笔"快速创建路径,只处理最简单的情形

(实线/空线、零宽度),不经过完整的 NtGdiCreatePen 用户态路径。

实现流程(按源码逐条):

  1. PEN_AllocPenWithHandle() 分配并插入句柄表的 PBRUSH 对象(画笔在 ReactOS 内部
    就是带笔属性的 BRUSH 对象------所以返回类型是 PBRUSH、类型断言见 brush.cpp:95-97)。
  2. 空笔宽度强制 1if ((dwPenStyle & PS_STYLE_MASK) == PS_NULL) dwWidth = 1;
    ------ 空线无需宽度,但内部实现要求宽度字段有效。
  3. 填充各字段:
    • iHatch = 0(无影线);
    • lWidth = abs(dwWidth),并用 FLOATOBJ_SetLong 同步浮点宽度 eWidth
    • ulPenStyle = dwPenStyle(保存原始样式字,含端帽/连接符位);
    • BrushAttr.lbColor = ulColor(颜色写入画刷属性,与画刷共用同一结构);
    • iBrushStyle = ulBrushStyle(BS_SOLID);
    • hbmClient = NULLdwStyleCount = 0pStyle = 0(无图案、无自定义线型数组);
    • flAttrs = BR_IS_OLDSTYLEPEN(标记为传统 LOGPEN 画笔,非扩展画笔)。
  4. PS_STYLE_MASK 设置附加标记:PS_NULL → BR_IS_NULLPS_SOLID → BR_IS_SOLID
  5. 取出句柄 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[] 对应槽位。

实现流程(按源码逐条):

  1. 取代码页RtlGetDefaultCodePage(&ActiveCodePage, &OemCodePage)

  2. 换算字符集IntCharSetFromCodePage()(定义于 freetype.c:318,内部查表把

    Windows 代码页映射为 *_CHARSET 值)得到 bActiveCharSetbOemCharSet

    若 OEM 字符集换算结果为 DEFAULT_CHARSET,则强制改为 OEM_CHARSET

  3. 按活动代码页选择 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 环境。

  4. 按 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
  5. 统一设置字符集OEMFixedFont.lfCharSet = bOemCharSet(OEM 字体用 OEM 字符集),

    其余 6 个全部用 bActiveCharSet

  6. 创建字体 :连续 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 的核心入口

实现流程(按源码逐条):

  1. 6 个画刷 :全部用 IntGdiCreateSolidBrush()(brush.cpp:288-299)创建实心画刷,

    NULL_BRUSHIntGdiCreateNullBrush()(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 空画刷

    其中 IntGdiCreateSolidBrushCreateBrushInternal(BR_IS_SOLID | BR_IS_GLOBAL, ...)

    new BRUSH(...) →(因 BR_IS_GLOBAL 跳过用户态属性分配)→ hInsertObject(PUBLIC) 插入

    句柄表 → 解锁返回。BR_IS_GLOBAL 意味着全局对象、跳过 brush attr 的用户态池分配

  2. 4 个画笔 :调 IntCreateStockPen(§3.1)。注意 DC_PEN 用的是 BlackPen 模板

    (黑色),与 BLACK_PEN 完全相同;WHITE_PEN 白色、NULL_PEN 空线。

  3. 槽位 20 置 NULL :源码注释 /* TODO: Unknown internal stock object */------保留位,

    无实际对象。

  4. DEFAULT_BITMAPGreCreateBitmap(1, 1, 1, 1, NULL) 创建 1×1 单色 (1 平面 1 位)

    位图(bitmaps.c:172-188 → GreCreateBitmapEx,DDB_SURFACE)。它是内存 DC 的

    默认画刷位图 :新建内存 DC 在未选入位图前,内部以该 1×1 位图兜底(dibobj.c:1747

    有相关注释)。

  5. 7 个字体CreateStockFonts()(§3.2)。

  6. DEFAULT_PALETTE :直接把全局默认调色板对象句柄拿来用:

    StockObjects[DEFAULT_PALETTE] = (HGDIOBJ)gppalDefault->BaseObject.hHmgr;

    ------ gppalDefaultInitPaletteImpl()(palette.c:66-74)创建:20 个系统保留色的

    PAL_INDEXED 调色板,并 GDIOBJ_vReferenceObjectByPointer 增引用。注意这里没有

    重新创建,而是共享同一个调色板对象(因此也不属于"本函数创建")。

  7. 统一打库存标记 :遍历 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 导出

实现流程

  1. 参数校验:Object < 0Object >= NB_STOCK_OBJECTS(22) 则返回 NULL;
  2. 否则直接返回 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:2407defwnd.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() 的内核语义:更新颜色值并就地更新对应画刷颜色

使全系统立即呈现新外观,同时保持画刷句柄不变(因此所有引用方持有的句柄依然有效)。

实现流程

  1. 遍历 nColors(索引, 颜色)
  2. 索引越界(>=31)则跳过(负索引经无符号转换同样越界);
  3. 写入颜色:gpsi->argbSystem[索引] = 新颜色(共享内存,用户态 GetSysColor
    立即可见);
  4. 就地改色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
    已保证,但内部直接调用者需自行加锁;
  • IntGdiSetSolidBrushColorBRUSH::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 的

DriverEntryCreateStockObjects() 之后立即调用。

作用 :初始化共享的 gpsi->argbSystem[] 颜色表,并为 31 个系统颜色各创建一个

全局库存画刷 存入 gpsi->ahbrSystem[]。这是 GetSysColorBrush /

IntGetSysColorBrush 的数据来源。

实现流程(按源码逐条):

  1. 第一步循环gpsi->argbSystem[i] = SysColors[i]; ------ 把默认颜色表整体拷入
    共享结构。注意此前 gpsi 已被 RtlZeroMemory 清零,这里全部重写;
  2. 第二步循环 :对每个 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)

  • 实心画刷全部走 IntGdiCreateSolidBrushCreateBrushInternal(BR_IS_SOLID | BR_IS_GLOBAL, crColor, 0, NULL, NULL)
    • new BRUSH(flAttrs, crColor, 0, NULL, NULL) ------ 构造函数(brush.cpp:14-53)
      分配全局唯一 ulBrushUniqueInterlockedIncrementUL)、pBrushAttr 指向内核
      属性、ptOrigin=0crBack/crFore=0ulPalTime/ulSurfTime=0pvRBrush=NULL
      hdev=NULLdwStyleCount=0pStyle=NULL
    • BR_IS_GLOBAL跳过 bAllocateBrushAttr()(不为每个进程分配用户态属性池);
    • 属主 BASEOBJECT::OWNER::PUBLIChInsertObject(PUBLIC) 插入句柄表。
  • NULL_BRUSHIntGdiCreateNullBrushCreateBrushInternal(BR_IS_NULL | BR_IS_GLOBAL, 0, 0, NULL, NULL)
  • 这些画刷的 flAttrs 同时带 BR_IS_GLOBAL | BR_IS_SOLID/NULLGetObject
    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 转换后具备三重"不可删除"保证:

  1. 句柄位标记hHmgr |= GDI_HANDLE_STOCK_MASK (0x00800000),任何调用方
    传入该句柄时,NtGdiDeleteObjectApp(dclife.c:950-965)第一行就拦截:

    c 复制代码
    if (GDI_HANDLE_IS_STOCKOBJ(hobj)) return TRUE;   // 什么都不做,假装成功
  2. 表条目标记pentry->FullUnique |= GDI_ENTRY_STOCK_MASK,句柄表层面
    该条目被标记为不可释放。

  3. 属主置 PUBLICpentry->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 为什么改色后所有窗口"自动"变新

  1. 共享颜色值gpsi->argbSystem[i] 是映射到所有 GUI 进程的共享内存,
    GetSysColor(i)(user32,defwnd.c:16-28)直接读它------零系统调用、零延迟
  2. 共享画刷对象gpsi->ahbrSystem[i] 是全局唯一画刷,所有窗口类
    hbrBackground、所有 IntGetSysColorBrush(i) 调用拿到的都是同一个句柄
  3. 就地改色vSetSolidColor 只改 lbColor 并令时间戳失效,不改句柄------
    所有已持有该句柄的进程无需重新获取,下一次绘制自然使用新颜色;
  4. 通知重绘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 完成。

相关推荐
SQL-First布道者1 小时前
持久层框架的评价标准:只有一个
java·spring boot·spring·tomcat·mybatis·spring jdbc
ZJU_统一阿萨姆1 小时前
【推理优化进阶】通信关键路径:NCCL、RDMA 与计算通信重叠
开发语言·人工智能·语言模型·架构·系统架构
我不是疯子是傻子2 小时前
Qt 程序打包部署全攻略:从 windeployqt 到 Inno Setup 的静默安装与版本迭代实战
开发语言·qt
j7~2 小时前
【C语言】《C语言函数递归完全指南:思想、限制、经典例题(阶乘/斐波那契/青蛙跳台阶/汉诺塔)与迭代优化》
c语言·函数递归·汉诺塔问题·青蛙跳台阶问题·递归与迭代·n的阶乘
城管不管3 小时前
重生——第十次面试之开源中国一面挂
java·linux·开发语言·算法·面试·职场和发展·开源
霸道流氓气质3 小时前
Java开发者AI编程常用MCP、Skills、Rules全景汇总
java·开发语言·ai编程
zoujiahui_20183 小时前
Python 包与环境管理工具 uv
开发语言·python·uv
YSL0701243 小时前
动态内存管理
c语言
吴声子夜歌4 小时前
Java面试题——JVM(二)
java·开发语言·jvm