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>


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.19。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/396404.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PostgreSQL里实现计算多个数字的排列组合

在进行排列组合的时候&#xff0c;每一次需要知道是否有重复的值&#xff0c;并过滤出已经排列过的值。这个可以创建支持可变参数的函数来实现。下边的函数用到了聚合判断&#xff0c;并且可变参数使用variadic标记的数组。 postgres<16.1>(ConnAs[postgres]:PID[188277…

SICTF Round#3 wp web

web hacker sql无列名注入&#xff1b; 提示查询username参数&#xff0c;flag在flag表中&#xff1b; 传参测试发现&#xff0c;union select 可用&#xff0c;空格被过滤可以使用/**/代替 &#xff0c;or也被过滤了且无法大小写、双写等绕过&#xff0c;导致无法查询flag表…

在线SM3 HMAC加密工具

在线HMAC加密工具提供一站式服务&#xff0c;支持MD5至SHA512、RIPEMD160及SM3等多种哈希算法&#xff0c;用户可便捷选择算法并生成安全的HMAC散列值&#xff0c;确保消息完整性与验证来源。适用于开发调试、网络安全测试及敏感数据处理场景。 在线HMAC加密 - BTool在线工具软…

【VSCode编写JavaScript】

VSCode编写JavaScript ■ 下载安装VSCode■ VSCode统一配置■ 格式化工具■ Tab size &#xff08;代码缩进 2个字符&#xff09;![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/7b79c59636f147c8b08a0fff37886e0a.png) ■ VSCode安装JS插件■ VSCode新建JS工程代码…

性能测试概述

1.性能测试介绍 好处: 有效的性能测试能给研发、运维团队提供有效的容量规划能力、系统风险识别、系统瓶颈识别、性能调优指导,保障尽量避免这些问题的发生。 例如: 假设:以下场景,不可用10分钟,带来的经济损失 天猫双十一峰值处理订单58.3万笔每秒 京东金融618战报…

fastApi笔记01-路径参数

路径参数 使用与 Python 格式化字符串相同的语法来声明路径"参数"或"变量" from fastapi import FastAPIapp FastAPI()app.get("/items/{item_id}") def read_item(item_id):return {"item_id": item_id} http://127.0.0.1:8000/i…

看小姐姐的效果棒极了,写了一个工具,逐帧解析视频转成图片,有没有带上商业思维的小伙伴一起研究下

一个突然的想法&#xff0c;促成了这个项目雏形。 原理是&#xff1a; 上传一个视频&#xff0c;自动将视频每一帧保存成图片 然后前端访问 就能实现如图效果 后端是python/flask 数据库mysql 前端uniapp 项目演示&#xff1a; xt.iiar.cn 后端代码如下&#xff1a; #学习…

蓝桥杯嵌入式STM32G431RBT6知识点(主观题部分)

目录 1 前置准备 1.1 Keil 1.1.1 编译器版本及微库 1.1.2 添加官方提供的LCD及I2C文件 1.2 CubeMX 1.2.1 时钟树 1.2.2 其他 1.2.3 明确CubeMX路径&#xff0c;放置芯片包 2 GPIO 2.1 实验1&#xff1a;LED1-LED8循环亮灭 ​编辑 2.2 实验2&#xff1a…

解决Edge浏览器,微博无法查看大图(Edge Image Viewer)

使用Edge浏览器浏览微博或其它带校验的图片时&#xff0c;会导致无法查看。 主要原因为Edge自带了一个Edge Image Viewer, 但是该图片查看器无法查看带校验数据的图片&#xff0c;所以导致查看时一片空白。 解决方法 地址栏输入 edge://flags/搜索 Edge Image Viewer选择 Disa…

c# #if 与 Conditional属性宏的区别

测试代码 using System; using System.Diagnostics;namespace ConsoleApp1 {public class TestClass{[Conditional("Debug1")]public static void Func1(){Console.WriteLine("Conditional 宏");}public static void Func2(){ #if Debug2Console.WriteLin…

【lesson59】线程池问题解答和读者写者问题

文章目录 线程池问题解答什么是单例模式什么是设计模式单例模式的特点饿汉和懒汉模式的理解STL中的容器是否是线程安全的?智能指针是否是线程安全的&#xff1f;其他常见的各种锁 读者写者问题 线程池问题解答 什么是单例模式 单例模式是一种 “经典的, 常用的, 常考的” 设…

【MATLAB源码-第140期】基于matlab的深度学习的两用户NOMA-OFDM系统信道估计仿真,对比LS,MMSE,ML。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 深度学习技术在无线通信领域的应用越来越广泛&#xff0c;特别是在非正交多址接入&#xff08;NOMA&#xff09;和正交频分复用&#xff08;OFDM&#xff09;系统中&#xff0c;深度学习技术被用来提高信道估计的性能和效率。…

Jmeter实现阶梯式线程增加的压测

安装相应jmeter 插件 1&#xff1a;安装jmeter 管理插件&#xff1a; 下载地址&#xff1a;https://jmeter-plugins.org/install/Install/&#xff0c;将下载下来的jar包放到jmeter文件夹下的lib/ext路径下&#xff0c;然后重启jmeter。 2&#xff1a;接着打开 选项-Plugins Ma…

【力扣 - 二叉树的最大深度】

题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 提示&#xff1a; 树中节点的数量在 [0, 10^4] 区间内。 -100 < Node.val < 100方法一&#xff1a;深度优先搜索 思路与算法 如…

面试经典150题——矩阵置零

​"Dream it. Wish it. Do it." - Unknown 1. 题目描述 2. 题目分析与解析 2.1 思路一——暴力求解 思路一很简单&#xff0c;就是尝试遍历矩阵的所有元素&#xff0c;如果发现值等于0&#xff0c;就把当前行与当前列的值分别置为0。同时我们需要注意&#xff0c;…

5G车载路由器引领无人驾驶车联网应用

随着无人驾驶技术的不断发展&#xff0c;车联网正逐渐成为实现智能交通的重要组成部分。5G车载路由器将在车联网的应用中起到至关重要的作用&#xff0c;它能够满足无人驾驶应用的低时延、高速率和实时控制等需求&#xff0c;进一步推动无人驾驶车联网技术。 5G路由器具备低时延…

VUE3 中导入Visio 图形

微软的Visio是一个功能强大的图形设计工具&#xff0c;它能够绘制流程图&#xff0c;P&ID&#xff0c;UML 类图等工程设计中常用的图形。它要比其它图形设计软件要简单许多。以后我的博文中将更多地使用VISO 来绘制图形。之前我一直使用的是corelDraw。 Visio 已经在工程设…

静态库、动态库制作

库介绍 静态库和动态库是软件开发中常用的两种库文件形式。静态库&#xff08;static library&#xff09;是在编译时被链接到程序中的库&#xff0c;它包含了一组预编译的目标代码&#xff0c;这些代码将直接复制到最终的可执行文件中。静态库的优点是简单易用&#xff0c;只…

搜狗的workflow的简单使用

workflow是一个网络库&#xff0c;是一个基于C在在线服务引擎 GitHub官网 运行hello world 1,创建一个server&#xff0c;构造函数入参传入一个入参是task的lamda函数&#xff0c;函数的内容会拿到response&#xff0c;并且可以在response中写body 2、server启动&#xff0c;…

普中51单片机学习(十一)

独立按键 独立按键原理 按键在闭合和断开时触电存在抖动现象 硬件消抖电路如下 实验代码 #include "reg52.h" typedef unsigned char u8; typedef unsigned int u16;void delay(u16 i) {while(i--); } sbit ledP2^0; sbit k1P3^1;void keypro() {if(k10){delay(1…