网站关键词的优化在哪做,企业网站建设找智恒网络,做鞋的垂直网站,网站买源代码目录
前言
一、关于 WorkerW 工作区窗口
二、关于窗口关系
2.1 窗口以及窗口隶属关系
2.2 桌面管理层窗口组分简析
2.3 厘清两个概念的区别
2.4 关于设置父窗口
三、编写代码以供在 Vista 上实现
3.1 方法二#xff1a;子类化并自绘窗口背景
四、初步分析桌面管理层…目录
前言
一、关于 WorkerW 工作区窗口
二、关于窗口关系
2.1 窗口以及窗口隶属关系
2.2 桌面管理层窗口组分简析
2.3 厘清两个概念的区别
2.4 关于设置父窗口
三、编写代码以供在 Vista 上实现
3.1 方法二子类化并自绘窗口背景
四、初步分析桌面管理层窗口创建的原理
4.1 桌面管理层窗口的创建流程
4.2 从管理层窗口回调看 0x052C 消息
总结 文章出处来源[https://blog.csdn.net/qq_59075481/article/details/133801491]。
前言
这是实现 D2WT (Dynamic Desktop Wallpaper Tools) 系列的第二节在本节中我们进一步讨论 WorkerW 窗口的功能介绍桌面窗口创建的流程同时讨论为什么在 Vista 上无法嵌入窗口。 【提示】本文涉及的关于窗口的处理部分基于我曾经发的《桌面自定义 WorkerW 窗口》一文。里面的思路有类似的地方但比那边讲的大概更加透彻。 需要查看第一节的可以点击这里实现桌面动态壁纸一
相关系列文章
序号文章标题链接AID1实现桌面动态壁纸一1253616502实现桌面动态壁纸二[本文] 133801491 3实现桌面动态壁纸三[未来发布]---4实现桌面动态壁纸——认识 WebView2 控件138637909 一、关于 WorkerW 工作区窗口
WorkerW 是 Windows 操作系统中的一个窗口站 (Window Station) 和桌面 (Desktop) 的组合。它是用于用户界面的一个基础组件用于管理和控制用户界面。WorkerW 从操作系统内核中获取资源包括 CPU 资源和内存资源并将其分配给用户进程以便它们能够在屏幕上显示图形和交互元素。WorkerW 通过窗口管理器将窗口和界面元素显示在屏幕上同时允许用户与它们进行交互。以上这段来源于网络
WorkerW/A 属于工作区窗口它基本上通过调用 Shell API 函数中的 SHCreateWorkerWindowW/A 创建。其中 W 代表 WideChar (UNICODE) 版本的窗口而 SHCreateWorkerWindowA 是该函数的 ASCII 版本。任何需要侦听窗口消息的应用程序都会调用此 API 来创建工作区窗口。 SHCreateWorkerWindowW 是为文档化的导出函数通过分析 explorer.exe 发现该函数是从 api-ms-win-shlwapi-winrt-storage-l1-1-1.dll 中导入的但是看到这个名称可能会很陌生。
在 explorer.exe 的导入表上SHCreateWorkerWindowW 函数是通过解析名为 api-ms-win-shlwapi-winrt-storage-l1-1-1 的 API 集而重定向到 shlwapi.dll所以最终是需要分析 shlwapi.dll 里面的函数。
API 集微软推出的用高度命名的链接库名称分类 API 的最小唯一核心库将 API 调用通过内置加载器转发到真实的 Dll 上截止 Win11 已经更新到 V10 版本
根据 ReactOS 的开发者文档可以知道 SHCreateWorkerWindow 的定义和内部实现。
HWND WINAPI SHCreateWorkerWindow( WNDPROC wndProc, HWND hWndParent, DWORD dwExStyle, DWORD dwStyle, HMENU hMenu, LONG_PTR wnd_extra ) SHCreateWorkerWindowA/W 其实就是 CreateWindowExA/W 的封装
HWND WINAPI SHCreateWorkerWindowA(
WNDPROC wndProc,
HWND hWndParent,
DWORD dwExStyle,
DWORD dwStyle,
HMENU hMenu,
LONG_PTR wnd_extra
)
{static const char szClass[] WorkerA;WNDCLASSA wc;HWND hWnd;TRACE((%p, %p, 0x%08x, 0x%08x, %p, 0x%08lx)\n,wndProc, hWndParent, dwExStyle, dwStyle, hMenu, wnd_extra);/* Create Window class */wc.style 0;wc.lpfnWndProc DefWindowProcA;wc.cbClsExtra 0;wc.cbWndExtra sizeof(LONG_PTR);wc.hInstance shlwapi_hInstance;wc.hIcon NULL;wc.hCursor LoadCursorA(NULL, (LPSTR)IDC_ARROW);wc.hbrBackground (HBRUSH)(COLOR_BTNFACE 1);wc.lpszMenuName NULL;wc.lpszClassName szClass;SHRegisterClassA(wc);hWnd CreateWindowExA(dwExStyle, szClass, 0, dwStyle, 0, 0, 0, 0,hWndParent, hMenu, shlwapi_hInstance, 0);if (hWnd){SetWindowLongPtrA(hWnd, 0, wnd_extra);if (wndProc) SetWindowLongPtrA(hWnd, GWLP_WNDPROC, (LONG_PTR)wndProc);}return hWnd;
}HWND WINAPI SHCreateWorkerWindowW(
WNDPROC wndProc,
HWND hWndParent,
DWORD dwExStyle,
DWORD dwStyle,
HMENU hMenu,
LONG_PTR wnd_extra
)
{static const WCHAR szClass[] { W, o, r, k, e, r, W, 0 };WNDCLASSW wc;HWND hWnd;TRACE((%p, %p, 0x%08x, 0x%08x, %p, 0x%08lx)\n,wndProc, hWndParent, dwExStyle, dwStyle, hMenu, wnd_extra);/* If our OS is natively ANSI, use the ANSI version */if (GetVersion() 0x80000000) /* not NT */{TRACE(fallback to ANSI, ver 0x%08x\n, GetVersion());return SHCreateWorkerWindowA(wndProc, hWndParent, dwExStyle, dwStyle, hMenu, wnd_extra);}/* Create Window class */wc.style 0;wc.lpfnWndProc DefWindowProcW;wc.cbClsExtra 0;wc.cbWndExtra sizeof(LONG_PTR);wc.hInstance shlwapi_hInstance;wc.hIcon NULL;wc.hCursor LoadCursorW(NULL, (LPWSTR)IDC_ARROW);wc.hbrBackground (HBRUSH)(COLOR_BTNFACE 1);wc.lpszMenuName NULL;wc.lpszClassName szClass;SHRegisterClassW(wc);hWnd CreateWindowExW(dwExStyle, szClass, 0, dwStyle, 0, 0, 0, 0,hWndParent, hMenu, shlwapi_hInstance, 0);if (hWnd){SetWindowLongPtrW(hWnd, 0, wnd_extra);if (wndProc) SetWindowLongPtrW(hWnd, GWLP_WNDPROC, (LONG_PTR)wndProc);}return hWnd;
}
在 DWM 机制完善之前的操作系统上切换桌面壁纸或者系统主题的时候窗口的绘制会出现卡顿、频闪现象。在切换主题的时候微软通过 LockWindowUpdate 函数阻止其他窗口的绘制并显示一个“请稍后”窗口来避免用户看到卡顿的桌面管理层窗口。但是这给用户的体验并不是特别好因为需要“等待”。随后在 DWM 组件的支持下切换壁纸前首先将 DefView 窗口分离出来然后利用 WorkerW 窗口去绘制 DefView 的背景在内存中首先生成双缓冲将新壁纸和旧壁纸的图案之间合成交叉溶解的图像动画从而实现窗口背景的平滑处理。
下图展示了在切换主题的交叉阶段桌面管理层窗口的变化新旧壁纸的交叉溶解效果 我们意识到SHCreateWorkerWindow 只能创建类名是 WorkerW 的窗口关键部分并不在于这个函数想要知道系统是如何实现透明层次的还需要研究其窗口过程以及后续的处理我想这需要对桌面窗口有一个深入一点的理解。
二、关于窗口关系
2.1 窗口以及窗口隶属关系
TODO之后补充
2.2 桌面管理层窗口组分简析
我们知道桌面管理层窗口在未产生 WorkerW 分层时窗口的层次应该如下所示 我么可以通过简单的手法理解这些窗口的作用
1 SysHeader32 窗口
SysHeader32 窗口是一个不可见窗口这个窗口主要负责在 ListView 上绘制每个图标的文本。
验证方法通过 SendMessageW(hSysHead, WM_CLOSE, 0, 0) 即可关闭该窗口按 F5 刷新桌面可以观察到图标的文本已经消失但是图标依然可以正常点击 并且右键菜单依然是有效的 2 SysListView32 窗口
SysListView32 窗口主要负责控制图标列表的显示和操作关闭或者隐藏后图标列表将不可见。
验证方法隐藏窗口 ShowWindow(hListView, SW_HIDE) 可以发现图标立即消失。 但是右键菜单依然可用说明右键菜单不归它管理 3 SHELLDLL_DefView 窗口
这个窗口我们需要通过两步验证它的功能。
SHELLDLL_DefView 窗口控制图标列表窗口的背景绘制工作这可以从 SysListView32 的属性页看出 SHELLDLL_DefView 还控制右键菜单使用 ShowWindow(hDefView, SW_HIDE) 后无法打开右键菜单。
验证是否支持背景绘制工作
第一步进一步隐藏 Program Manager 窗口桌面管理层窗口的背景变成白色 这说明了Program 窗口的背景是系统设置的壁纸。
第二步将 DefView 窗口变成弹出式窗口(独立化)并恢复显示。
会发现无论 Program 窗口是否可见图标窗口的背景都是黑色的 于是我们可以判断出SHELLDLL_DefView 可以通过获取父窗口会判断是不是 Progman 窗口的图像缓冲来绘制子窗口的背景。
4 Program Manager 窗口
Program Manager 窗口是桌面管理层的主窗口Program Manager 窗口响应 WM_CLOSE 时(不响应 SC_CLOSE )会调用 Shell32.dll 中的符号并显示一个询问是否需要关闭计算机的对话框 Program 还负责显示桌面壁纸隐藏或者关闭后背景将变为白色 至此我们从窗口的可视化角度简单分析了各个桌面管理层窗口的基本作用。
2.3 厘清两个概念的区别
在这个系列的一开始我们就用“桌面管理层窗口”来称呼包含桌面图标在内的几个窗口的集合 但是我们在第一篇中我们也提到过桌面窗口这个名字桌面窗口和桌面管理层窗口有什么区别呢
桌面窗口是其他窗口的祖先在系统启动时创建类名为 “#32769” 。这个窗口由 csrss.exe 进程创建所有父窗口显示为 NULL 的窗口其实是以该窗口作为父窗口。所有窗口都在这个窗口内。所以它是 Z 序最高的窗口。
而桌面管理层窗口则是 Z 序最低的窗口。桌面管理层窗口是以名为 Program Manager 窗口为主窗口管理左面文件夹图标列表的显示、操作、桌面壁纸等功能的一系列窗口。
而这本质上不是一类窗口。此外通过 GetDesktopWindow 函数获取的窗口句柄是桌面窗口句柄而不是桌面管理层的窗口句柄。关于他们的详细内容在接下来的文章中我们会一一介绍
2.4 关于设置父窗口
在 Windows Vista 上SHELL_DefView 不支持背景透明化我们想到可以利用扩展属性 WS_EX_LAYERED 实现背景透明但是 MSDN 上明确说明该扩展属性从 Windows 8 开始才对子窗口有效果。也就是说在 Vista 上对子窗口 SHELL_DefView 设置分层属性是无效的。
这时候我们就需要将 SHELL_DefView 独立出来将其变成弹出式窗口就可以设置该属性了。
这里我么可以使用 SetParent 并指定父窗口为 NULL随后去除窗口的 WS_CHILD 属性添加 WS_POPUP | WS_EX_TOOLWINDOW 等属性来实现将窗口独立化。 HWND SetParent( _In_ HWND hWndChild, _In_opt_ HWND hWndNewParent ); [in] hWndChild 类型HWND 子窗口的句柄。 [in, optional] hWndNewParent 类型HWND 新父窗口的句柄。 如果此参数为 NULL桌面窗口将成为新的父窗口。 如果此参数 HWND_MESSAGE则子窗口将成为 仅消息窗口。 部分资料对这里的参数为 NULL 时SetParent 的行为认知可能有误解这里不是指桌面管理层窗口他不是 Progman 窗口而是由 csrss.exe 进程创建的类名为 “#32769” 窗口他是一切桌面顶级窗口的父窗口不是所有者窗口称为桌面窗口然而顶级窗口的父窗口常常被标记为 NULL。
“#32769” 窗口是一切桌面窗口的祖先窗口是系统启动的时候创建的第一个窗口。Spy 下可以看到第一个窗口就是它 查看窗口对应的进程信息 显然窗口由 CSRSS 创建。
接下来我们用一个很简单的例子测试一下就可以理解正在发生的事情
#include iostream
#include Windows.hint main()
{HWND h32769Wnd NULL;HWND hDesktopwnd NULL;HWND hNewParent NULL;HWND hNotepad NULL;HWND hOwner NULL;SetLastError(0);h32769Wnd FindWindowW(L#32769, NULL);printf(FindDesktopWnd:[ 0x%I64X ], find #32769. err_code:[%d]\n,(unsigned long long)h32769Wnd, GetLastError());hNotepad FindWindowA(Notepad, NULL);if (hNotepad){hNewParent GetAncestor(hNotepad, GA_PARENT);GetWindow(hNotepad,GW_OWNER);printf(Notepad:[ 0x%I64X ]; GetAncestorParent:[ 0x%I64X ]; GetOwner:[ 0x%I64X ].\n, (unsigned long long)hNotepad, (unsigned long long)hNewParent,(unsigned long long)hOwner);hDesktopwnd GetDesktopWindow();printf(Desktopwnd:[ 0x%I64X ], use GetDesktopWindow.\n,(unsigned long long)hDesktopwnd);if (hDesktopwnd){printf(SetParent use hDesktopwnd.\n);hNewParent SetParent(hNotepad, hDesktopwnd);printf(LastParent:[ 0x%I64X ], retn by SetParent.\n,(unsigned long long)hNewParent);}hNewParent GetAncestor(hNotepad, GA_PARENT);printf(Notepad:[ 0x%I64X ]; GetAncestorParent:[ 0x%I64X ]; GetOwner:[ 0x%I64X ].\n,(unsigned long long)hNotepad,(unsigned long long)hNewParent,(unsigned long long)hOwner);// --------------------------------------printf(\n\nSetParent use (null) ptr.\n);hNewParent SetParent(hNotepad, NULL);printf(LastParent:[ 0x%I64X ], retn by SetParent.\n,(unsigned long long)hNewParent);hNewParent GetAncestor(hNotepad, GA_PARENT);printf(Notepad:[ 0x%I64X ]; GetAncestorParent:[ 0x%I64X ]; GetOwner:[ 0x%I64X ].\n,(unsigned long long)hNotepad,(unsigned long long)hNewParent,(unsigned long long)hOwner);}system(pause);return 0;
}
我们首先尝试使用 FindWindow 查找类名但是以失败告终我们获得了无效句柄这可能和FindWindow 的机制有关没搞清楚原因只知道他是 NtUserFindWindowEx 的封装。据我推断它只从第一个顶级窗口开始检索而且没有找到 GetLastError 并不能取到非零值。
随后我们调用 SetParent 尝试设置 Notepad 的父窗口这里我们进行了横向对比第一次我们使用 GetDesktopWindow 函数获取桌面窗口句柄并把它作为第二参数传入 SetParent通过分析父窗口和返回值我们得到和 Spy 相同的结论句柄指向 #32769 窗口
第二次我们按照 MSDN 上的说明把第二个参数设置为 NULL并再次获取信息发现效果等同于传入 #32769 的有效句柄这说明 SetParent 确实会在内部将 NULL 参数解释为桌面窗口( #32769 )的句柄。
下图展示了对 Notepad 窗口进行设置父窗口的操作前后其父窗口的变化 关于 SetParent 的注意事项在我之前的一篇博客中有详细分析就不展开讨论了
SetParent 的 NULL 传参其实有两个作用
1设置窗口成为桌面顶级窗口
2将窗口提升 Z 序至前端替代 SetForegroundWindow 甚至解决了 SetForegroundWindow 有时候失败的问题。
关于第二个相当于副产品解决 SetForegroundWindow 失败网上给的代码一般是这样子的
if(hWnd)
{HWND hForeWnd GetForegroundWindow();DWORD dwForeID GetWindowThreadProcessId(hForeWnd,NULL);DWORD dwCurID GetCurrentThreadId();AttachThreadInput(dwCurID,dwForeID,TRUE);ShowWindow(hWnd,SW_SHOWNORMAL);SetWindowPos(hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);SetWindowPos(hWnd,HWND_NOTOPMOST,0,0,0,0, SWP_NOSIZE|SWP_NOMOVE);SetForegroundWindow(hWnd);AttachThreadInput(dwCurID,dwForeID,FALSE);// hWnd 就是需要置前的窗口句柄
}而我们只需要判断这个窗口是不是 POPUP 窗口并 SetParent 传参 NULL 即可。
三、编写代码以供在 Vista 上实现
在 Vista 上DWM 被首次引入操作系统但是它的框架结构和现在的有很大的不同比如它不能够响应 0x052C (WM_USER 300)的消息而创建 WorkerW 窗口。这就是为什么在第一篇章中我们直言在 Vista 上即使有开启 DWM 也不能够通过窗口嵌入的方式实现动态壁纸。
那么如果我们固执的想要在早期的系统环境下实现动态壁纸我们该如何做呢
我在之前研究过自己实现一个 WorkerW那篇博客限于一些原因一些实现细节没能公布。这里我们可以说即使不使用 WorkerW 依然可以实现动态壁纸。
我们想到将壁纸主窗口设置为 Progman 的子窗口但是 SetParent 函数有个坏毛病它会自动“擦屁股”自动调用 CZOrderManagerService 内部函数将我们的窗口 Z 序放在 SHELLDLL_DefView 的前面这是一个非常糟糕的。因为我们的窗口将完全遮盖 SHELLDLL_DefView 窗口这使得我们无法看到图标列表窗口我们的窗口始终位于上方。怎么办呢
别急这里有几种方法解决问题
3.1 方法二子类化并自绘窗口背景
TODO后期补充
四、初步分析桌面管理层窗口创建的原理 由于对桌面管理层窗口的逆向分析没有找到实质性的材料而作者本人又是初学一些反汇编知识如有分析错误的地方还望指拨。 4.1 桌面管理层窗口的创建流程
首先我们需要回顾一下桌面管理层窗口的组成 桌面浏览器窗口( DesktopBrowser )主要包括 Progman 父窗口和 DefView 窗口DefView 窗口的子窗口 SysListView32 用于绘制桌面图标等相关组件。而 Progman 的背景则绘制为桌面壁纸。
打开 IDA Pro 并反汇编 explorer.exe 可以定位到入口函数 wWinMain可以看到 wWinMain 调用了 CreateDesktopAndTray 函数这个函数是对 SHCreateDesktop 的封装用于创建桌面和 CTray 的相关成员。 从 F5 的信息可以看出函数调用了延迟加载的 Shell32.dll 中的 SHCreateDesktop 函数。 跟进 Shell32.dll 查看该函数的内部实现 有三个函数调用是关键性的
1CDesktopBrowser::CDesktopBrowser 初始化 DesktopBrowserCDesktopBrowser 内部类实现了很多函数包括图标窗口、任务栏控件、虚拟多桌面等等
2RegisterDesktopClass 是对 RegisterClassW 的封装
3SHFusionCreateWindowEx 是对 CreateWindowExW 的封装。
首先看 SHFusionCreateWindowEx 函数前面谈到初始化 DesktopBrowser 的过程似乎在主窗口创建之前然而分析上下文却能发现这两个实际上是并行操作。在 SHFusionCreateWindowEx 内首先激活并发上下文然后尝试创建主窗口同时初始化 DesktopBrowser 最后结束并发上下文并返回窗口句柄。 然后我们看一下 RegisterDesktopClass 函数这个就比较简单了 最重要的是 CDesktopBrowser 这个类里面包含了有关桌面管理层窗口的很多未导出的内部函数。
RegisterDesktopClass 函数中调用的 CDesktopBrowser::s_DesktopWndProc 回调实现对 SysListView32 窗口的创建和处理。
调用树如下图所示 SysListView32 窗口的创建和处理在 CreateDesktopView 中完成流程比较复杂暂不分析。
然后继续跟踪找到了 CDefView 类一个关键的成员函数为 CDefView::CreateViewWindow
他是对 CDefView::CreateViewWindow2 的封装CDefView::CreateViewWindow2 进行了一些对参数的初始化处理随后把工作交给了 CDefView::CreateViewWindow3在 CDefView::CreateViewWindow3 里面最终实现了创建 SHELLDLL_DefView 窗口。 4.2 从管理层窗口回调看 0x052C 消息
【这部分将在之后完善】 总结
自此我们的桌面管理层窗口的创建已经基本完成以上分析只是简单梳理一下流程其中大量调用通过 COM 类接口实现这里暂不展开分析。 本文属于原创文章转载请注明出处
https://blog.csdn.net/qq_59075481/article/details/133801491
文章更新于2023.10.202024.07.04。
文章发布于2024.07.04。