ReactOS 窗口系统架构分析
目录
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、光标缓存 pCursorCache、USERSTARTUPINFO(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_UserCreateWindowEx、NtUserCreateWindowEx(L2699)、NtUserSetWindowLong(L4070) |
| 类解析 | class.c(file:///d:/reactos/win32ss/user/ntuser/class.c) | IntGetAndReferenceClass、UserRegisterSystemClasses、NtUserSetClassLong(L2706) |
| 位置/可见 | winpos.c(file:///d:/reactos/win32ss/user/ntuser/winpos.c) | NtUserSetWindowPos(L3610)、NtUserShowWindow(L3898)、co_WinPosSetWindowPos、co_WinPosShowWindow、co_WinPosMinMaximize、co_WinPosWindowFromPoint |
| 绘制协议 | painting.c(file:///d:/reactos/win32ss/user/ntuser/painting.c) | NtUserRedrawWindow(L2065)、NtUserInvalidateRect(L2604)、IntBeginPaint、IntGetDCEx、IntUpdateWindow |
| 可见区域 | 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 是窗口可见性的两大入口。内部流程:
- 首次显示 :窗口首次变为可见(
SW_SHOW且原不可见)时调用IntCreateWindowPos/DceUpdateVisRgn,为窗口创建 GDI 表面并计算可见区域(vis.c)。 - Z 序调整 :
SWP_TOP/SWP_BOTTOM/HWND_TOPMOST等通过spwndPrev/spwndNext重排窗口树;置顶窗口走Z-Order特殊链表。 - 移动/缩放 :
SetWindowPos改变rcWindow,计算新旧区域差(hrgnNewFrame),通过IntRedrawWindow让受影响窗口重绘;WM_WINDOWPOSCHANGING/WM_WINDOWPOSCHANGED/WM_MOVE/WM_SIZE/WM_SIZING/WM_MOVING依次派发。 - 可见区域更新 :受影响窗口的
hrgnVis由 vis.c 重新合成,其他窗口被遮挡部分产生更新区域。 - 激活联动 :
ShowWindow的SW_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/DceUpdateVisRgn 把 hrgnVis 填入 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/MsgFilterMax、hWnd、QS_*)决定 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 是它的系统调用包装。
5.4 菜单 --- menu.c(6699 行,拆两册)
菜单子系统是整个窗口管理中最复杂的部分:
- 菜单对象 :
PMENU/PITEM,MENUINFO包含菜单样式、项数组;菜单内存分配自桌面堆。 - 菜单构造 :
CreateMenu/CreatePopupMenu/AppendMenu/InsertMenuItem(IntInsertMenuItem)/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_OBJECT 的 spwndClipOwner/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)处理每次鼠标输入:
- 光标位置更新 :
gpti->ptLast记录屏幕坐标;触发IntTrackMouseEvent(WM_MOUSELEAVE/WM_MOUSEHOVER,NtUserTrackMouseEvent L482)。 - 命中测试 :
co_WinPosWindowFromPoint(winpos.h)从桌面窗口开始沿 Z 序向下找包含点的最深可见窗口,返回htEx(HTCLIENT/HTMAXBUTTON 等命中码)。捕获窗口存在时直接发送给捕获窗口。 - 消息构造 :按按钮状态生成
WM_MOUSEMOVE/WM_LBUTTONDOWN/WM_LBUTTONUP/WM_MBUTTON*/WM_RBUTTON*/WM_XBUTTON*/WM_MOUSEWHEEL等,wParam携带 Shift/Ctrl/各键状态。 - 双击判定 :队列
msgDblClk记录上次点击,MsqIsDblClk判断是否构成双击,生成WM_LBUTTONDBLCLK;双击时间来自GetDoubleClickTime(系统参数)。 - 非客户区 :命中码为 HT* 非客户区时生成
WM_NCLBUTTONDOWN等。 - 光标切换 :按窗口的类光标(
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:
- 键状态更新 :
afKeyState/gafAsyncKeyState(2 位/键:down+lock),NtUserGetKeyState(msgqueue.c L2573)/NtUserGetAsyncKeyState(keyboard.c L649)读取。 - 布局翻译 :
kbdlayout.c按当前键盘布局(KL对象)把扫描码+虚键翻译成字符/虚键,处理 Shift/Caps 组合。 - 消息入队 :生成
WM_KEYDOWN/WM_KEYUP/WM_SYSKEYDOWN/WM_SYSKEYUP,lParam编码扫描码/重复计数/up 标志。 - 焦点定向 :发送给队列
spwndFocus焦点窗口(否则活动窗口),IntImmProcessKey(ime.c)先给输入法处理。 - 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.c :
IntBlockInput(NtUserBlockInput L435)全局屏蔽输入;UserAttachThreadInput合并线程队列;DoTheScreenSaver触发屏保;RawInputThreadMain是系统输入线程,从ghKeyboardDevice/鼠标设备读取数据。 - event.c :
NtUserSetWinEventHook等可访问性事件(IntNotifyWinEvent,window.h)在窗口创建/销毁/激活/菜单弹出等时向事件钩子投递。
7. 消息传递机制
7.1 用户态回调(KeUserModeCallback)
窗口过程、钩子过程、回调都在用户态执行,内核通过 KeUserModeCallback 进入用户态(callback.c 的 co_IntCallWindowProc 等封装):
内核需要调用窗口过程
→ co_IntCallWindowProc(callback.c,参数打包到堆)
→ KeUserModeCallback("USER32CallWindowProc" 等入口)
→ user32.dll 入口函数(如 User32CallWindowProc)
→ 调用应用窗口过程
→ 结果经回调返回内核
user32 提供一系列回调入口:User32CallWindowProc、User32CallHookProc、User32CallEventProc、User32CallSendAsyncProcForKernel(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):
- 发送消息(SentMessagesListHead,QS_SENDMESSAGE)
- 硬件消息(HardwareMessagesListHead,QS_INPUT=QS_KEY|QS_MOUSEMOVE|QS_MOUSEBUTTON)
- 投递消息(PostedMessagesListHead,QS_POSTMESSAGE)
- WM_PAINT(cPaintsReady>0,QS_PAINT)
- WM_TIMER(cTimersReady>0,QS_TIMER)
- 检查 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