Windows编程系列:设置资源管理器背景
偶然的机会,在github上发现了一个有趣且优秀的项目,https://github.com/Maplespe/explorerTool。
这里学习了一下,并顺带学习了一下涉及的相关知识点。不得不感叹作者的厉害之处,能想到这种方法。
主要实现原理:
1、创建一个DLL,通过远线程注入的方式,注入资源管理器进程
2、在DLL被加载时时,对api进行hook,包括CreateWindowExW,DestroyWindow, BeginPaint, FillRect, CreateCompatibleDC等函数
3、通过ApiHook,在调用CreateWindows函数创建窗口时,如果是资源管理器的内容区域窗口,就将窗口记录下来,
说明:不同版本的Windows版本窗口层级有变化,需要针对不同版本做不同处理
4、通过ApiHook,hook FillRect函数,在系统绘制完成之后 ,调用AlphaBlend函数,绘制背景图像
最终实现效果如上图所示,其它区域都不支持透明,所以没办法让背景全部透过去。
以前想过自己开发一个资源管理器,以便对UI进行完全控制。 但是资源管理器是一个大工程,时间有限,一直没有付诸行动。
从XP以后,再也没有实现过资源管理器背景,这种方法虽然是通过“外挂”的形式实现,但至少也弥补了我心中的一点遗憾。再次感谢原作者。
这个项目主要涉及以下几个知识点
1、GDI绘图(资源管理器的窗口是使用GDI技术进行绘制的,在user32.dll中)
2、ApiHook
3、DLL编程及DLL注入
下面会详细介绍这个功能是如何实现的。
ApiHook
关于ApiHook,可以参考前面的文章
https://www.cnblogs.com/zhaotianff/p/18073138,这篇文章中介绍了minHook库的使用。
GDI绘图
GDI绘图可以参考这两篇文章
https://www.cnblogs.com/zhaotianff/p/18108747
https://www.cnblogs.com/zhaotianff/p/18120634
掌握这部分理论,后面的内容就可以理解了。
DLL方面
如果在DLL编程还没有了解过,也可以参考 https://www.cnblogs.com/zhaotianff/p/15141399.html
在dllmain中调用apihook,示例代码如下:
1 BOOL APIENTRY DllMain( HMODULE hModule, 2 DWORD ul_reason_for_call, 3 LPVOID lpReserved 4 ) 5 { 6 switch (ul_reason_for_call) 7 { 8 case DLL_PROCESS_ATTACH: 9 if (NULL == g_hShellHookDllModule) 10 { 11 12 //省略 13 //获取当前进程名并判断是否为explorer.exe 14 if (lstrcmp(szModuleName, L"explorer.exe") == 0) 15 { 16 //开始hook 17 StartHook(); 18 } 19 } 20 break; 21 case DLL_THREAD_ATTACH: 22 case DLL_THREAD_DETACH: 23 case DLL_PROCESS_DETACH: 24 break; 25 } 26 return TRUE; 27 }
StartHook函数内部对api进行hook
1 VOID StartHook() 2 { 3 //省略初始化代码 4 5 if (MH_Initialize() != MH_OK) 6 return; 7 8 //调用minHook库中的函数对api进行hook 9 MH_CreateHookEx(&CreateWindowExW, &MyCreateWindowExW, &InitCreateWindowExW); 10 MH_CreateHookEx(&DestroyWindow, &MyDestroyWindow, &InitDestroyWindow); 11 MH_CreateHookEx(&BeginPaint, &MyBeginPaint, &InitBeginPaint); 12 MH_CreateHookEx(&FillRect, &MyFillRect, &InitFillRect); 13 MH_CreateHookEx(&CreateCompatibleDC, &MyCreateCompatibleDC, &InitCreateCompatibleDC); 14 15 MH_EnableHook(MH_ALL_HOOKS); 16 }
在窗口创建时,判断是否是资源管理器窗口,并进行记录
1 HWND MyCreateWindowExW(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) 2 { 3 HWND hwnd = InitCreateWindowExW(dwExStyle, lpClassName, lpWindowName, 4 dwStyle, X, Y, nWidth, nHeight, hWndParent, 5 hMenu, hInstance, lpParam); 6 7 TCHAR szClassName[260]{}; 8 TCHAR szParentClassName[260]{}; 9 GetClassName(hwnd, szClassName, 260); 10 GetClassName(hWndParent, szParentClassName, 260); 11 12 //通过窗口层级判断是否是内容区域句柄 13 if (lstrcmp(szClassName, L"DirectUIHWND") == 0 14 && lstrcmp(szParentClassName, L"SHELLDLL_DefView") == 0) 15 { 16 HWND hWndGranp = GetParent(hWndParent); 17 TCHAR szGranpClassName[260]{}; 18 GetClassName(hWndGranp, szGranpClassName, 260); 19 20 if (lstrcmp(szGranpClassName, L"ShellTabWindowClass") == 0) 21 { 22 ShellWindow shellwindow; 23 shellwindow.hwnd = hwnd; 24 25 //以当前线程ID为Key将窗口句柄记录下来 26 g_mapShellWindow[GetCurrentThreadId()] = shellwindow; 27 } 28 } 29 30 return hwnd; 31 }
这里我们创建了一个记录句柄和HDC的数据结构,它的作用是将窗口句柄和HDC成对保存起来。
定义如下:
1 typedef struct _ShellWindow
2 {
3 HWND hwnd;
4 HDC hdc;
5 SIZE size;
6 }ShellWindow,*PShellWindow;
在窗口被销毁时,从记录的窗口列表中移除窗口
1 BOOL MyDestroyWindow(HWND hwnd) 2 { 3 std::unordered_map<DWORD, ShellWindow>::iterator iter = g_mapShellWindow.find(GetCurrentThreadId()); 4 5 if (iter != g_mapShellWindow.end() && hwnd == iter->second.hwnd) 6 { 7 g_mapShellWindow.erase(iter); 8 } 9 10 return InitDestroyWindow(hwnd); 11 }
BeginPaint
BeginPaint是获取客户区窗口DC的一种方式,这部分涉及GDI绘图,可以参考前面的文章。
获取HDC后,将HDC记录到窗口对应的HDC里
1 HDC MyBeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint) 2 { 3 HDC hdc = InitBeginPaint(hWnd, lpPaint); 4 5 auto iter = g_mapShellWindow.find(GetCurrentThreadId()); 6 7 if (iter != g_mapShellWindow.end() && hWnd == iter->second.hwnd) 8 { 9 iter->second.hdc = hdc; 10 } 11 12 return hdc; 13 }
CreateCompitableDC
CreateCompatibleDC 函数创建与指定设备兼容的内存设备上下文 (DC) 。
它会在BeginPaint之后调用,创建与指定设备兼容的DC,我们将新创建出来的DC替换原来的DC,并记录下来
HDC MyCreateCompatibleDC(HDC hDc) { HDC retHdc = InitCreateCompatibleDC(hDc); auto iter = g_mapShellWindow.find(GetCurrentThreadId()); if (iter != g_mapShellWindow.end() && iter->second.hdc == hDc) { iter->second.hdc = retHdc; } return retHdc; }
FillRect
有了前面的拿到的HDC,我们可以在系统对窗口内容区域进行填充完成后,将背景绘制到窗口上。
AlphaBlend 函数的功能是显示具有透明或半透明像素的位图。支持透明。
1 int MyFillRect(HDC hDC, RECT* lprc, HBRUSH hbr) 2 { 3 4 //省略 5 BLENDFUNCTION bf = { AC_SRC_OVER, 0, img->opactiy, AC_SRC_ALPHA }; 6 //绘制背景 7 AlphaBlend(hDC, pos.x, pos.y, dstSize.cx, dstSize.cy, img->hdc, 0, 0, img->size.cx, img->size.cy, bf); 8 } 9 return ret; 10 }
到这里基本已经实现全部功能了,在DLL释放时,再释放申请的资源就可以了。
还有一些细节方面,没有进行详细说明,可以自行阅读源码了解。
示例代码
完整的实现代码可以参考原作者的项目地址:https://github.com/Maplespe/explorerTool
也可以参考我仓库里的代码:https://github.com/zhaotianff/WindowsX/tree/main/WindowsX/WindowsX.Core.ShellHook
原作者的最新版本已经改为COM实现了,我这里还是使用的远线程注入的方式实现。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
2020-04-22 C#代码分析工具Style Cop使用