Windows核心编程_将窗口嵌入到桌面图标下面不被遮挡+spy++分析过程

近年很流行动态视频桌面,实则上早期的windows vista系统上有一个Windows DreamScene软件将桌面壁纸设置成视频,但是是收费的!

首先先来观察一下Windows桌面的组成单元:

我们可以通过spy++工具来帮助我们探测屏幕窗口:

1.打开spy++


选择窗口探测功能:



按住图标然后将焦点挪移到电脑桌面上

然后在点击确定


就可以找到桌面的组成单元了!


可以看到桌面是由三个窗口组成的!父窗口-背景-图标

那么接下来我们先编写代码试试可不可以直接嵌入到背景的窗口看一下程序会不会被遮挡:

首先开始之前需要介绍三个Windows_SDK函数

1.FindWindow(宏函数,原函数:FindWindowA)

函数原型:

FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName name);

参数介绍:

lpClassName
指向一个以NULL字符结尾的、用来指定类名的字符串或一个可以确定类名字符串的原子。如果这个参数是一个原子,那么它必须是一个在调用此函数前已经通过GlobalAddAtom函数创建好的全局原子。这个原子(一个16bit的值),必须被放置在lpClassName的低位字节中,lpClassName的高位字节置零。
如果该参数为null时,将会寻找任何与lpWindowName参数匹配的窗口。
lpWindowName
指向一个以NULL字符结尾的、用来指定窗口名(即窗口标题)的字符串。如果此参数为NULL,则匹配所有窗口名。

返回值:

如果函数执行成功,则返回值是拥有指定窗口类名或窗口名的窗口的句柄。
如果函数执行失败,则返回值为 NULL 。可以通过调用GetLastError函数获得更加详细的错误信息。

2. FindWindowEx(宏函数,原函数:FindWindowExA)

函数原型:

FindWindowEx(HWND hWndParent,HWND hWndChildAfter,LPCWSTR lpszClass,LPCWSTR lpszWindow);

参数介绍:

(1)hwndParent:要查找的子窗口所在的父窗口的句柄(如果设置了hwndParent,则表示从这个hwndParent指向的父窗口中搜索子窗口)。
如果hwndParent为 0 ,则函数以桌面窗口为父窗口,查找桌面窗口的所有子窗口。
Windows NT5.0 and later:如果hwndParent是HWND_MESSAGE,函数仅查找所有消息窗口。
(2)hwndChildAfter :子窗口句柄。查找从在Z序中的下一个子窗口开始。子窗口必须为hwndParent窗口的直接子窗口而非后代窗口。如果HwndChildAfter为NULL,查找从hwndParent的第一个子窗口开始。如果hwndParent 和 hwndChildAfter同时为NULL,则函数查找所有的顶层窗口及消息窗口。
(3)lpszClass:指向一个指定了类名的空结束字符串,或一个标识类名字符串的成员的指针。如果该参数为一个成员,则它必须为前次调用theGlobaIAddAtom函数产生的全局成员。该成员为16位,必须位于lpClassName的低16位,高位必须为0。
(4)lpszWindow:指向一个指定了窗口名(窗口标题)的空结束字符串。如果该参数为 NULL,则为所有窗口全匹配。

返回值:

Long,找到的窗口的句柄。如未找到相符窗口,则返回零。会设置GetLastError
如果函数成功,返回值为具有指定类名和窗口名的窗口句柄。如果函数失败,返回值为NULL。
若想获得更多错误信息,请调用GetLastError函数。

3. SetParent

函数原型:

HWND SetParent(HWND hWndChild,HWND hWndNewParent);

参数介绍:

hWndChild:
子窗口句柄。
hWndNewParent:
新的父窗口句柄。如果该参数是NULL,则桌面窗口就成为新的父窗口。在WindowsNT5.0中,如果参数为HWND_MESSAGE,则子窗口成为消息窗口。

返回值:

如果函数成功,返回值为子窗口的原父窗口句柄;如果函数失败,返回值为NULL。若想获得多错误信息,请调用GetLastError函数。

开始编写代码:

开始之前需要一个窗口,这里我已经创建好了一个MFC窗口并将背景图片更改了一下,窗口样式改为无边框,窗口标题是:123


1.获取要嵌入窗口的句柄

//获取要嵌入窗口的句柄
	HWND hWnd = FindWindow(_T("Progman"), _T("Program Manager"));
	if (hWnd == NULL){
		printf("无法获取窗口句柄.1");
		getchar();
		return -1;
	}

2.获取背景窗口句柄

//背景窗口句柄
	HWND hWndex = FindWindowEx(hWnd, 0,_T("SHELLDLL_DefView"),NULL);
	if (hWndex == NULL){
		printf("无法获取窗口句柄.2");
		getchar();
		return -1;
	}

3.获取被嵌入窗口的句柄,获取成功嵌入到背景窗口中

//被嵌入的窗口句柄
	HWND test_hwnd = FindWindow(NULL, _T("123"));
	if (test_hwnd == NULL){
		printf("无法获取窗口句柄.3");
		getchar();
		return -1;
	}
	else{
		//嵌入窗口
		SetParent(hWndex, hWnd);
		printf("窗口嵌入完成");
	}

完整代码:

//获取要嵌入窗口的句柄
	HWND hWnd = FindWindow(_T("Progman"), _T("Program Manager"));
	if (hWnd == NULL){
		printf("无法获取窗口句柄.1");
		getchar();
		return -1;
	}
	//背景窗口句柄
	HWND hWndex = FindWindowEx(hWnd, 0,_T("SHELLDLL_DefView"),NULL);
	if (hWndex == NULL){
		printf("无法获取窗口句柄.2");
		getchar();
		return -1;
	}
	//被嵌入的窗口句柄
	HWND test_hwnd = FindWindow(NULL, _T("123"));
	if (test_hwnd == NULL){
		printf("无法获取窗口句柄.3");
		getchar();
		return -1;
	}
	else{
		//嵌入窗口
		SetParent(hWndex, hWnd);
		printf("窗口嵌入完成");
	}
	getchar();
	return -1;
}

运行效果:


可以看到遮挡了图标,很明显这不是我们想要的效果!

实则上无论你嵌入到那一层窗口要么被遮挡无法显示,要么遮挡图片,这是因为Windows桌面程序Z序的原因,来底层的分析一下:

假如说你已经创建了一个窗口,窗口名为:ONE,一个子窗口为TEST,此时窗口里的Z序为:ONE(0)-TEST(1)

根据Z序的显示顺序就是:


如果你在第三方加入一个窗口,窗口名为OIU,那么它的Z序就是3,如果加进去就是这样的效果:


就算你加入进去了,并修改了Z序也还是不行的,因为Windows窗口的分层窗口不是透明的,可以自行使用SPY++发送透明消息测试!

那么这个时候我们需要让它的Z序发生变化,也就是将桌面改成分屏的,让其多屏显示,将图标层窗口单独放到一个透明窗口中显示:

下面给大家介绍一个Windows桌面消息:

十六进制:

0x005C

十进制:

1324

这是Windows桌面系统保留的消息,当你将这个消息发送到Windows桌面时,桌面程序就会自动生成一个透明窗口:WorkerW,Progman上的背景窗口和图标List都会成为WorkerW窗口的子窗口,同时还会产生一个分屏窗口:WorkerW同名的窗口,该窗口属于Progman!

当该消息产生后屏幕Z序将会发生改变(不是窗口Z序):

Z序为:WorkerW-WorkerW(多屏消息产生的窗口)-Progman

首先我们编写代码使用SendMessage函数来对Windows窗口发送消息看看!

开始之前介绍一下SendMessage(宏函数,原函数SendMessageA)函数:

函数原型:

LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam)

参数介绍:

hWnd:其窗口程序将接收消息的窗口的句柄。如果此参数为HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息不被发送到子窗口。
Msg:指定被发送的消息。
wParam:指定附加的消息特定信息。
IParam:指定附加的消息特定信息。

返回值:

返回值指定消息处理的结果,依赖于所发送的消息。

发送多屏消息:

//获取窗口句柄
	HWND hWnd = FindWindow(_T("Progman"), _T("Program Manager"));
	if (hWnd == NULL){
		printf("无法获取桌面句柄");
		getchar();
		return 0;
	}
	//发送多屏消息
	SendMessage(hWnd, 0x052c, 0, 0);

完整代码:

//获取窗口句柄
	HWND hWnd = FindWindow(_T("Progman"), _T("Program Manager"));
	if (hWnd == NULL){
		printf("无法获取桌面句柄");
		getchar();
		return 0;
	}
	//发送多屏消息
	SendMessage(hWnd, 0x052c, 0, 0);
	getchar();
	return -1;

重新使用spy++获取看一下桌面结构有没有发生改变:


可以看到SHELLDLL_DefView不在是Porgram的子窗口则是WorkerW(透明分层窗口)的子窗口,同时又多出一个窗口WorkerW,通过SPY++窗口属性中得出该窗口的父窗口是Program,但是屏幕Z序高于Program窗口(注意为什么子窗口Z序高于父窗口?注意这里多出来的WorkerW窗口只是把Program作为父窗口,但是实则上自己并没有嵌入到Program窗口当中,所以在WorkerW(透明分层窗口中,它的Z序要高于Program窗口,也就是屏幕Z序))!

如果子窗口嵌入父窗口的话,关系线会以这样的形式表现:


此时,你将窗口在嵌入到SHELLDLL_DefView也是不行的,因为你嵌入之后根据Z序的排列,你的窗口会在SysListView窗口的下面那么这样会遮挡图标,也就是如下窗口Z序结构:

SHELLDLL_DefView-SysListView32-你的窗口

但是已经发送多屏消息之后的Windows桌面已经是透明的了,所以我们不必要将窗口嵌入到该窗口上,我们可以嵌入到Progman窗口上,同时要关闭:WorkerW窗口,因为它会遮挡你的窗口!为什么?

如果你嵌入到Program窗口上的话还有一个问题就是会被WorkerW遮挡,所以我们要关闭WorkerW窗口,然后在嵌入到Program窗口上!

注意WorkerW窗口,你不能嵌入上去,因为Windows生成的分屏窗口WorkerW,被Windows隐藏了,所以你嵌入上去是不可见得,你可以发送消息让其可,这样也能达到不遮挡图标的效果!

从上面可以看出来有一个矛盾:

1.WorkerW窗口已经被隐藏了,直接嵌入到Program窗口按理说不会遮挡啊?可是就是这么奇怪即便是隐藏了,你不关闭它还是会遮挡你的窗口,原因如下:

当WorkerW(分层透明)窗口被创建出来时所产生的分层窗口(WokrerW)背景是白色的,且不可见!

那么最大的疑问来了,既然不可见,为什么还能遮挡我嵌入到Program上的窗口?

第一该分层窗口虽然被显示出来了,但是屏幕缓冲区没有改变,也就是说Windows在内存中绘制出来了,但是并没有立即写入显存,因为Windows一开始就没打算让其显示出来,所以那一块显存内容还是原先的Program窗口内容,至于Windows为什么要创建这么一个窗口且不可见,具体原因要问微软的程序员了,注意存放WorkerW(分层窗口)这块显存的缓冲区实则上还是原来之前的Program窗口数据(并没有被改变)而Program窗口上则显示着桌面背景图,我们可以动手发送消息让其重新绘图然后将其显示出来看一下:


可以看到桌面变成了白色,知道了为什么被遮挡问题之后,就知道如何解决了:

1.发送绘图消息让其WorkerW窗口重新绘制,并隐藏WorkerW(多出来的分层窗口)窗口,然后将窗口嵌入到Program窗口上

2.关闭WorkerW窗口,然后将窗口嵌入到Program窗口上

3.发送绘图消息让其WorkerW窗口重新绘制,并嵌入到WorkerW窗口上(根据屏幕Z序原因不会遮挡图标)

这里我们使用第二种方法,也就是比较难的方法,因为WorkerW分层窗口不能直接获取,所以要遍历,也就是比较复杂的,因为只有难的方法才能凸显个人手动能力!

注意要关闭WorkerW不可以直接使用FindWindow获取,因为Windows中存在一个优先级较高的同类WorkerW窗口,倘若你使用WorkerW函数获取的话不会获取到窗口,而是获取到一个隐藏的WorkerW窗口,所以我们需要以遍历屏幕窗口类的方式来获取!

开始前介绍一下所需函数:

函数原型:

BOOL EnumWindows(WNDENUMPROC lpEnumFunc,LPARAM lParam);

参数介绍:

lpEnumFunc:指向一个应用程序定义的回调函数指针,请参看EnumWindowsProc。
lPararm:指定一个传递给回调函数的应用程序定义值。

回调函数原型:

BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam);
参数:
hwnd:顶层窗口的句柄
lparam:应用程序定义的一个值(即EnumWindows中lParam)

返回值:

如果函数成功,返回值为非零;如果函数失败,返回值为零。若想获得更多错误信息,请调用GetLastError函数。

好了思路推理出来了,也分析出来了,就可以编写实际代码了:

1.声明变量与数据结构体:

//声明重载EnumWindows回调函数EnumWindowsProc
extern BOOL CALLBACK  EnumWindowsProc(HWND hwnd, LPARAM lParam);
//声明一个结构体用于存储获取到的所有窗口类名
typedef struct windows_class{
	char window_class_name[256];
	HWND win_hwnd;
	windows_class *next;

}windows_class;
//声明一个全局结构体
windows_class *class_name;
//记录屏幕窗口类数量
int num;

2.获取窗口句柄

//获取窗口句柄
	HWND hWnd = FindWindow(_T("Progman"), _T("Program Manager"));
	if (hWnd == NULL){
		printf("无法获取桌面句柄");
		getchar();
		return 0;
	}

3.发送多屏消息

//发送多屏消息
SendMessage(hWnd, 0x052c, 0, 0);
4.结构体初始化
//结构体初始化
class_name = (windows_class*)malloc(sizeof(windows_class));

5. 枚举屏幕窗口

//枚举屏幕上所有窗口
	EnumWindows(EnumWindowsProc, 0);

回调函数:

BOOL CALLBACK  EnumWindowsProc(HWND hwnd, LPARAM lParam)

{
	//声明结构体
	windows_class *enum_calss_name;
	//初始化
	enum_calss_name = (windows_class*)malloc(sizeof(windows_class));
	//填充0到类名变量中
	memset(enum_calss_name->window_class_name, 0, sizeof(enum_calss_name->window_class_name));
	//获取窗口类名
	GetClassNameA(hwnd, enum_calss_name->window_class_name, sizeof(enum_calss_name->window_class_name));
	//获取窗口句柄
	enum_calss_name->win_hwnd = hwnd;
	//递增类数量
	num += 1;
	//链表形式存储
	enum_calss_name->next = class_name;
	class_name = enum_calss_name;
	return TRUE;//这里必须返回TRUE,返回FALSE就不在枚举了
}

6.循环比对找到->WorkerW类

for (int i = 0;i<num; ++i){
		if (strncmp(class_name->window_class_name, "WorkerW", strlen(class_name->window_class_name)) == 0){//以有效字符比对,防止连同字符“0”等无效字符也一同包含在一起比对
			HWND window_hwnd = FindWindowExA(class_name->win_hwnd, 0, "SHELLDLL_DefView", NULL);
			if (window_hwnd == NULL){	//无法获取句柄代表该workerw类窗口没有子窗口也就是获取到图标下面的WorkerW类窗口了
				//直接关闭该窗口
				SendMessage(class_name->win_hwnd, 16, 0, 0);
				break;
			}
			else{
				//获取成功看一下下一个窗口是不是Progman
				class_name = class_name->next;
				if (class_name->window_class_name == "Progman"){
					HWND window_hwnd = FindWindowExA(class_name->win_hwnd, 0, "WorkerW", NULL);	//获取图标下面的WorkerW子窗口
					if (window_hwnd == NULL){	//获取不到代表该窗口已经被关闭了
						printf("该窗口已经被关闭..");
						getchar();
						break;
					}
					else{	//结束窗口
						SendMessage(window_hwnd, 16, 0, 0);
					}
				}
				else{//如果不是Progman就代表WorkerW类窗口的屏幕Z序列高于Progman,就说明获取到了WorkerW类窗口,直接关闭即可
					SendMessage(class_name->win_hwnd, 16, 0, 0);
				}//注意WorkerW类是多屏消息产生的,是从Progman类分割下来的,在屏幕Z序列中会相邻在一起,所以不用担心next下一个类窗口不是Progman或者要删除的图标下面的WorkerW类
				
			}
			//break;		//这行代码注释掉,如果上面的代码没有关闭图标下的WorkerW窗口说明不是真正的多屏消息产生的WorkerW窗口,让其继续循环下去,将屏幕所有窗口遍历一遍!
		}
		class_name = class_name->next;
	}
7.嵌入窗口
//到了这一步就可以将你的视频窗口嵌入到Progman类窗口当中了,嵌入之后也不会被遮挡,因为遮挡的WorkerW窗口已经被关闭了!
	//所以这里我就随便嵌入一个窗口进入桌面看一下会不会被遮挡
	//获取要嵌入窗口的句柄
	HWND test_hwnd = FindWindow(NULL, _T("123"));
	if (test_hwnd == NULL){
		printf("要嵌入的窗口不存在..");
		getchar();
		return -1;
	}
	else{
		SetParent(test_hwnd, hWnd);
		printf("窗口嵌入完成");
	}

运行效果:


完整代码:

#include <windows.h>
//声明重载EnumWindows回调函数EnumWindowsProc
extern BOOL CALLBACK  EnumWindowsProc(HWND hwnd, LPARAM lParam);
//声明一个结构体用于存储获取到的所有窗口类名
typedef struct windows_class{
	char window_class_name[256];
	HWND win_hwnd;
	windows_class *next;

}windows_class;
//声明一个全局结构体
windows_class *class_name;
//记录屏幕窗口类数量
int num;
int main()
{
	//获取窗口句柄
	HWND hWnd = FindWindow(_T("Progman"), _T("Program Manager"));
	if (hWnd == NULL){
		printf("无法获取桌面句柄");
		getchar();
		return 0;
	}
	//发送多屏消息
	SendMessage(hWnd, 0x052c, 0, 0);
	//结构体初始化
	class_name = (windows_class*)malloc(sizeof(windows_class));
	//枚举屏幕上所有窗口
	EnumWindows(EnumWindowsProc, 0);
	//循环比对找到->WorkerW类
	for (int i = 0;i<num; ++i){
		if (strncmp(class_name->window_class_name, "WorkerW", strlen(class_name->window_class_name)) == 0){//以有效字符比对,防止连同字符“0”等无效字符也一同包含在一起比对
			HWND window_hwnd = FindWindowExA(class_name->win_hwnd, 0, "SHELLDLL_DefView", NULL);
			if (window_hwnd == NULL){	//无法获取句柄代表该workerw类窗口没有子窗口也就是获取到图标下面的WorkerW类窗口了
				//直接关闭该窗口
				SendMessage(class_name->win_hwnd, 16, 0, 0);
				break;
			}
			else{
				//获取成功看一下下一个窗口是不是Progman
				class_name = class_name->next;
				if (class_name->window_class_name == "Progman"){
					HWND window_hwnd = FindWindowExA(class_name->win_hwnd, 0, "WorkerW", NULL);	//获取图标下面的WorkerW子窗口
					if (window_hwnd == NULL){	//获取不到代表该窗口已经被关闭了
						printf("该窗口已经被关闭..");
						getchar();
						break;
					}
					else{	//结束窗口
						SendMessage(window_hwnd, 16, 0, 0);
					}
				}
				else{//如果不是Progman就代表WorkerW类窗口的屏幕Z序列高于Progman,就说明获取到了WorkerW类窗口,直接关闭即可
					SendMessage(class_name->win_hwnd, 16, 0, 0);
				}//注意WorkerW类是多屏消息产生的,是从Progman类分割下来的,在屏幕Z序列中会相邻在一起,所以不用担心next下一个类窗口不是Progman或者要删除的图标下面的WorkerW类
				
			}
			//break;		//这行代码注释掉,如果上面的代码没有关闭图标下的WorkerW窗口说明不是真正的多屏消息产生的WorkerW窗口,让其继续循环下去,将屏幕所有窗口遍历一遍!
		}
		class_name = class_name->next;
	}
	//到了这一步就可以将你的视频窗口嵌入到Progman类窗口当中了,嵌入之后也不会被遮挡,因为遮挡的WorkerW窗口已经被关闭了!
	//所以这里我就随便嵌入一个窗口进入桌面看一下会不会被遮挡
	//获取要嵌入窗口的句柄
	HWND test_hwnd = FindWindow(NULL, _T("123"));
	if (test_hwnd == NULL){
		printf("要嵌入的窗口不存在..");
		getchar();
		return -1;
	}
	else{
		SetParent(test_hwnd, hWnd);
		printf("窗口嵌入完成");
	}
	getchar();
	return -1;
}
BOOL CALLBACK  EnumWindowsProc(HWND hwnd, LPARAM lParam)

{
	//声明结构体
	windows_class *enum_calss_name;
	//初始化
	enum_calss_name = (windows_class*)malloc(sizeof(windows_class));
	//填充0到类名变量中
	memset(enum_calss_name->window_class_name, 0, sizeof(enum_calss_name->window_class_name));
	//获取窗口类名
	GetClassNameA(hwnd, enum_calss_name->window_class_name, sizeof(enum_calss_name->window_class_name));
	//获取窗口句柄
	enum_calss_name->win_hwnd = hwnd;
	//递增类数量
	num += 1;
	//链表形式存储
	enum_calss_name->next = class_name;
	class_name = enum_calss_name;
	return TRUE;//这里必须返回TRUE,返回FALSE就不在枚举了
}

尾言:

你可以根据本博客上的教程,写一个属于自己的动态视频桌面!

相关链接:动态视频桌面插件

猜你喜欢

转载自blog.csdn.net/bjbz_cxy/article/details/79893134