Windows 使设置更改立即生效——并行发送广播消息

目录

前言

1 遍历窗口句柄列表

2 使用 SendMessageTimeout 发送延时消息

3 并行发送消息实现模拟广播消息

4 修改 UIPI 消息过滤器设置

5 托盘图标刷新的处理

6 完整代码和测试


本文属于原创文章,转载请注明出处:

https://blog.csdn.net/qq_59075481/article/details/136175227

前言

在 Windows 上使得设置的更改立即生效的方法不唯一。本文分析全局发送 WM_SETTINGCHANGE 消息来通知系统设置发生更改这一方法。该方法适用范围比较广泛,但有时候还是需要结合 SHChangeNotify 等函数来刷新更改,甚至还有一部分设置更改就只能重启计算机生效。

我们知道如果使用 HWND_BROADCAST 广播消息的话,虽然会通知所有顶级窗口,只消息窗口等窗口,但是该消息的处理在任意一个窗口处理后就会立即终止并返回,消息接收方有权不处理消息,我们并不容易获取消息处理的详细情况,并且他不能针对子窗口等窗口。

所以我们肯定要自己去实现自己的广播消息的方式。

1 遍历窗口句柄列表

我们首先通过 EnumWindows 和 EnumChildWindows 以递归的方式遍历,获取所有窗口句柄列表。

// Callback function for window enumeration
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
    // Cast the lParam to a vector of HWND pointers
    std::vector<HWND>* windowList = reinterpret_cast<std::vector<HWND>*>(lParam);

    // Add the window handle to the vector
    windowList->push_back(hwnd);

    // Enumerate child windows
    EnumChildWindows(hwnd, EnumWindowsProc, lParam);

    // Continue enumeration
    return TRUE;
}

// Enumerate all windows
std::vector<HWND> windowList;
EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&windowList));

2 使用 SendMessageTimeout 发送延时消息

SendMessageTimeout 的好处是它可以比 SendMessage 多出来等待时间这个限制,SendMessage 会阻滞调用线程直到接收消息的线程处理完消息,所以不能用 SendMessage 同时发送消息到多个窗口;它比 PostMessage 也有优势,PostMessage 立即返回,所以在不关心处理结果的情况下我们可能选择 PostMessage。

SendMessageTimeout 函数的声明如下:

LRESULT SendMessageTimeoutW(
  HWND       hWnd,
  UINT       Msg,
  WPARAM     wParam,
  LPARAM     lParam,
  UINT       fuFlags,
  UINT       uTimeout,
  PDWORD_PTR lpdwResult
);

我们使用该函数就可以实现发送消息并等待一定时间,MSDN 说明如下:

https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-sendmessagetimeouta

3 并行发送消息实现模拟广播消息

单线程似乎并不能满足我们同时请求多个窗口的需求。所以,我们可以将处理放置在多线程任务中。并将消息处理的结果使用链表来管理。

数据结构:

typedef struct __STMO_MSGEVENT {
    SIZE_T      nCount;
    SIZE_T      nSize;
    HWND        hWnd;
    BOOL        isActive;
    LRESULT     lResult;
    DWORD_PTR   lpStatus;
    struct __STMO_MSGEVENT* pNext[1];
} STMO_MSGEVENT, * LPSTMO_MSGEVENT;

其中,isActive 表示接收消息的线程是否在规定时间处理了消息,nSize 表示结点总数,头结点的nCount 表示所有窗口个数。 

线程执行函数:

// Function to send message to a window using SendMessageTimeout
void SendMessageToWindow(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LPSTMO_MSGEVENT* lpstmoMsg)
{

    // Set the timeout value (in milliseconds)
    DWORD timeout = 5000;

    // Call ChangeWindowsMessageFilterEx to modify the message filter
    ChangeWindowMessageFilterEx(hwnd, uMsg, MSGFLT_ALLOW, 0);

    // Send the message using SendMessageTimeout
    DWORD_PTR lpStatus = 0;
    LRESULT   lResult = 0;
    lResult = SendMessageTimeoutW(hwnd, uMsg, wParam, lParam, SMTO_ABORTIFHUNG, timeout, &lpStatus);
    bool oldCount = lResult > 0 ? true : false;

    AddNode(lpstmoMsg, hwnd, oldCount, lResult, lpStatus);

}

4 修改 UIPI 消息过滤器设置

从 Vista 引入的消息安全机制将限制低完整级别程序向高完整级别程序发送消息的过程,此时可以使用 ChangeWindowMessageFilterEx 来允许特定的消息通过 UIPI。

// Call ChangeWindowsMessageFilterEx to modify the message filter
ChangeWindowMessageFilterEx(hwnd, uMsg, MSGFLT_ALLOW, 0);

指定 MSGFLT_ALLOW 以允许消息通过 UIPI。

5 托盘图标刷新的处理

托盘图标刷新需要单独模拟发送 TaskbarCreated 消息。在任务栏重建时,会向系统中所有窗口广播 TaskbarCreated 消息,在负责通知栏图标的线程接收到消息时,接收消息的线程按照规范应该调用 Shell_NotifyIcon 重新创建通知栏图标。

TaskbarCreated 字符串消息是通过 RegisterWindowMessage 在系统级别注册的,因为该函数内部封装了 NtUserRegisterWindowMessage 的调用,这是一个用户态/内核态切换过程。通过逆向分析,我们了解到 RegisterWindowMessage 的返回值是 Global ATOM 数值的一部分,系统内核在全局维护一个原子表 RTL_ATOM_TABLE 来通过哈希桶管理一些窗口进程交互需要的信息。

所以,在系统重启前,TaskbarCreated 消息对应的 IntAtom 索引号保持不变,该值与 explorer 的重启无关。

调用 RegisterWindowMessage 并指定 "TaskbarCreated" 字符串将首先检索消息是否已经在 ATOM 表中注册。由于该消息在系统初始化时已经注册,所以,执行过程只会增加该消息的引用计数而不会重建消息。

所以,我们可以利用 RegisterWindowMessage 作为一个官方支持的技巧,轻松获取"TaskbarCreated"消息的窗口消息码,调用在非消息线程/非窗口进程/非系统进程就可以调用成功。所以该消息不会失败,除非交互系统未能够完成初始化。

// 该字符串消息使用系统原子表,返回值是 IntAtom 编号,
// 一般情况下在计算机重启时才刷新。
UINT QueryTaskbarCreateMsg()
{
    return RegisterWindowMessageW(L"TaskbarCreated");
}

6 完整代码和测试

#include <Windows.h>
#include <iostream>
#include <vector>
#include <thread>

#pragma comment(lib, "User32.lib")

typedef struct __STMO_MSGEVENT {
    SIZE_T      nCount;
    HWND        hWnd;
    BOOL        isActive;
    LRESULT     lResult;
    DWORD_PTR   lpStatus;
    struct __STMO_MSGEVENT* pNext[1];
} STMO_MSGEVENT, * LPSTMO_MSGEVENT;

LPSTMO_MSGEVENT CreateNode(HWND hWnd) {
    LPSTMO_MSGEVENT newNode = (LPSTMO_MSGEVENT)malloc(sizeof(STMO_MSGEVENT));
    newNode->nCount = 0;
    newNode->hWnd = hWnd;
    newNode->isActive = FALSE;
    newNode->lResult = 0;
    newNode->lpStatus = 0;
    return newNode;
}

void AddNode(LPSTMO_MSGEVENT* pList, HWND hWnd, LRESULT lResult, DWORD_PTR lpStatus) {
    LPSTMO_MSGEVENT newNode = CreateNode(hWnd);
    newNode->isActive = (lResult > 0);
    newNode->lResult = lResult;
    newNode->lpStatus = lpStatus;
    newNode->pNext[0] = *pList;
    *pList = newNode;
    (*pList)->nCount++;
}

void SendMessageToWindow(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LPSTMO_MSGEVENT* lpstmoMsg)
{
    DWORD timeout = 5000;
    ChangeWindowMessageFilterEx(hwnd, uMsg, MSGFLT_ALLOW, 0);
    DWORD_PTR lpStatus = 0;
    LRESULT lResult = SendMessageTimeoutW(hwnd, uMsg, wParam, lParam, SMTO_ABORTIFHUNG, timeout, &lpStatus);

    //LPSTMO_MSGEVENT newNode = CreateNode(hwnd);
    //newNode->isActive = (lResult > 0);
   // newNode->lResult = lResult;
    //newNode->lpStatus = lpStatus;

    AddNode(lpstmoMsg, hwnd, lResult, lpStatus);
}

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
    std::vector<HWND>* windowList = reinterpret_cast<std::vector<HWND>*>(lParam);
    windowList->push_back(hwnd);
    EnumChildWindows(hwnd, EnumWindowsProc, lParam);
    return TRUE;
}

void TraverseList(LPSTMO_MSGEVENT pList) {
    LPSTMO_MSGEVENT pNode = pList;
    while (pNode != nullptr) {
        std::cout << "hWnd: " << pNode->hWnd << std::endl;
        std::cout << "isActive: " << (pNode->isActive ? "true" : "false") << std::endl;
        std::cout << "lResult: " << pNode->lResult << "  lpStatus: " << pNode->lpStatus << std::endl;
        pNode = pNode->pNext[0];
    }
}

void FreeList(LPSTMO_MSGEVENT* pList) {
    LPSTMO_MSGEVENT pNode = *pList;
    while (pNode != nullptr) {
        LPSTMO_MSGEVENT temp = pNode;
        pNode = pNode->pNext[0];
        free(temp);
    }
    *pList = nullptr;
}

// 该字符串消息使用系统原子表,返回值是 IntAtom 编号,
// 一般情况下在计算机重启时才刷新。
UINT QueryTaskbarCreateMsg()
{
    return RegisterWindowMessageW(L"TaskbarCreated");
}

int main()
{
    std::vector<HWND> windowList;
    EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&windowList));
    SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, SPI_SETNONCLIENTMETRICS, 0);
    std::vector<std::thread> threads;
    UINT uMsg = WM_SETTINGCHANGE;
    WPARAM wParam = SPI_SETNONCLIENTMETRICS;
    LPARAM lParam = (LPARAM)0;
    LPSTMO_MSGEVENT msgev = nullptr;

    for (HWND hwnd : windowList)
    {
        threads.emplace_back(SendMessageToWindow, hwnd, uMsg, wParam, lParam, &msgev);
    }

    for (std::thread& thread : threads)
    {
        thread.join();
    }

    std::cout << "List contents:\n";
    TraverseList(msgev);

    FreeList(&msgev);

    return 0;
}

 

#include <Windows.h>
#include <iostream>
#include <vector>
#include <thread>


typedef struct __STMO_MSGEVENT {
    SIZE_T      nCount;
    SIZE_T      nSize;
    HWND        hWnd;
    BOOL        isActive;
    LRESULT     lResult;
    DWORD_PTR   lpStatus;
    struct __STMO_MSGEVENT* pNext[1];
} STMO_MSGEVENT, * LPSTMO_MSGEVENT;


LPSTMO_MSGEVENT CreateNode(HWND hWnd, BOOL isActive, LRESULT lResult, DWORD_PTR lpStatus, SIZE_T nCount) {
    LPSTMO_MSGEVENT newNode = (LPSTMO_MSGEVENT)malloc(sizeof(STMO_MSGEVENT));
    newNode->nCount = nCount;
    newNode->nSize = 1;
    newNode->hWnd = hWnd;
    newNode->isActive = isActive;
    newNode->lResult = lResult;
    newNode->lpStatus = lpStatus;
    return newNode;
}

LPSTMO_MSGEVENT InitializeList(SIZE_T initialSize) {
    LPSTMO_MSGEVENT newList = (LPSTMO_MSGEVENT)malloc(sizeof(STMO_MSGEVENT) + initialSize * sizeof(LPSTMO_MSGEVENT));
    if (newList != NULL) {
        memset(newList, 0, sizeof(LPSTMO_MSGEVENT) * initialSize + sizeof(STMO_MSGEVENT));
        newList->nCount = 1;
        newList->nSize = initialSize;
        newList->hWnd = NULL;
        newList->isActive = FALSE;
        newList->lResult = 0;
        newList->lpStatus = 0;
        return newList;
    }
    else {
        printf("Failed to allocate memory for the list.\n");
        return NULL;
    }
}

void AddNode(LPSTMO_MSGEVENT* pList, HWND hWnd, BOOL isActive, LRESULT lResult, DWORD_PTR lpStatus) {
    LPSTMO_MSGEVENT newNode = CreateNode(hWnd, isActive, lResult, lpStatus, (*pList)->nCount);  // 也可以固定为 nSize,这只是一个记录
    if ((*pList)->nCount < (*pList)->nSize) {
        (*pList)->pNext[(*pList)->nCount] = newNode;
        (*pList)->nCount++;
    }
    else {
        SIZE_T newSize = (*pList)->nSize * 2;
        LPSTMO_MSGEVENT newList = (LPSTMO_MSGEVENT)realloc(*pList, sizeof(STMO_MSGEVENT) + newSize * sizeof(LPSTMO_MSGEVENT));
        if (newList != NULL) {
            memset(newList, 0, sizeof(LPSTMO_MSGEVENT) * newSize + sizeof(STMO_MSGEVENT));
            *pList = newList;
            (*pList)->pNext[(*pList)->nCount] = newNode;
            (*pList)->nCount++;
            (*pList)->nSize = newSize;
        }
        else {
            free(newNode);
            printf("Failed to allocate memory for the new node.\n");
        }
    }
}

void TraverseList(LPSTMO_MSGEVENT pList) {
    for (SIZE_T i = 0; i < pList->nCount; i++) {
        LPSTMO_MSGEVENT pNode = pList->pNext[i];
        std::cout << "index: 0x" << i << std::endl;
        std::cout << "hWnd: " << pNode->hWnd << std::endl;
        std::cout << "isActive: " << (pNode->isActive ? "true" : "false") << std::endl;
        std::cout << "lResult: " << pNode->lResult << "  lpStatus: " << pNode->lpStatus << std::endl;
    }
}

void FreeList(LPSTMO_MSGEVENT* pList) {
    for (SIZE_T i = 0; i < (*pList)->nCount; i++) {
        free((*pList)->pNext[i]);
    }
    free(*pList);
    *pList = NULL;
}

// Function to send message to a window using SendMessageTimeout
void SendMessageToWindow(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LPSTMO_MSGEVENT* lpstmoMsg)
{

    // Set the timeout value (in milliseconds)
    DWORD timeout = 5000;

    // Call ChangeWindowsMessageFilterEx to modify the message filter
    ChangeWindowMessageFilterEx(hwnd, uMsg, MSGFLT_ALLOW, 0);

    // Send the message using SendMessageTimeout
    DWORD_PTR lpStatus = 0;
    LRESULT   lResult = 0;
    lResult = SendMessageTimeoutW(hwnd, uMsg, wParam, lParam, SMTO_ABORTIFHUNG, timeout, &lpStatus);
    bool oldCount = lResult > 0 ? true : false;

    AddNode(lpstmoMsg, hwnd, oldCount, lResult, lpStatus);

}

// 该字符串消息使用系统原子表,返回值是 IntAtom 编号,
// 一般情况下在计算机重启时才刷新。
UINT QueryTaskbarCreateMsg()
{
    return RegisterWindowMessageW(L"TaskbarCreated");
}

// Callback function for window enumeration
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
    // Cast the lParam to a vector of HWND pointers
    std::vector<HWND>* windowList = reinterpret_cast<std::vector<HWND>*>(lParam);

    // Add the window handle to the vector
    windowList->push_back(hwnd);

    // Enumerate child windows
    EnumChildWindows(hwnd, EnumWindowsProc, lParam);

    // Continue enumeration
    return TRUE;
}

int main()
{
    // Enumerate all windows
    std::vector<HWND> windowList;
    EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&windowList));

    // 要刷新任务栏的话用这个消息即可
    UINT uTaskbarMsg = QueryTaskbarCreateMsg();
    std::cout << "TaskbarCreateMsgAtom: 0x" << std::hex << uTaskbarMsg << std::endl;
    HWND hWnd = FindWindowW(L"Shell_TrayWnd", nullptr);

    // Create a vector of threads
    std::vector<std::thread> threads;

    UINT uMsg = WM_SETTINGCHANGE; // uTaskbarMsg;
    WPARAM wParam = (WPARAM)0;    // hWnd;
    LPARAM lParam = 0;

    LPSTMO_MSGEVENT msgev = InitializeList(1024);

    // Launch threads to send messages to windows
    for (HWND hwnd : windowList)
    {
        // Create a thread and pass the window handle as an argument
        threads.emplace_back(SendMessageToWindow, hwnd, uMsg, wParam, lParam, &msgev);
    }

    // Wait for all threads to finish
    for (std::thread& thread : threads)
    {
        thread.join();
    }

    // Traverse and print the list
    printf("List contents:\n");
    TraverseList(msgev);

    // Free the list
    FreeList(&msgev);

    return 0;
}

P.S. : 为了便于测试,我们选用了任务栏重建时的 TaskbarCreated 消息作为示例(这只会通知更新托盘图标),如果要全局通知系统设置更改应该用 WM_SETTINGCHANGE 消息,但由于 WM_SETTINGCHANGE 是一瞬间的,不方便截图,所以我只给出了 Taskbar Create 消息测试的结果。但是在上面的代码中,我使用  WM_SETTINGCHANGE ,并注释了 Taskbar Create 消息。

操作需要提升管理员权限,否则部分窗口可能因为 UIPI 过滤而无法接收到消息

我们开起了一个托盘图标窗口程序,用于在接收到 TASKBAR_CREATED 消息时弹出消息框。

使用上文代码工具广播消息时,程序成功受到广播的信息:

广播结果截图

测试程序的图标

本文属于原创文章,转载请注明出处:

https://blog.csdn.net/qq_59075481/article/details/136175227

文章发布于:2024.02.19,更新于:2024.02.20。

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Windows 操作系统中,修改环境变量后需要重新打开命令行窗口或者重新启动计算机才能使修改生效。如果想要在 Python 脚本中实现修改环境变量后立即生效的功能,可以使用以下方法: 1. 使用 `os.environ` 对象修改环境变量,并且使用 `ctypes` 模块调用 `SendMessage` API 发送消息Windows Explorer 进程,通知它更新环境变量。示例代码如下: ``` import os import ctypes # 修改环境变量 PATH new_path = os.environ['PATH'] + ';C:\\NewFolder' os.environ['PATH'] = new_path # 发送消息Windows Explorer 进程,通知它更新环境变量 HWND_BROADCAST = 0xFFFF WM_SETTINGCHANGE = 0x1A SMTO_ABORTIFHUNG = 0x0002 result = ctypes.windll.user32.SendMessageTimeoutW( HWND_BROADCAST, WM_SETTINGCHANGE, 0, 'Environment', SMTO_ABORTIFHUNG, 5000, None ) if result == 0: print('Failed to update environment variables') else: print('Environment variables updated successfully') ``` 在代码中,首先使用 `os.environ` 对象修改环境变量 PATH,然后使用 `ctypes` 模块调用 `SendMessage` API 发送消息Windows Explorer 进程,通知它更新环境变量。如果更新成功,`SendMessage` API 将返回一个非零值,否则返回零。 2. 使用 `winreg` 模块修改系统注册表中的环境变量,并且使用 `win32api` 模块调用 `SendMessage` API 发送消息Windows Explorer 进程,通知它更新环境变量。示例代码如下: ``` import winreg import win32api # 修改环境变量 PATH with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as hkey: with winreg.OpenKeyEx(hkey, r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 0, winreg.KEY_WRITE) as key: new_path = winreg.QueryValueEx(key, 'PATH')[0] + ';C:\\NewFolder' winreg.SetValueEx(key, 'PATH', 0, winreg.REG_EXPAND_SZ, new_path) # 发送消息Windows Explorer 进程,通知它更新环境变量 HWND_BROADCAST = 0xFFFF WM_SETTINGCHANGE = 0x1A SMTO_ABORTIFHUNG = 0x0002 result = win32api.SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, 'Environment', SMTO_ABORTIFHUNG, 5000) if result == 0: print('Failed to update environment variables') else: print('Environment variables updated successfully') ``` 在代码中,首先使用 `winreg` 模块修改系统注册表中的环境变量 PATH,然后使用 `win32api` 模块调用 `SendMessage` API 发送消息Windows Explorer 进程,通知它更新环境变量。如果更新成功,`SendMessage` API 将返回一个非零值,否则返回零。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涟幽516

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值