ReactOS 窗口系统架构分析

ReactOS 窗口系统架构分析

目录

  1. 概述
  2. 整体架构分层
  3. 核心数据结构
  4. 主线一:窗口显示
  5. 主线二:窗口管理
  6. 主线三:鼠标控制
  7. 消息传递机制
  8. 初始化与生命周期
  9. 窗口系统分册规划
  10. 附录:关键文件索引

1. 概述

ReactOS 的窗口系统位于 win32ss/user/(Win32 子系统 USER 半边),是 win32k.sys 内核模块中与 GDI 平级的另一大组成部分。GDI 负责"画",USER(窗口系统)负责"管"------管理窗口、消息、输入、桌面、菜单、剪贴板等。整个窗口系统被编译为两部分:

  • win32k.sys (内核模式):user/ntuser/ 目录下的 USER 内核实现,处理所有 NtUser* 系统调用。
  • user32.dll(用户模式):导出全部 Win32 USER API(CreateWindowExW、GetMessage、SendMessage、TrackPopupMenu 等),封装系统调用。

窗口系统覆盖的功能包括:

  • 窗口对象的创建、销毁、遍历、Z 序管理
  • 窗口类(Class)注册与实例化
  • 消息队列与消息循环(GetMessage/PeekMessage/SendMessage/PostMessage)
  • 窗口绘制协议(WM_PAINT、更新区域、非客户区绘制)
  • 输入子系统(鼠标、键盘、触摸、输入法)
  • 桌面(Desktop)与窗口站(Window Station)两级安全容器
  • 菜单、滚动条、计时器、钩子、热键、剪贴板、焦点、光标等窗口管理设施

三条核心主线(本分析文档集的编排主线):

主线 回答的问题 核心文件
窗口显示 窗口怎么样实现显示 window.c、class.c、winpos.c、painting.c、vis.c、windc.c、nonclient.c、layered.c、draw.c
窗口管理 怎么样实现窗口管理 msgqueue.c、message.c、defwnd.c、menu.c、scrollbar.c、timer.c、prop.c、accelerator.c、hook.c、focus.c、clipboard.c、desktop.c、winsta.c、object.c、cursoricon.c
鼠标控制 窗口怎么样实现鼠标控制 input.c、mouse.c、keyboard.c、kbdlayout.c、ime.c、event.c

2. 整体架构分层

复制代码
┌─────────────────────────────────────────────────────────┐
│                    应用程序 (Application)                │
├─────────────────────────────────────────────────────────┤
│   user32.dll (用户模式 USER)    gdi32.dll (用户模式 GDI) │
│         │ NtUser* 系统调用               │ NtGdi*       │
├─────────────────────────────────────────────────────────┤
│               win32k.sys (内核模式)                      │
│  ┌──────────────┬──────────────┬──────────────┐        │
│  │ user/ntuser/ │  gdi/ntgdi/  │  reactx/     │        │
│  │ (窗口系统)   │  (内核 GDI)  │  (DirectX)   │        │
│  └──────┬───────┴──────┬───────┴──────┬───────┘        │
│         │              │              │                  │
│  ┌──────┴──────────────┴──────────────┴───────┐        │
│  │         gdi/eng/ (GDI 引擎)                │        │
│  │  ┌──────────────────────────────┐        │        │
│  │  │  gdi/dib/ (DIB 渲染引擎)     │        │        │
│  │  └──────────────────────────────┘        │        │
│  └──────┬───────────────────────────┘        │        │
├─────────┼──────────────────────────────────────────────┤
│         ↓ (DDI 回调)                                  │
│  ┌──────────────┐  ┌──────────────┐                    │
│  │ framebuf/    │  │ vga/ vga_new/│  ... 显示驱动      │
│  └──────────────┘  └──────────────┘                    │
└─────────────────────────────────────────────────────────┘

分层说明:

层次 位置 功能
user32.dll 用户模式 DLL 导出 Win32 USER API(窗口、消息、输入、菜单),封装为 NtUser* 系统调用
ntuser/ 内核模式 (win32k.sys) USER 内核实现:窗口对象、消息队列、输入处理、桌面/窗口站管理
winsrv/ 用户模式服务器 CSRSS 的服务器 DLL(usersrv/consrv),负责会话初始化、硬错误、控制台
GDI 引擎 内核模式 窗口绘制最终委托给 GDI(窗口 DC、可见区域、BitBlt 到屏幕表面)
显示驱动 内核模式 通过 GDI 引擎间接完成屏幕输出

USER 与 GDI 的关系 :USER 不直接操作显示硬件。窗口的"显示"本质是:USER 维护窗口对象与可见区域(hrgnVis),当窗口需要重绘时产生 WM_PAINT 消息,应用程序通过 GetDC/BeginPaint 获得窗口 DC(windc.c 创建),GDI 以窗口可见区域为裁剪区绘制到屏幕表面。可见区域(vis.c)的合成是 USER 与 GDI 协作的桥梁。


3. 核心数据结构

3.1 WND(窗口对象)--- ntuser.h(file:///d:/reactos/win32ss/include/ntuser.h#L693-L767)

窗口对象是窗口系统的核心,每个 Win32 窗口在内核中对应一个 WND 结构:

c 复制代码
typedef struct _WND
{
    THRDESKHEAD head;          // 对象头:pti(线程)+ rpdesk(桌面)+ 引用计数
    DWORD state;               // 窗口状态位(WNDS_*)
    DWORD state2;
    DWORD ExStyle;             // 扩展样式 WS_EX_*
    DWORD style;               // 窗口样式 WS_*
    HINSTANCE hModule;         // 创建模块
    DWORD fnid;                // 窗口功能 ID(FNID_*,区分桌面/消息/滚动条窗口等)
    struct _WND *spwndNext;    // 兄弟链表:下一个
    struct _WND *spwndPrev;    // 兄弟链表:上一个
    struct _WND *spwndParent;  // 父窗口
    struct _WND *spwndChild;   // 第一个子窗口
    struct _WND *spwndOwner;   // 拥有者窗口
    RECT rcWindow;             // 窗口矩形(屏幕坐标)
    RECT rcClient;             // 客户区矩形
    WNDPROC lpfnWndProc;       // 窗口过程(用户态回调)
    PCLS pcls;                 // 指向窗口类
    HRGN hrgnUpdate;           // 更新区域(待重绘区域)
    LIST_ENTRY PropListHead;   // 属性链表(SetProp/GetProp)
    ULONG PropListItems;       // 属性数量
    PSBINFO pSBInfo;           // 滚动条信息
    HMENU SystemMenu;          // 系统菜单句柄
    UINT_PTR IDMenu;           // 窗口菜单句柄或子窗口 ID
    HRGN hrgnClip;             // 裁剪区域(WINDOWPOS 设置)
    HRGN hrgnNewFrame;         // 新窗口框区域(重绘用)
    LARGE_UNICODE_STRING strName;  // 窗口标题
    ULONG cbwndExtra;          // 窗口附加字节大小
    struct _WND *spwndLastActive; // 上一激活窗口(同级)
    HIMC hImc;                 // 输入上下文
    LONG_PTR dwUserData;
    PVOID pActCtx;             // 激活上下文
    struct _WND *spwndClipboardListener; // 剪贴板监听窗口链
    DWORD ExStyle2;            // 第二扩展样式
    struct { RECT NormalRect; POINT IconPos; POINT MaxPos; UINT flags; } InternalPos; // 恢复/最大化位置
    UINT Unicode:1;            // 是否 Unicode 窗口
    PSBINFOEX pSBInfoex;       // 滚动条扩展信息
    LIST_ENTRY ThreadListEntry; // 线程窗口链表项
    PVOID DialogPointer;
} WND, *PWND;

窗口对象头 THRDESKHEAD 包含 pti(所属线程)、rpdesk(所属桌面)与引用计数,窗口对象通过 USER 对象引用计数机制(UserReferenceObject/UserDereferenceObject,object.c)管理生命周期。窗口句柄 HWND 由 UserCreateObject 分配,其值域分为窗口句柄区与菜单/光标/图标句柄区。

3.2 CLS(窗口类)--- class.c / class.h

c 复制代码
typedef struct _CLS  // PCLS
{
    HEAD head;                    // 对象头
    struct _CLS *pclsNext;        // 类链表(同桌面)
    ATOM atomClassName;           // 类名原子
    HINSTANCE hModule;            // 所属模块
    WNDPROC lpfnWndProc;          // 类窗口过程
    INT cbWndExtra;               // 窗口附加字节
    INT cbClsExtra;               // 类附加字节
    UINT style;                   // CS_* 类样式
    HICON hIcon;                  // 大图标
    HICON hIconSm;                // 小图标
    HCURSOR hCursor;              // 光标
    HBRUSH hbrBackground;         // 背景画刷
    LPCWSTR lpszMenuName;         // 菜单名
    // ... 还有 ClassAtom/Desktop/version 等
} CLS, *PCLS;

类分为系统类SystemClassList,由 win32k 注册,如"#32770"对话框类)与应用类 (每进程 pclsPrivateList/pclsPublicList)。类对象记录了创建该类窗口所需的一切默认属性。窗口的 lpfnWndProc 在创建时从类的窗口过程复制。

3.3 THREADINFO / PROCESSINFO --- win32.h

每个 Win32 线程对应一个 THREADINFO(pti),每个进程对应一个 PROCESSINFO(ppi),通过 PsGetCurrentThreadWin32Thread()/PsGetCurrentProcessWin32Process() 获取:

c 复制代码
typedef struct _THREADINFO : _W32THREAD
{
    PPROCESSINFO        ppi;              // 所属进程
    struct _USER_MESSAGE_QUEUE* MessageQueue; // 线程消息队列
    struct tagKL*       KeyboardLayout;   // 键盘布局
    struct _DESKTOP*    rpdesk;           // 线程桌面
    struct _CLIENTTHREADINFO* pcti;       // 用户态线程信息
    FLONG               TIF_flags;        // 线程标志(TIF_*)
    struct _USER_SENT_MESSAGE *pusmSent;  // 发送消息
    struct _USER_SENT_MESSAGE *pusmCurrent;
    LIST_ENTRY          SentMessagesListHead;  // 发送消息链表
    LONG                timeLast;         // 最后消息时间
    BOOLEAN             QuitPosted;       // WM_QUIT 已投递
    INT                 exitCode;         // 退出码
    UINT                cPaintsReady;     // 待处理 WM_PAINT 数
    UINT                cTimersReady;     // 待处理 WM_TIMER 数
    struct tagMENUSTATE* pMenuState;      // 菜单状态
    PTHREADINFO         ptiSibling;       // 线程链表
    ULONG               fsHooks;          // 已安装钩子标志
    LIST_ENTRY          PostedMessagesListHead; // 投递消息链表
    UINT                cWindows;         // 线程窗口数
    UINT                cVisWindows;      // 可见窗口数
    LIST_ENTRY          WindowListHead;   // 线程窗口链表
    DWORD               nCntsQBits[QSIDCOUNTS]; // 队列位计数
} THREADINFO;

PROCESSINFO 额外包含:进程窗口站 prpwinsta、私有/公共类链表、剪贴板序列号 iClipSerialNumber、光标缓存 pCursorCacheUSERSTARTUPINFO(STARTUPINFO 拷贝)、用户句柄表映射等。

3.4 USER_MESSAGE_QUEUE(消息队列)--- msgqueue.h

c 复制代码
typedef struct _USER_MESSAGE_QUEUE
{
    LONG References;                    // 引用计数
    struct _DESKTOP *Desktop;           // 所属桌面
    PTHREADINFO ptiSysLock;
    PTHREADINFO ptiMouse;               // 队列鼠标归属线程
    PTHREADINFO ptiKeyboard;            // 队列键盘归属线程
    LIST_ENTRY HardwareMessagesListHead; // 硬件消息链表(鼠标/键盘)
    MSG msgDblClk;                      // 双击判定
    PWND spwndCapture;                  // 捕获窗口
    PWND spwndFocus;                    // 焦点窗口
    PWND spwndActive;                   // 活动窗口
    PWND spwndActivePrev;               // 上一活动窗口
    HWND MoveSize;                      // 移动/缩放窗口
    HWND MenuOwner;                     // 菜单拥有窗口
    BYTE MenuState;                     // 菜单状态
    DWORD QF_flags;                     // 队列标志(QF_*)
    DWORD cThreads;                     // 共享队列线程数(AttachThreadInput)
    LPARAM ExtraInfo;                   // 消息附加信息
    BYTE afKeyRecentDown[256/8];        // 按键最近按下
    BYTE afKeyState[256*2/8];           // 按键状态(2 位/键)
    INT iCursorLevel;                   // 光标显隐计数
    PCURICON_OBJECT CursorObject;       // 当前光标
    THRDCARETINFO CaretInfo;            // 插入符信息
} USER_MESSAGE_QUEUE, *PUSER_MESSAGE_QUEUE;

队列标志 QF_* 记录菜单状态、激活变化、焦点丢失等。同一桌面下多个线程可通过 AttachThreadInput 共享同一个消息队列(cThreads 计数)。

3.5 DESKTOP / WINSTATION_OBJECT --- desktop.h / winsta.h

复制代码
WINSTATION_OBJECT(窗口站,\Windows\WindowStations\)
  └── DESKTOP(桌面,\Windows\WindowStations\WinSta0\Default)
        ├── 窗口树根(桌面窗口 → 顶级窗口 → 子窗口)
        ├── spwndForeground / spwndTray / spwndMessage / spwndTooltip
        ├── 系统菜单(spmenuSys/spmenuDialogSys/spmenuHScroll/spmenuVScroll)
        ├── ActiveMessageQueue(活动队列)
        └── PtiList(桌面线程链表)

DESKTOP 持有 pheapDesktop(桌面堆,窗口对象/菜单/窗口类等 USER 对象都在桌面堆中分配,并映射到用户态)、鼠标跟踪状态(spwndTrack/rcMouseHover/dwMouseHoverTime)、spwndLockUpdate(LockWindowUpdate 拖动锁定)等。

WINSTATION_OBJECT 持有桌面链表、原子表(AtomTable)、剪贴板状态(spwndClipOwner/pClipBase 等)、键盘布局链表(spklList)、ActiveDesktop 指针。

3.6 全局用户信息 gpsi(SERVERINFO)

gpsi(main.c 中 PSERVERINFO gpsi = NULL)是全局 USER 服务器信息,包含系统原子(atomSysClass[ICLS_*])、系统颜色、系统画刷、系统度量(SysMetrics)、默认布局等。窗口系统初始化(InitUserImpl)与系统参数(sysparams.c)都会填充 gpsi。


4. 主线一:窗口显示

回答"窗口怎么样实现显示"。一个窗口从"创建"到"出现在屏幕上"经历:

复制代码
CreateWindowEx(user32.dll)
  → NtUserCreateWindowEx → co_UserCreateWindowEx
  → IntCreateWindow(window.c)
      ├── 解析窗口类(class.c:IntGetAndReferenceClass)
      ├── 分配 WND 对象(UserCreateObject,桌面堆)
      ├── 建立父子/拥有关系(spwndParent/spwndChild/spwndOwner 链表)
      ├── 计算 rcWindow/rcClient(含非客户区尺寸)
      ├── 挂到线程窗口链表(ThreadListEntry)
      └── 发送 WM_NCCREATE/WM_CREATE
  → ShowWindow(winpos.c:NtUserShowWindow → co_WinPosShowWindow)
      ├── 首次显示:创建窗口表面(windc.c/vis.c:hsurf 与 hrgnVis)
      ├── 计算可见区域(vis.c:Vis__xxx 合成窗口的可见区域)
      ├── 更新 Z 序(spwndNext/spwndPrev 重排)
      └── 设置 WS_VISIBLE,产生更新区域
  → WM_PAINT(painting.c)
      ├── IntInvalidateRect/IntRedrawWindow 累积更新区域 hrgnUpdate
      └── 消息队列得到 WM_PAINT,应用 BeginPaint 取窗口 DC 绘制

各阶段对应文件:

阶段 文件 关键函数
创建 window.c(file:///d:/reactos/win32ss/user/ntuser/window.c) IntCreateWindow(L1805)、co_UserCreateWindowExNtUserCreateWindowEx(L2699)、NtUserSetWindowLong(L4070)
类解析 class.c(file:///d:/reactos/win32ss/user/ntuser/class.c) IntGetAndReferenceClassUserRegisterSystemClassesNtUserSetClassLong(L2706)
位置/可见 winpos.c(file:///d:/reactos/win32ss/user/ntuser/winpos.c) NtUserSetWindowPos(L3610)、NtUserShowWindow(L3898)、co_WinPosSetWindowPosco_WinPosShowWindowco_WinPosMinMaximizeco_WinPosWindowFromPoint
绘制协议 painting.c(file:///d:/reactos/win32ss/user/ntuser/painting.c) NtUserRedrawWindow(L2065)、NtUserInvalidateRect(L2604)、IntBeginPaintIntGetDCExIntUpdateWindow
可见区域 vis.c(file:///d:/reactos/win32ss/user/ntuser/vis.c) Vis__xxx 系列区域合成
窗口 DC windc.c(file:///d:/reactos/win32ss/user/ntuser/windc.c) UserGetDesktopDC、窗口表面创建、DceUpdateVisRgn
非客户区 nonclient.c(file:///d:/reactos/win32ss/user/ntuser/nonclient.c) NtUserDefWindowProc 分支中的 NC 绘制、标题栏/边框/按钮绘制
分层窗口 layered.c(file:///d:/reactos/win32ss/user/ntuser/layered.c) NtUserUpdateLayeredWindow、分层表面合成
绘制辅助 draw.c(file:///d:/reactos/win32ss/user/ntuser/draw.c) 绘制填充、聚焦矩形等工具

4.1 窗口创建 --- window.c

IntCreateWindow 是内核创建窗口的核心路径,处理:

  • 类查找pcls 通过类名原子查类链表(系统类或进程类),类未找到时返回 CLASS_NOT_FOUND 并让用户态加载控件库。
  • WND 分配UserCreateObject 在桌面堆中分配 WND(含 cbwndExtra 附加字节)。
  • 窗口树挂接IntLinkHwnd 按父/拥有者插入 Z 序(spwndNext/spwndPrev),IntLinkWindow 挂到线程链表。
  • 矩形计算IntCreateWindow 结合 IntGetWindowBorderMeasures/co_WinPosGetNonClientSize 计算窗口矩形与客户区。
  • 属性初始化strName 拷贝窗口标题,PropListHead 初始化,pSBInfo 滚动条信息,hImc 输入上下文。
  • 通知 :发送 WM_NCCREATE(可失败回滚)、WM_CREATE
  • 特殊窗口fnid 标记(FNID_DESKTOP/FNID_MESSAGEWND/FNID_SCROLLBAR 等)驱动不同行为。

4.2 窗口显示 --- winpos.c

NtUserShowWindow/NtUserSetWindowPos 是窗口可见性的两大入口。内部流程:

  1. 首次显示 :窗口首次变为可见(SW_SHOW 且原不可见)时调用 IntCreateWindowPos/DceUpdateVisRgn,为窗口创建 GDI 表面并计算可见区域(vis.c)。
  2. Z 序调整SWP_TOP/SWP_BOTTOM/HWND_TOPMOST 等通过 spwndPrev/spwndNext 重排窗口树;置顶窗口走 Z-Order 特殊链表。
  3. 移动/缩放SetWindowPos 改变 rcWindow,计算新旧区域差(hrgnNewFrame),通过 IntRedrawWindow 让受影响窗口重绘;WM_WINDOWPOSCHANGING/WM_WINDOWPOSCHANGED/WM_MOVE/WM_SIZE/WM_SIZING/WM_MOVING 依次派发。
  4. 可见区域更新 :受影响窗口的 hrgnVis 由 vis.c 重新合成,其他窗口被遮挡部分产生更新区域。
  5. 激活联动ShowWindowSW_SHOWNORMAL 等可能触发 IntSetActiveWindow(focus.c)。

4.3 绘制协议 --- painting.c + windc.c + vis.c

窗口显示的核心机制是更新区域WM_PAINT

复制代码
IntInvalidateRect(hWnd, rect)         // 用户 InvalidateRect
  → 更新区域累加到 hrgnUpdate
  → 队列置 QS_PAINT(nCntsQBits[QSRos...])
IntRedrawWindow(...)                   // RedrawWindow
  → 根据 RDW_INVALIDATE/ERASE/FRAME/UPDATENOW 组合
  → 可能直接 IntUpdateWindow 同步重绘
应用 GetMessage → WM_PAINT(QS_PAINT 时)
  → 应用 BeginPaint(user32)→ NtUserBeginPaint → IntBeginPaint
      → hrgnUpdate 转交为 hrgnClip(可见区∩更新区)
      → 返回窗口 DC
  → 应用 GDI 绘制 → EndPaint(清除更新区域)

窗口 DC(windc.c):GetDC 时 user32 调用 NtUserGetDCEx,内核通过 DceAllocDC 创建 DC 并关联窗口表面;绘制时 GDI 以窗口的 hrgnVis(可见区域,vis.c 合成)∩ hrgnUpdate 为裁剪,将像素写入窗口表面,最终 BitBlt 到屏幕。

可见区域合成(vis.c) :窗口的可见区域 = 窗口矩形 − 被覆盖部分。vis.c 遍历 Z 序上层的兄弟窗口,用区域差运算逐层扣除被遮挡区域,得到 hrgnVis;桌面窗口的可见区域与屏幕 DC 关联。这是 USER 与 GDI 的关键接口:GDI 绘制窗口时 IntGetWindowRegion/DceUpdateVisRgnhrgnVis 填入 DC 的裁剪。

4.4 非客户区绘制 --- nonclient.c

窗口矩形 = 非客户区(标题栏、边框、菜单栏、滚动条、系统按钮)+ 客户区。非客户区由系统绘制DefWindowProc 处理 WM_NCPAINT/WM_NCACTIVATE 时调用 nonclient.c 的绘制函数(NtUserDefWindowProc → NC 分支),绘制标题栏文字、图标、最小化/最大化/关闭按钮,滚动条由 scrollbar.c 绘制。应用可通过处理 WM_NCPAINT 自定义。

4.5 分层窗口 --- layered.c

WS_EX_LAYERED 窗口(UpdateLayeredWindow/SetLayeredWindowAttributes)具有独立合成表面:分层窗口使用 AlphaBlend 与屏幕合成,layered.c 维护分层表面(hSection/映射),NtUserUpdateLayeredWindow 更新其内容与 alpha,窗口移动时由 IntUpdateLayeredWindowPosition/绘制路径重合成。


5. 主线二:窗口管理

回答"怎么样实现窗口管理"。窗口管理围绕消息展开:窗口是一台状态机,行为完全由消息驱动。

5.1 消息队列 --- msgqueue.c

每个线程(或 attach 组)拥有一个 USER_MESSAGE_QUEUE,消息分三类:

类型 存储 函数 说明
硬件消息 HardwareMessagesListHead UserProcessMouseInput/UserProcessKeyboardInput/co_MsqInsertMouseMessage 鼠标/键盘原始输入转化为 MSG 入队
投递消息 PostedMessagesListHead MsqPostMessage/NtUserPostMessage(message.c L2275) PostMessage/PostThreadMessage,异步
发送消息 SentMessagesListHead/pusmSent co_MsqSendMessage/co_IntSendMessage SendMessage/SendMessageTimeout,跨线程时排队等待

队列状态用 QS 标志位QS_KEY/QS_MOUSEMOVE/QS_MOUSEBUTTON/QS_POSTMESSAGE/QS_SENDMESSAGE/QS_PAINT/QS_TIMER/QS_HOTKEY/QS_INPUT 等)聚合,消息到达时设置相应 QS 位并唤醒队列。线程的 nCntsQBits[QSIDCOUNTS] 做精确计数。

5.2 消息循环 --- message.c

复制代码
GetMessage(NtUserGetMessage L2343)
  → co_IntGetPeekMessage(有则取出,无则等待)
  → 优先处理发送消息(co_MsqDispatchOneSentMessage)
  → 硬件消息→投递消息→WM_PAINT→WM_TIMER→WM_QUIT 的优先级顺序
TranslateMessage(NtUserTranslateMessage L2490)
  → 键盘消息转为 WM_CHAR/WM_SYSCHAR/IME 消息
DispatchMessage(NtUserDispatchMessage L2464)
  → IntDispatchMessage → 查窗口过程 → co_IntCallWindowProc(callproc.c)
      → 用户态 KeUserModeCallback 回调窗口过程

消息过滤器(MsgFilterMin/MsgFilterMaxhWndQS_*)决定 GetMessage/PeekMessage 返回哪些消息。WaitMessage(NtUserWaitMessage L2329)睡眠等待新消息。

5.3 默认窗口过程 --- defwnd.c

IntDefWindowProc(defwnd.c)处理所有未被应用处理的系统消息:WM_NCCREATE/WM_CREATE/WM_NCPAINT/WM_MOVE/WM_SIZE/WM_ERASEBKGND(用类背景画刷擦背景)/WM_SETTEXT/WM_CLOSE(DestroyWindow)/WM_SETFOCUS 等。NtUserDefWindowProc 是它的系统调用包装。

菜单子系统是整个窗口管理中最复杂的部分:

  • 菜单对象PMENU/PITEMMENUINFO 包含菜单样式、项数组;菜单内存分配自桌面堆。
  • 菜单构造CreateMenu/CreatePopupMenu/AppendMenu/InsertMenuItemIntInsertMenuItem)/LoadMenu(用户态解析资源后逐项插入)。
  • 菜单栏IntSetMenu 挂到窗口,MENU_DrawMenuBar 绘制顶层菜单栏。
  • 菜单循环TrackPopupMenuEx(NtUserTrackPopupMenuEx L6634)进入模态菜单循环------捕获鼠标、命中测试项、高亮移动、子菜单展开、键盘导航(方向键/加速键)、MENUSTATE 状态机(pMenuState)。
  • 消息交互:WM_MENUSELECT/WM_INITMENU/WM_INITMENUPOPUP/WM_COMMAND/WM_MEASUREITEM/WM_DRAWITEM(自绘)等。

5.5 滚动条 --- scrollbar.c + scrollex.c

窗口可带垂直/水平滚动条(WS_HSCROLL/WS_VSCROLL)或滚动条控件。SBINFO/SBINFOEX 保存范围、位置、页面大小。scrollbar.c 实现滚动条窗口过程(NtUserScrollWindowEx、WM_VSCROLL/WM_HSCROLL 生成),scrollex.c 处理拖动 thumb 时的跟踪循环(SetScrollInfo/GetScrollInfo 等)。

5.6 计时器/属性/加速键 --- timer.c + prop.c + accelerator.c + hotkey.c

  • 计时器TIMER 对象挂线程链表(ptmrList),内核维护定时器队列;到期后向队列投递 WM_TIMER(PostTimerMessages/ProcessTimers)。系统计时器(ID_EVENT_SYSTIMER_*)用于光标闪烁、窗口闪烁等内部功能。
  • 属性SetProp/GetProp/RemoveProp(prop.c,NtUserSetProp L247)把应用数据以原子为键挂到窗口的 PropListHead
  • 加速键TranslateAccelerator 的键盘侧由 accelerator.c 的 NtUserTranslateAccelerator 处理,查窗口菜单加速键表。
  • 热键RegisterHotKey(hotkey.c)注册全局热键(含 MOD_WIN 等修饰键),按键时向注册线程投递 WM_HOTKEY。

5.7 钩子 --- hook.c

SetWindowsHookEx(NtUserSetWindowsHookEx L1439)安装系统级或线程级钩子(WH_KEYBOARD/WH_MOUSE/WH_CALLWNDPROC/WH_GETMESSAGE/WH_MSGFILTER 等)。钩子对象记录模块/函数地址/线程,THREADINFO.fsHooks 位图标记每线程安装了哪些钩子。消息在关键路径上调用 IntCallHookProc/IntCallMsgFilter(msgqueue.c)回调钩子过程;钩子过程通过 KeUserModeCallback(callback.c)在用户态执行。

5.8 焦点与激活 --- focus.c

  • 焦点SetFocus(NtUserSetFocus L1717)把键盘输入定向到指定窗口------更新队列 spwndFocus,发送 WM_KILLFOCUS/WM_SETFOCUS,并通知输入法。
  • 激活SetActiveWindow(NtUserSetActiveWindow L1658)与 SetForegroundWindow 更新队列 spwndActive/spwndActivePrev、桌面 spwndForeground,派发 WM_ACTIVATE/WM_NCACTIVATE/WM_ACTIVATEAPP,并驱动非客户区重绘。
  • 捕获SetCapture(NtUserSetCapture L1699)把鼠标消息重定向到捕获窗口(队列 spwndCapture),ReleaseCapture 恢复。

5.9 剪贴板 --- clipboard.c

剪贴板以窗口站 为容器(WINSTATION_OBJECTspwndClipOwner/pClipBase/cNumClipFormats):OpenClipboard(L538)加锁并记录所有者窗口,SetClipboardData(L1089)/GetClipboardData/EmptyClipboard/CloseClipboard 管理格式与数据句柄;WM_CLIPBOARDUPDATE(监听者)/WM_DRAWCLIPBOARD(查看者)通知机制;延迟渲染(fInDelayedRendering)在应用要求数据时才回调 WM_RENDERFORMAT

5.10 桌面与窗口站 --- desktop.c + winsta.c

  • 窗口站 (winsta.c):CreateWindowStation(NtUserCreateWindowStation L741)创建命名对象 \Windows\WindowStations\WinSta0,是剪贴板、原子、键盘布局、安全的主容器;InputWindowStation 为当前输入窗口站。
  • 桌面 (desktop.c):CreateDesktopEx/OpenDesktop/SwitchDesktop/SetThreadDesktop 管理桌面;DesktopThreadMain 是桌面专用线程;桌面窗口(DesktopWindowClass)是窗口树的根;co_IntShowDesktop/IntHideDesktop 切换桌面时切换可见窗口集。
  • 会话session.c 管理会话级结构;win32k 每会话一份。

5.11 用户对象管理 --- object.c + usrheap.c + useratom.c

  • 对象系统 (object.c):gHandleTable(USER_HANDLE_TABLE)管理全部 USER 对象句柄(窗口/菜单/光标/图标/钩子/加速键表/插入符等)。UserCreateObject 分配对象 + 句柄,UserReferenceObjectByHandle(ValidateHandle)校验句柄并加引用,UserDeleteObject 删除。句柄表映射到用户态,user32 可快速校验。
  • 桌面堆 (usrheap.c):GlobalUserHeap 是所有 USER 对象的存放堆,映射到每个进程用户态;桌面堆 pheapDesktop 每桌面一个。DesktopHeapAlloc/UserHeapAlloc 宏封装。
  • 原子表 (useratom.c):IntAddAtom/IntAddGlobalAtom 维护全局/会话原子,窗口类名、属性名、消息名都用原子。

5.12 光标与图标 --- cursoricon.c

CURICON_OBJECT 统一表示光标与图标:CreateCursor/CreateIcon/LoadCursor/SetCursor(NtUserSetCursor L1051)。光标对象包含 AND/XOR 掩码位图或彩色位图、热点(ptHotSpot)、帧动画(.ani)。SetCursor 更新队列 CursorObject 与光标显示(UserSetCursor/IntShowCursor,msgqueue.c),系统光标(IDC_ARROW 等)由 co_IntLoadDefaultCursors 在初始化时加载。


6. 主线三:鼠标控制

回答"窗口怎么样实现鼠标控制"------即鼠标硬件输入如何变成发给特定窗口的鼠标消息。

6.1 输入管线总览

复制代码
鼠标硬件中断
  → 输入驱动(i8042prt/mouclass)→ 原始数据(MOUSE_INPUT_DATA)
  → RawInput 线程(input.c: RawInputThreadMain)
      → UserProcessMouseInput(mouse.c: L~,input.h 声明)
          ├── 更新全局键状态/光标位置(gpti 队列)
          ├── 命中测试:co_WinPosWindowFromPoint(winpos.c)
          ├── 捕获检查:队列 spwndCapture 优先
          ├── 生成 MSG(WM_MOUSEMOVE/WM_LBUTTONDOWN 等,含 wParam 键状态)
          ├── co_MsqInsertMouseMessage 入队(msgqueue.c)
          └── 光标移动/形状切换(cursoricon.c)
  → 应用 GetMessage → 窗口过程收到 WM_MOUSE* 消息

6.2 鼠标消息生成 --- mouse.c

UserProcessMouseInput(mouse.c)处理每次鼠标输入:

  1. 光标位置更新gpti->ptLast 记录屏幕坐标;触发 IntTrackMouseEvent(WM_MOUSELEAVE/WM_MOUSEHOVER,NtUserTrackMouseEvent L482)。
  2. 命中测试co_WinPosWindowFromPoint(winpos.h)从桌面窗口开始沿 Z 序向下找包含点的最深可见窗口,返回 htEx(HTCLIENT/HTMAXBUTTON 等命中码)。捕获窗口存在时直接发送给捕获窗口。
  3. 消息构造 :按按钮状态生成 WM_MOUSEMOVE/WM_LBUTTONDOWN/WM_LBUTTONUP/WM_MBUTTON*/WM_RBUTTON*/WM_XBUTTON*/WM_MOUSEWHEEL 等,wParam 携带 Shift/Ctrl/各键状态。
  4. 双击判定 :队列 msgDblClk 记录上次点击,MsqIsDblClk 判断是否构成双击,生成 WM_LBUTTONDBLCLK;双击时间来自 GetDoubleClickTime(系统参数)。
  5. 非客户区 :命中码为 HT* 非客户区时生成 WM_NCLBUTTONDOWN 等。
  6. 光标切换 :按窗口的类光标(pcls->hCursor)或当前命中(HTCLIENT→箭头、HTSIZE→双向箭头等)调用 UserSetCursor 更新光标形状。

6.3 鼠标捕获 --- focus.c + mouse.c

SetCapture(focus.c NtUserSetCapture L1699)将队列 spwndCapture 设为指定窗口;此后所有鼠标消息(无论在哪)都发往捕获窗口,直到 ReleaseCapture 或捕获窗口销毁。捕获还用于菜单循环、滚动条拖动、标题栏拖动移动窗口等内部操作。QF_CAPTURELOCKED 标志表示捕获被锁定(SetCapture 内部使用 UserSetCapture)。

6.4 键盘输入 --- keyboard.c + kbdlayout.c

与鼠标平行的键盘管线:UserProcessKeyboardInput(keyboard.c)处理 KEYBOARD_INPUT_DATA

  1. 键状态更新afKeyState/gafAsyncKeyState(2 位/键:down+lock),NtUserGetKeyState(msgqueue.c L2573)/NtUserGetAsyncKeyState(keyboard.c L649)读取。
  2. 布局翻译kbdlayout.c 按当前键盘布局(KL 对象)把扫描码+虚键翻译成字符/虚键,处理 Shift/Caps 组合。
  3. 消息入队 :生成 WM_KEYDOWN/WM_KEYUP/WM_SYSKEYDOWN/WM_SYSKEYUPlParam 编码扫描码/重复计数/up 标志。
  4. 焦点定向 :发送给队列 spwndFocus 焦点窗口(否则活动窗口),IntImmProcessKey(ime.c)先给输入法处理。
  5. TranslateMessage:用户态 TranslateMessage 把键消息变成 WM_CHAR/WM_SYSCHAR;加速键与快捷键(WM_HOTKEY/系统命令 SC_*)在此阶段识别。

6.5 输入法 --- ime.c

IME(输入法)在键盘消息进入窗口过程前拦截:IntImmProcessKey(ime.c)对每个按键先询问输入法(imm32 用户态 + 内核 IME 类窗口),输入法处理组合序列后产生 WM_IME_COMPOSITION/WM_IME_STARTCOMPOSITION 等消息并把最终字符投递为 WM_CHAR。窗口创建时 IntWantImeWindow/co_IntCreateDefaultImeWindow 为焦点窗口创建默认 IME 窗口("IME" 类),hImc(输入上下文)关联窗口与输入法状态。

6.6 输入状态管理 --- input.c + event.c

  • input.cIntBlockInput(NtUserBlockInput L435)全局屏蔽输入;UserAttachThreadInput 合并线程队列;DoTheScreenSaver 触发屏保;RawInputThreadMain 是系统输入线程,从 ghKeyboardDevice/鼠标设备读取数据。
  • event.cNtUserSetWinEventHook 等可访问性事件(IntNotifyWinEvent,window.h)在窗口创建/销毁/激活/菜单弹出等时向事件钩子投递。

7. 消息传递机制

7.1 用户态回调(KeUserModeCallback)

窗口过程、钩子过程、回调都在用户态执行,内核通过 KeUserModeCallback 进入用户态(callback.c 的 co_IntCallWindowProc 等封装):

复制代码
内核需要调用窗口过程
  → co_IntCallWindowProc(callback.c,参数打包到堆)
  → KeUserModeCallback("USER32CallWindowProc" 等入口)
  → user32.dll 入口函数(如 User32CallWindowProc)
  → 调用应用窗口过程
  → 结果经回调返回内核

user32 提供一系列回调入口:User32CallWindowProcUser32CallHookProcUser32CallEventProcUser32CallSendAsyncProcForKernel(window.c L23)等。

7.2 SendMessage 跨线程语义

复制代码
SendMessage(hWnd, msg, wParam, lParam)
  → NtUserSendMessage(message.c)
  → co_IntSendMessage(msgqueue.h L168)
      ├── 同线程:直接 co_IntCallWindowProc
      ├── 跨线程:co_MsqSendMessage 分配 USER_SENT_MESSAGE
      │     挂到接收线程 SentMessagesListHead,置 QS_SENDMESSAGE
      │     接收线程在 GetMessage 前 co_MsqDispatchOneSentMessage 处理
      │     发送者等待完成事件(同步)或注册回调(SendMessageCallback)
      └── 接收线程无响应:SMF_RECEIVERBUSY / 超时处理(SendMessageTimeout)

7.3 消息优先级与唤醒

GetMessage 的取消息顺序(co_IntGetPeekMessage):

  1. 发送消息(SentMessagesListHead,QS_SENDMESSAGE)
  2. 硬件消息(HardwareMessagesListHead,QS_INPUT=QS_KEY|QS_MOUSEMOVE|QS_MOUSEBUTTON)
  3. 投递消息(PostedMessagesListHead,QS_POSTMESSAGE)
  4. WM_PAINT(cPaintsReady>0,QS_PAINT)
  5. WM_TIMER(cTimersReady>0,QS_TIMER)
  6. 检查 WM_QUIT

线程睡眠时,MsqWaitForNewMessages/co_MsqWaitForNewMessages 等待队列事件(hEventQueueClient),有新消息时 MsqWakeQueue 唤醒。


8. 初始化与生命周期

8.1 win32k 驱动加载(main.c)

win32k.sys 由 ntoskrnl 加载,DriverEntry(main.c)初始化 GDI 与 USER:

复制代码
DriverEntry(main.c)
  ├── 保存 CSRSS 进程(gpepCSRSS)
  ├── 注册进程/线程回调(Win32kProcessCallback/Win32kThreadCallback)
  ├── 初始化 GDI(GdiProcessCreate 等 + InitializeGreCSRSS)
  └── UserInitialize(ntuser.c)
        ├── InitUserImpl:句柄表、会话、原子、系统参数
        ├── InitVideo:显示驱动
        ├── InitDesktopImpl(desktop.c):桌面/桌面线程
        ├── InitWindowStationImpl(winsta.c)
        ├── InitInputImpl / InitKeyboardImpl / InitMouseImpl
        ├── 注册系统类(UserRegisterSystemClasses)
        └── 加载默认光标/系统菜单(co_IntLoadDefaultCursors 等)

8.2 每线程/每进程初始化

  • 线程连接IntThreadConnect,main.c):首次调用 USER API 时分配 THREADINFO、创建消息队列(MsqCreateMessageQueue)、映射用户堆。
  • 进程连接IntProcessConnect):分配 PROCESSINFO、创建用户句柄表映射、初始化 GDI 池。
  • 清理ExitProcessCallback/ExitThreadCallback,main.c L304):销毁消息队列(MsqDestroyMessageQueue)、清理钩子/计时器/菜单/窗口(co_UserFreeWindow/IntCleanupMenus)、销毁进程类(DestroyProcessClasses)。

9. 窗口系统分册规划

本文档集按三大主线 组织 24 个分册,命名 ReactOS 窗口系统分析_NN.md,置于 doc/窗口管理/

主线一:窗口显示(_1 ~ _7)

分册 主题 核心源码
_1 窗口对象与创建 window.c、win32.h、ntuser.h(WND)
_2 窗口类 class.c、class.h
_3 窗口位置与可见性 winpos.c、winpos.h
_4 窗口绘制与更新区域 painting.c、painting.h
_5 可见区域与窗口 DC vis.c、windc.c、dce.h
_6 非客户区绘制 nonclient.c
_7 分层窗口与绘制辅助 layered.c、draw.c

主线二:窗口管理(_8 ~ _20)

分册 主题 核心源码
_8 消息队列 msgqueue.c、msgqueue.h
_9 消息循环与分发 message.c
_10 默认窗口过程 defwnd.c
_11 菜单(上):对象与构造 menu.c(菜单对象/项/资源)
_12 菜单(下):循环与交互 menu.c(TrackPopupMenu/菜单循环)
_13 滚动条 scrollbar.c、scrollex.c、scroll.h
_14 计时器/属性/加速键/热键 timer.c、prop.c、accelerator.c、hotkey.c
_15 钩子 hook.c、hook.h
_16 焦点与激活 focus.c、focus.h
_17 剪贴板 clipboard.c、clipboard.h
_18 桌面与窗口站 desktop.c、winsta.c、session.c
_19 用户对象/句柄/堆/原子 object.c、usrheap.c、useratom.c、tags.h
_20 光标与图标 cursoricon.c、cursoricon.h

主线三:鼠标控制(_21 ~ _24)

分册 主题 核心源码
_21 输入子系统 input.c、event.c
_22 鼠标 mouse.c
_23 键盘 keyboard.c、kbdlayout.c
_24 输入法 ime.c

辅助文件(main.c、callback.c、callproc.c、misc.c、sysparams.c、ntstubs.c、csr.c、display.c、monitor.c、power.c、security.c、shutdown.c、console.c、dde.c、ghost.c、guicheck.c、metric.c、simplecall.c、win32kdebug.h 等)在各分册相关章节中穿插说明,不单独设册。


10. 附录:关键文件索引

窗口显示

文件 功能
window.c(file:///d:/reactos/win32ss/user/ntuser/window.c) 窗口创建/销毁/窗口管理
winpos.c(file:///d:/reactos/win32ss/user/ntuser/winpos.c) 窗口位置、可见性、Z 序
class.c(file:///d:/reactos/win32ss/user/ntuser/class.c) 窗口类注册与管理
painting.c(file:///d:/reactos/win32ss/user/ntuser/painting.c) 更新区域、WM_PAINT、BeginPaint
vis.c(file:///d:/reactos/win32ss/user/ntuser/vis.c) 可见区域合成
windc.c(file:///d:/reactos/win32ss/user/ntuser/windc.c) 窗口 DC、表面
nonclient.c(file:///d:/reactos/win32ss/user/ntuser/nonclient.c) 非客户区绘制
layered.c(file:///d:/reactos/win32ss/user/ntuser/layered.c) 分层窗口
draw.c(file:///d:/reactos/win32ss/user/ntuser/draw.c) 绘制辅助

窗口管理

文件 功能
msgqueue.c(file:///d:/reactos/win32ss/user/ntuser/msgqueue.c) 消息队列
message.c(file:///d:/reactos/win32ss/user/ntuser/message.c) 消息循环 API
defwnd.c(file:///d:/reactos/win32ss/user/ntuser/defwnd.c) 默认窗口过程
menu.c(file:///d:/reactos/win32ss/user/ntuser/menu.c) 菜单子系统
scrollbar.c(file:///d:/reactos/win32ss/user/ntuser/scrollbar.c) 滚动条
scrollex.c(file:///d:/reactos/win32ss/user/ntuser/scrollex.c) 滚动条扩展
timer.c(file:///d:/reactos/win32ss/user/ntuser/timer.c) 计时器
prop.c(file:///d:/reactos/win32ss/user/ntuser/prop.c) 窗口属性
accelerator.c(file:///d:/reactos/win32ss/user/ntuser/accelerator.c) 加速键
hook.c(file:///d:/reactos/win32ss/user/ntuser/hook.c) 钩子
focus.c(file:///d:/reactos/win32ss/user/ntuser/focus.c) 焦点/激活/捕获
clipboard.c(file:///d:/reactos/win32ss/user/ntuser/clipboard.c) 剪贴板
desktop.c(file:///d:/reactos/win32ss/user/ntuser/desktop.c) 桌面
winsta.c(file:///d:/reactos/win32ss/user/ntuser/winsta.c) 窗口站
object.c(file:///d:/reactos/win32ss/user/ntuser/object.c) USER 对象系统
usrheap.c(file:///d:/reactos/win32ss/user/ntuser/usrheap.c) 用户堆
useratom.c(file:///d:/reactos/win32ss/user/ntuser/useratom.c) 原子
cursoricon.c(file:///d:/reactos/win32ss/user/ntuser/cursoricon.c) 光标/图标

鼠标控制

文件 功能
input.c(file:///d:/reactos/win32ss/user/ntuser/input.c) 输入子系统
mouse.c(file:///d:/reactos/win32ss/user/ntuser/mouse.c) 鼠标处理
keyboard.c(file:///d:/reactos/win32ss/user/ntuser/keyboard.c) 键盘处理
kbdlayout.c(file:///d:/reactos/win32ss/user/ntuser/kbdlayout.c) 键盘布局
ime.c(file:///d:/reactos/win32ss/user/ntuser/ime.c) 输入法
event.c(file:///d:/reactos/win32ss/user/ntuser/event.c) 可访问性事件

基础/支撑

文件 功能
main.c(file:///d:/reactos/win32ss/user/ntuser/main.c) DriverEntry、进程/线程回调
callback.c(file:///d:/reactos/win32ss/user/ntuser/callback.c) KeUserModeCallback 用户态回调
callproc.c(file:///d:/reactos/win32ss/user/ntuser/callproc.c) 窗口过程调用
ntuser.c(file:///d:/reactos/win32ss/user/ntuser/ntuser.c) USER 初始化、UserLock
caret.c(file:///d:/reactos/win32ss/user/ntuser/caret.c) 插入符
misc.c(file:///d:/reactos/win32ss/user/ntuser/misc.c) 杂项
sysparams.c(file:///d:/reactos/win32ss/user/ntuser/sysparams.c) 系统参数
metric.c(file:///d:/reactos/win32ss/user/ntuser/metric.c) 系统度量

共享头文件

文件 功能
ntuser.h(file:///d:/reactos/win32ss/include/ntuser.h) WND/句柄类型定义
win32.h(file:///d:/reactos/win32ss/user/ntuser/win32.h) THREADINFO/PROCESSINFO
msgqueue.h(file:///d:/reactos/win32ss/user/ntuser/msgqueue.h) 消息队列结构
desktop.h(file:///d:/reactos/win32ss/user/ntuser/desktop.h) DESKTOP 结构
winsta.h(file:///d:/reactos/win32ss/user/ntuser/winsta.h) WINSTATION_OBJECT
window.h(file:///d:/reactos/win32ss/user/ntuser/window.h) 窗口内部接口
winpos.h(file:///d:/reactos/win32ss/user/ntuser/winpos.h) CVR/SMWP 结构
menu.h(file:///d:/reactos/win32ss/user/ntuser/menu.h) 菜单内部接口
input.h(file:///d:/reactos/win32ss/user/ntuser/input.h) 输入接口
callback.h(file:///d:/reactos/win32ss/user/ntuser/callback.h) 回调接口
object.h(file:///d:/reactos/win32ss/user/ntuser/object.h) 对象接口
usrheap.h(file:///d:/reactos/win32ss/user/ntuser/usrheap.h) 用户堆接口
tags.h(file:///d:/reactos/win32ss/user/ntuser/tags.h) USERTAG_* 池标签

本文档基于 ReactOS 源代码分析,涵盖截至 2026 年 8 月的代码结构。

主要分析目录:win32ss/user/ntuser/win32ss/user/user32/win32ss/user/winsrv/

配套分册:doc/窗口管理/ReactOS 窗口系统分析_01.md ~ _24.md

相关推荐
caimouse1 小时前
ReactOS 图形系统分析(46):DC 工具 — dcutil.c
c语言·开发语言
BreezeJuvenile1 小时前
嵌入式C语言常见考察点(2)
c语言·嵌入式
王志来137944730081 小时前
聚焦五大核心场景:工控服务器机箱平台的垂直深耕之路
运维·服务器·python
caimouse2 小时前
ReactOS 图形系统分析(50):画笔子系统 — pen.c
c语言·开发语言·stm32
便利店10242 小时前
边界与远程:墙、洞与加密通道
运维·服务器·防火墙·acl·nat
caimouse2 小时前
ReactOS 图形系统分析(48):弧线绘制 — arc.c
c语言·开发语言
cypking3 小时前
Objective-C 语法完整学习手册(小白自学 + 开发备查)
c语言·开发语言·学习·objective-c
三言老师11 小时前
K8s集群运行时自动化运维全覆盖落地实操(下)
linux·运维·服务器·网络
Super 含12 小时前
Android 启动优化(五):线程、GC 与 IO 为什么会拖慢启动?
java·服务器·数据库