ReactOS 图形系统分析(15):窗口对象 — EWNDOBJ(engwindow.c)
2026/8/15 9:52:16 网站建设 项目流程

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

失败

失败

EngCreateWnd
(pso, hWnd, pfn, fl, iPixelFormat)

UserEnterExclusive
(用户子系统独占锁)

fl 含未支持标志?
(WO_RGN_WINDOW 等)

FIXME 警告

继续

UserGetWindowObject(hWnd)
获取窗口对象

退出返回 NULL

分配 EWNDOBJ
EngAllocMem

IntEngInitClipObj
(Rects 指向内嵌 rclBounds)

IntEngWndUpdateClipObj
用窗口可见区域填充裁剪对象

释放内存

填充 DDI 公开部分
psoOwner = pso, pvConsumer = NULL

填充内部字段
Hwnd/ChangeProc/Flags/PixelFormat

UserSetProp 关联到窗口
(AtomWndObj 属性)

gcountPWO++

UserLeave 返回 WNDOBJ

关键代码:

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. 数据流(窗口变化 → 驱动收到通知)

显示驱动engwindow.cntuser (WND 窗口)显示驱动engwindow.cntuser (WND 窗口)驱动创建窗口表面窗口移动/改变尺寸驱动处理销毁EngCreateWnd (pso, hWnd, ChangeProc, fl)UserGetWindowObject + VIS_ComputeVisibleRegionWNDOBJ (含可见区域 CLIPOBJ)IntEngWindowChanged (WOC_RGN_CLIENT)VIS_ComputeVisibleRegion (重新计算)IntEngWndUpdateClipObj (更新裁剪对象 + iUniq++)ChangeProc(WNDOBJ, WOC_RGN_CLIENT)ChangeProc(NULL, WOC_CHANGED)WNDOBJ_cEnumStart / WNDOBJ_bEnum (枚举可见矩形)可见矩形列表只重绘可见区域 (硬件加速)EngDeleteWndUserRemoveProp (解除关联)

5. 设计特点总结

  1. WNDOBJ 即特殊 CLIPOBJ:窗口对象继承 CLIPOBJ,裁剪区域 = 窗口可见区域,枚举完全复用 CLIPOBJ 机制。
  2. 可见区域计算VIS_ComputeVisibleRegion计算扣除遮挡后的真实可见区域(多矩形 → DC_COMPLEX)。
  3. 回调过滤:只发送驱动请求的标志(flChanged &= Clip->Flags),减少无关通知。
  4. 窗口属性关联:通过AtomWndObj属性把 WNDOBJ 挂到窗口,窗口变化可快速找回。
  5. iUniq 失效标记:裁剪区域每次更新iUniq++,驱动可据此判断缓存是否失效。
  6. HACK 兼容:创建/设置消费者时补发初始状态通知,符合 MSDN 对 WNDOBJCHANGEPROC 的约定。
  7. 精灵通知预留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 月)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询