再聊Win11 24H2 自定义桌面背景

在之前的文章聊聊Win11 24H2 自定义桌面背景_24h2自定义壁纸-CSDN博客中,针对24H2的改动,分析了解决的方法,并给出了C和C#两种代码。后来有些小伙伴来问我,发现没有测试过的C代码竟然是无法使用的,这里先说声抱歉。

这段时间一直在忙着玩逆向,今天终于有时间来看一下原因了。

首先,C我是用了一个控制台程序去启动一个界面程序,并通过标题获取程序句柄,并设置ExStyle,再把程序父窗口设置为workerw,按道理说,应该是正确的,但无论怎样处理,程序在改变父窗体后都是不可见的状态,这就非常奇怪了。

于是,今天想尝试一下,看看是不是因为启动程序的方式不对导致的。

尝试创建一个windows窗体程序,程序很简单,也不需要放任何控件,只需要确定程序启动后,正确嵌入桌面,并在图标下层即可。尝试编写如下代码

// CountDownApp.cpp : 定义应用程序的入口点。
//

#include "framework.h"
#include "CountDownApp.h"

#define MAX_LOADSTRING 100

// 全局变量:
HINSTANCE hInst;                                // 当前实例
WCHAR szTitle[MAX_LOADSTRING];                  // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING];            // 主窗口类名

// 此代码模块中包含的函数的前向声明:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: 在此处放置代码。

    // 初始化全局字符串
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_COUNTDOWNAPP, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 执行应用程序初始化:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_COUNTDOWNAPP));

    MSG msg;

    // 主消息循环:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}



//
//  函数: MyRegisterClass()
//
//  目标: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_COUNTDOWNAPP));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_COUNTDOWNAPP);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

BOOL CALLBACK EnumWinProc(HWND hwnd, LPARAM IParam)
{
    HWND hDefView = FindWindowEx(hwnd, 0, "SHELLDLL_DefView", 0);
    if (hDefView != NULL) {
        HWND hWorkerw = FindWindowEx(hwnd, NULL, "WorkerW", 0);
        //获取窗口的句柄
        HWND hAppHwnd = (HWND)IParam;
        //获取窗口扩展式样
        LONG nExStyle = GetWindowLong(hAppHwnd, GWL_EXSTYLE);
        //窗口扩展式样添加分层
        nExStyle |= WS_EX_LAYERED;
        nExStyle |= WS_EX_COMPOSITED;
        LONG setResult = SetWindowLong(hAppHwnd, GWL_EXSTYLE, nExStyle);
        //刷新窗口,让式样立即生效
        BOOL ret = UpdateWindow(hAppHwnd);
        ret = ShowWindow(hAppHwnd, SW_SHOW);
        //窗口设置为桌面窗口的子窗口
        HWND preHwnd = SetParent(hAppHwnd, hWorkerw);
        return FALSE;
    }
    return TRUE;
}

//
//   函数: InitInstance(HINSTANCE, int)
//
//   目标: 保存实例句柄并创建主窗口
//
//   注释:
//
//        在此函数中,我们在全局变量中保存实例句柄并
//        创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 将实例句柄存储在全局变量中

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   HWND hProgman = FindWindow("Progman", 0);//发送0x52c消息
   LRESULT result = SendMessage(hProgman, 0x52c, 0, 0);
   if (EnumWindows(EnumWinProc, (LPARAM)hWnd))
   {
       ShowWindow(hWnd, nCmdShow);
       UpdateWindow(hWnd);
   }

   return TRUE;
}

//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目标: 处理主窗口的消息。
//
//  WM_COMMAND  - 处理应用程序菜单
//  WM_PAINT    - 绘制主窗口
//  WM_DESTROY  - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 在此处添加使用 hdc 的任何绘图代码...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

一次就成功了!!说实话,有点懵,程序逻辑非常简单,就是在创建窗口实力的时候完成了控制台程序的操作,为啥就成功了呢?控制台创建的程序属性也一样,但就是不显示,程序自己来就显示正常。

具体原因还是不确定,猜测可能的原因是:

控制台创建进程后,窗口一完成load事件,之后再改动属性并刷新时,无法达到预期效果

2025.06.13更新:经过这两天的调试跟踪发现,WS_EX_LAYERED带有透明属性,会让界面的WM_PAINT消息失效,也就是说,在窗口创建前设置WS_EX_LAYERED不会影响窗口绘制,一旦窗口绘制成功,切换父句柄会让窗体变透明,由于WM_PAINT不会再执行,所以,默认控件无法再次显示出来。C#也一样,使用Paint事件跟踪发现,WS_EX_LAYERED窗体创建时,Paint事件执行2次,非WS_EX_LAYERED窗体执行1次。如果创建时没有改变父窗体,通过按钮改变,WS_EX_LAYERED窗体Paint事件只会调用一次,这样整个窗体就一直保持透明状态。

目前还没找到解决方案,也许用WS_EX_LAYERED属性本身有问题,只适合一次性启动嵌入桌面背景的程序,而要通过按钮切换则无法达到效果。后续可能会尝试其它方案来实现自定义,但目前还没有思路。

你可能感兴趣的:(再聊Win11 24H2 自定义桌面背景)