ReactOS 图形系统分析(15):窗口对象 — EWNDOBJ(engwindow.c)
1. 概述
engwindow.c位于win32ss/gdi/eng/engwindow.c,是 GDI 引擎的窗口对象(WNDOBJ/EWNDOBJ)管理模块。它让显示驱动能够跟踪窗口的位置与可见区域变化,从而优化窗口表面的绘制(如硬件加速、合成器)。
ntuser (WND 窗口对象:位置/尺寸/可见区域) ↓ VIS_ComputeVisibleRegion 计算可见区域 engwindow.c (EWNDOBJ 管理 ← 本分析对象) ├── EngCreateWnd / EngDeleteWnd 生命周期 ├── IntEngWndUpdateClipObj 从 WND 更新裁剪对象 ├── IntEngWindowChanged 窗口变化通知(回调驱动) └── WNDOBJ_bEnum / cEnumStart 可见区域矩形枚举 ↓ 显示驱动 (通过 WNDOBJCHANGEPROC 接收变化通知)核心思想:驱动创建窗口表面时调用EngCreateWnd获得 WNDOBJ,之后窗口移动/改变大小时引擎通过WNDOBJCHANGEPROC回调通知驱动,驱动可据此高效更新(例如只重绘露出部分,或跟踪精灵位置)。
2. 核心数据结构
2.1 EWNDOBJ(扩展窗口对象,[engobjects.h](file:///d:/reactos/win32ss/gdi/eng/engobjects.h#L92-L105))
typedefstruct_EWNDOBJ:_XCLIPOBJ// 继承:CLIPOBJ + RWNDOBJ + 裁剪私有字段{/* 扩展窗口对象部分 */HWND Hwnd;// 关联的窗口句柄WNDOBJCHANGEPROC ChangeProc;// 变化回调(驱动注册)FLONG Flags;// 关心的变化标志intPixelFormat;// 像素格式(OpenGL 用)}EWNDOBJ,*PEWNDOBJ;2.2 WNDOBJ(DDI 公开部分,= CLIPOBJ + RWNDOBJ)
typedefstruct_RWNDOBJ{PVOID pvConsumer;// 驱动私有数据RECTL rclClient;// 客户区矩形SURFOBJ*psoOwner;// 所有者表面}RWNDOBJ;/* WNDOBJ = CLIPOBJ + RWNDOBJ(通过 _WNDOBJ 别名引用) */WNDOBJ 本质是一个特殊的 CLIPOBJ:它的裁剪区域 = 窗口的可见客户区。驱动通过WNDOBJ_bEnum/WNDOBJ_cEnumStart枚举可见矩形(复用 CLIPOBJ 机制,见《ReactOS 图形系统分析_8.md》)。
2.3 变化标志
驱动请求的标志(WO_*,EngCreateWnd 的 fl 参数):
| 标志 | 含义 |
|---|---|
| WO_RGN_CLIENT | 客户区变化通知 |
| WO_RGN_CLIENT_DELTA | 客户区增量变化 |
| WO_RGN_SURFACE | 表面区域变化 |
| WO_RGN_SURFACE_DELTA | 表面区域增量变化 |
| WO_DRAW_NOTIFY | 绘制通知 |
| WO_SPRITE_NOTIFY | 精灵重叠通知 |
| WO_RGN_WINDOW / WO_RGN_DESKTOP_COORD / WO_RGN_UPDATE_ALL | 未实现(FIXME) |
引擎触发的回调标志(WOC_*):
| 标志 | 含义 |
|---|---|
| WOC_RGN_CLIENT | 客户区可见区域已更新 |
| WOC_CHANGED | 窗口已变化 |
| WOC_DELETE | 窗口对象将被删除 |
| WOC_DRAWN | 已绘制 |
| WOC_SPRITE_OVERLAP / WOC_SPRITE_NO_OVERLAP | 精灵重叠/不重叠 |
3. 核心函数分析
3.1 创建 — EngCreateWnd
关键代码:
WNDOBJ*APIENTRYEngCreateWnd(SURFOBJ*pso,HWND hWnd,WNDOBJCHANGEPROC pfn,FLONG fl,intiPixelFormat){Window=UserGetWindowObject(hWnd);// 1. 取窗口对象if(Window==NULL)gotoExit;Clip=EngAllocMem(FL_ZERO_MEMORY,sizeof(EWNDOBJ),GDITAG_WNDOBJ);IntEngInitClipObj((XCLIPOBJ*)Clip);// 2. 初始化裁剪对象/* 3. 用窗口可见区域填充裁剪对象 */if(!IntEngWndUpdateClipObj(Clip,Window)){EngFreeMem(Clip);gotoExit;}/* 4. 填充 DDI 公开部分 */WndObjUser=(WNDOBJ*)Clip;WndObjUser->psoOwner=pso;WndObjUser->pvConsumer=NULL;/* 5. 填充内部字段 */Clip->Hwnd=hWnd;Clip->ChangeProc=pfn;Clip->Flags=fl&(WO_RGN_CLIENT_DELTA|WO_RGN_CLIENT|WO_RGN_SURFACE_DELTA|WO_RGN_SURFACE|WO_DRAW_NOTIFY);if(fl&WO_SPRITE_NOTIFY)Clip->Flags|=WOC_SPRITE_OVERLAP|WOC_SPRITE_NO_OVERLAP;Clip->Flags|=WOC_CHANGED|WOC_DELETE;// 始终发送Clip->PixelFormat=iPixelFormat;/* 6. 通过窗口属性关联到窗口 */UserSetProp(Window,AtomWndObj,Clip,TRUE);++gcountPWO;returnWndObjUser;}要点:
- 只保存驱动请求的标志(
fl & WO_*),回调时按此过滤 WOC_CHANGED/WOC_DELETE始终加入 Flags(这些通知必须发送)- 通过
AtomWndObj窗口属性把 EWNDOBJ 挂到窗口上,窗口变化时能找回
3.2 从窗口更新裁剪对象 — IntEngWndUpdateClipObj
BOOLEAN FASTCALLIntEngWndUpdateClipObj(EWNDOBJ*Clip,PWND Window){/* 计算窗口的可见区域(考虑兄弟窗口遮挡) */visRgn=VIS_ComputeVisibleRegion(Window,TRUE,TRUE,TRUE);if(visRgn!=NULL&&visRgn->rdh.nCount>0){/* 可见区域 → 裁剪对象(多矩形 → DC_COMPLEX) */IntEngUpdateClipRegion((XCLIPOBJ*)Clip,visRgn->rdh.nCount,visRgn->Buffer,&visRgn->rdh.rcBound);REGION_Delete(visRgn);}else{/* 退化为客户区矩形 */IntEngUpdateClipRegion((XCLIPOBJ*)Clip,1,&Window->rcClient,&Window->rcClient);}/* 更新客户区矩形和唯一性标志 */Clip->rclClient=Window->rcClient;Clip->iUniq++;returnTRUE;}要点:VIS_ComputeVisibleRegion计算窗口的真实可见区域(扣除被覆盖部分)——多矩形时裁剪对象变为DC_COMPLEX,驱动用WNDOBJ_bEnum枚举可见矩形只绘制可见部分。
3.3 窗口变化通知 — IntEngWindowChanged
窗口移动/改变尺寸/删除时由 ntuser 调用:
VOID FASTCALLIntEngWindowChanged(PWND Window,FLONG flChanged){Clip=UserGetProp(Window,AtomWndObj,TRUE);// 找回 WNDOBJif(!Clip)return;switch(flChanged){caseWOC_RGN_CLIENT:/* 更新裁剪对象和客户区 */IntEngWndUpdateClipObj(Clip,Window);break;caseWOC_DELETE:/* FIXME: WNDOBJ 由 win32k 还是驱动删除? */break;}/* 调用驱动回调 */IntEngWndCallChangeProc(Clip,flChanged);/* HACK: WOC_RGN_CLIENT 后再发 WOC_CHANGED */if(flChanged==WOC_RGN_CLIENT)IntEngWndCallChangeProc(Clip,WOC_CHANGED);}3.4 回调调度 — IntEngWndCallChangeProc
VOID FASTCALLIntEngWndCallChangeProc(EWNDOBJ*Clip,FLONG flChanged){if(Clip->ChangeProc==NULL)return;/* 按驱动请求的标志过滤 */flChanged&=Clip->Flags;if(flChanged==0)return;/* WOC_CHANGED 无矩形数据,传 NULL;其余传 WNDOBJ 指针 */if(flChanged==WOC_CHANGED)Clip->ChangeProc(NULL,flChanged);elseClip->ChangeProc((WNDOBJ*)Clip,flChanged);}3.5 删除 — EngDeleteWnd
VOID APIENTRYEngDeleteWnd(WNDOBJ*pwo){Clip=(EWNDOBJ*)pwo;/* 从窗口移除属性 */Window=UserGetWindowObject(Clip->Hwnd);if(Window)UserRemoveProp(Window,AtomWndObj,TRUE);--gcountPWO;/* 释放资源(Rects 堆数组 + 对象本身) */IntEngFreeClipResources((XCLIPOBJ*)Clip);EngFreeMem(Clip);}3.6 DDI 枚举函数(驱动侧)
/* 枚举可见矩形(转给 CLIPOBJ_bEnum) */BOOL APIENTRYWNDOBJ_bEnum(WNDOBJ*pwo,ULONG cj,ULONG*pul){returnCLIPOBJ_bEnum(&pwo->coClient,cj,pul);}/* 枚举初始化(转给 CLIPOBJ_cEnumStart) */ULONG APIENTRYWNDOBJ_cEnumStart(WNDOBJ*pwo,ULONG iType,ULONG iDirection,ULONG cLimit){returnCLIPOBJ_cEnumStart(&pwo->coClient,FALSE,iType,iDirection,cLimit);}要点:WNDOBJ 的枚举完全复用 CLIPOBJ 机制——WNDOBJ 本质就是带窗口语义的 CLIPOBJ。
3.7 设置消费者 — WNDOBJ_vSetConsumer
VOID APIENTRYWNDOBJ_vSetConsumer(WNDOBJ*pwo,PVOID pvConsumer){Hack=(pwo->pvConsumer==NULL);pwo->pvConsumer=pvConsumer;// 保存驱动私有数据/* HACK: 首次设置时补发初始状态(MSDN 要求创建后回调最近状态) */if(Hack){IntEngWndCallChangeProc(Clip,WOC_RGN_CLIENT);IntEngWndCallChangeProc(Clip,WOC_CHANGED);IntEngWndCallChangeProc(Clip,WOC_DRAWN);}}4. 数据流(窗口变化 → 驱动收到通知)
5. 设计特点总结
- WNDOBJ 即特殊 CLIPOBJ:窗口对象继承 CLIPOBJ,裁剪区域 = 窗口可见区域,枚举完全复用 CLIPOBJ 机制。
- 可见区域计算:
VIS_ComputeVisibleRegion计算扣除遮挡后的真实可见区域(多矩形 → DC_COMPLEX)。 - 回调过滤:只发送驱动请求的标志(
flChanged &= Clip->Flags),减少无关通知。 - 窗口属性关联:通过
AtomWndObj属性把 WNDOBJ 挂到窗口,窗口变化可快速找回。 - iUniq 失效标记:裁剪区域每次更新
iUniq++,驱动可据此判断缓存是否失效。 - HACK 兼容:创建/设置消费者时补发初始状态通知,符合 MSDN 对 WNDOBJCHANGEPROC 的约定。
- 精灵通知预留:
WO_SPRITE_NOTIFY支持精灵重叠跟踪(未完整实现)。
6. 源码索引
| 位置 | 关键内容 |
|---|---|
| [engwindow.c](file:///d:/reactos/win32ss/gdi/eng/engwindow.c#L18-L44) | IntEngWndCallChangeProc(回调过滤调度) |
| [engwindow.c](file:///d:/reactos/win32ss/gdi/eng/engwindow.c#L49-L92) | IntEngWndUpdateClipObj(可见区域 → 裁剪对象) |
| [engwindow.c](file:///d:/reactos/win32ss/gdi/eng/engwindow.c#L97-L138) | IntEngWindowChanged(窗口变化通知) |
| [engwindow.c](file:///d:/reactos/win32ss/gdi/eng/engwindow.c#L143-L214) | EngCreateWnd(创建) |
| [engwindow.c](file:///d:/reactos/win32ss/gdi/eng/engwindow.c#L220-L250) | EngDeleteWnd(删除) |
| [engwindow.c](file:///d:/reactos/win32ss/gdi/eng/engwindow.c#L256-L317) | WNDOBJ_bEnum / cEnumStart / vSetConsumer |
| [engobjects.h](file:///d:/reactos/win32ss/gdi/eng/engobjects.h#L42-L46) | RWNDOBJ 结构 |
| [engobjects.h](file:///d:/reactos/win32ss/gdi/eng/engobjects.h#L92-L105) | EWNDOBJ 结构 |
关联模块:
- [eng/clip.c](file:///d:/reactos/win32ss/gdi/eng/clip.c) — CLIPOBJ 机制(WNDOBJ 的继承基础)
- [ntuser/window.c](file:///d:/reactos/win32ss/user/ntuser/window.c) — WND 窗口对象(上游)
- [ntuser/vis.c](file:///d:/reactos/win32ss/user/ntuser/vis.c) — VIS_ComputeVisibleRegion 可见区域计算
关联文档:
- 《ReactOS 图形系统分析_4.md》— GDI 引擎整体架构(本文件是窗口对象子模块)
- 《ReactOS 图形系统分析_8.md》— 裁剪管理(WNDOBJ 复用的 CLIPOBJ 机制)
本文档基于 ReactOS 源代码
win32ss/gdi/eng/engwindow.c分析(2026 年 8 月)