当前位置: 首页 > news >正文

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 pointersstd::vector<HWND>* windowList = reinterpret_cast<std::vector<HWND>*>(lParam);// Add the window handle to the vectorwindowList->push_back(hwnd);// Enumerate child windowsEnumChildWindows(hwnd, EnumWindowsProc, lParam);// Continue enumerationreturn 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 filterChangeWindowMessageFilterEx(hwnd, uMsg, MSGFLT_ALLOW, 0);// Send the message using SendMessageTimeoutDWORD_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 filterChangeWindowMessageFilterEx(hwnd, uMsg, MSGFLT_ALLOW, 0);// Send the message using SendMessageTimeoutDWORD_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 pointersstd::vector<HWND>* windowList = reinterpret_cast<std::vector<HWND>*>(lParam);// Add the window handle to the vectorwindowList->push_back(hwnd);// Enumerate child windowsEnumChildWindows(hwnd, EnumWindowsProc, lParam);// Continue enumerationreturn TRUE;
}int main()
{// Enumerate all windowsstd::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 threadsstd::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 windowsfor (HWND hwnd : windowList){// Create a thread and pass the window handle as an argumentthreads.emplace_back(SendMessageToWindow, hwnd, uMsg, wParam, lParam, &msgev);}// Wait for all threads to finishfor (std::thread& thread : threads){thread.join();}// Traverse and print the listprintf("List contents:\n");TraverseList(msgev);// Free the listFreeList(&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。

相关文章:

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

目录 前言 1 遍历窗口句柄列表 2 使用 SendMessageTimeout 发送延时消息 3 并行发送消息实现模拟广播消息 4 修改 UIPI 消息过滤器设置 5 托盘图标刷新的处理 6 完整代码和测试 本文属于原创文章&#xff0c;转载请注明出处&#xff1a; https://blog.csdn.net/qq_5907…...

PostgreSQL使用session_exec和file_fdw实现失败次数锁定用户策略

使用session_exec 、file_fdw以及自定义函数实现该功能。 缺陷&#xff1a;实测发现锁用户后&#xff0c;进去解锁特定用户。只能允许一次登陆&#xff0c;应该再次登陆的时候&#xff0c;触发函数&#xff0c;把之前的日志里的错误登陆的信息也计算到登录次数里了。而且foreig…...

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

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

Linux----防火墙之保存规则

一、关于iptables规则的保存 之前写的iptables的设置&#xff0c;但是都是临时生效的&#xff0c;一旦电脑重启&#xff0c;那么就会失效&#xff0c;如何永久保存&#xff0c;需要借助iptables-save命令&#xff0c;开机生效需要借助iptables-restore命令&#xff0c;并写入规…...

spring-orm:6 HibernateJpaVendorAdapter源码解析

版本 spring-orm:6.1.3 源码 org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter {// 旧版本Hibernate的方言类是否存在标识private static final boolean oldDialectsPresent Clas…...

php捕获Fatal error错误与异常处理

在php5的版本中&#xff0c;如果出现致命错误是无法被 try {} catch 捕获的&#xff0c;如下所示&#xff1a; <?phperror_reporting(E_ALL); ini_set(display_errors, on);try {hello(); } catch (\Exception $e) {echo $e->getMessage(); } 运行脚本&#xff0c;最终…...

PyCharm 调试过程中控制台 (Console) 窗口内运行命令 - 实时获取中间状态

PyCharm 调试过程中控制台 [Console] 窗口内运行命令 - 实时获取中间状态 1. yongqiang.py2. Debugger -> Console3. Show Python PromptReferences 1. yongqiang.py #!/usr/bin/env python # -*- coding: utf-8 -*- # yongqiang chengfrom __future__ import absolute_imp…...

MacBook Pro如何安装rust编程环境

安装过程分为以下几步&#xff1a; 1. 安装Homebrew。Homebrew是一个流行的MacOS的包管理器&#xff0c;可用于方便地安装各种软件。打开终端&#xff0c;运行以下命令&#xff1a; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD…...

SparkUI任务启动参数介绍(148个参数)

文章目录 SparkUI任务启动参数介绍&#xff08;148个参数&#xff09;1 spark.app.id: Spark 应用程序的唯一标识符。2 spark.app.initial.jar.urls: Spark 应用程序的初始 Jar 包的 URL。3 spark.app.name: Spark 应用程序的名称。4 spark.app.startTime: Spark 应用程序的启动…...

nginx 安装

Nginx 简介 nginx一种十分轻量级的http服务器一种高性能的HTTP和反向代理服务器,同时是一个IMAP/POP3/SMTP 代理服务器其中官网网站 安装Nginx 使用源码编辑安装 #提前安装相关工具软件包 yum -y install net-tools tar unzip gcc make pcre-devel openssl-devel httpd-too…...

手撕扩散模型(一)| 训练部分——前向扩散,反向预测代码全解析

文章目录 1 直接使用 核心代码2 工程代码实现2.1 DDPM2.2 训练 三大模型VAE&#xff0c;GAN&#xff0c; DIffusion扩散模型 是生成界的重要模型&#xff0c;但是最近一段时间扩散模型被用到的越来越多的&#xff0c;最近爆火的OpenAI的 Sora文生视频模型其实也是用了这种的方…...

linux 防火墙

防火墙分类 按保护范围划分 主机防火墙&#xff1a;服务服务为当前一台主机 网络防火墙&#xff1a;服务服务为防火墙一侧的局域网 按实现方式分类划分 硬件防火墙&#xff1a;在专用硬件级别实现部分功能的防火墙&#xff1b;另一部分基于软件的实现 如&#xff1a;华为&#…...

Go应用性能分析实战

Go很适合用来开发高性能网络应用&#xff0c;但仍然需要借助有效的工具进行性能分析&#xff0c;优化代码逻辑。本文介绍了如何通过go test benchmark和pprof进行性能分析&#xff0c;从而实现最优的代码效能。原文: Profiling Go Applications in the Right Way with Examples…...

MySQL的索引类型

目录 1. 主键索引 (PRIMARY KEY) 2. 唯一索引 (UNIQUE) 3. 普通索引 (INDEX) 4. 全文索引 (FULLTEXT) 5. 空间索引 (SPATIAL) 6. 组合索引 (COMPOSITE INDEX) 7. 前缀索引 (PREFIX INDEX) 8. 覆盖索引 (COVERING INDEX) 1. 主键索引 (PRIMARY KEY) 描述&#xff1a;表…...

picker选择器-年月日选择

从底部弹起的滚动选择器。支持五种选择器&#xff0c;通过mode来区分&#xff0c;分别是普通选择器&#xff0c;多列选择器&#xff0c;时间选择器&#xff0c;日期选择器&#xff0c;省市区选择器&#xff0c;默认是普通选择器。 学习一下日期选择器 平台差异说明 日期选择默…...

【LeetCode-494】目标和(回溯动归)

目录 LeetCode494.目标和 题目描述 解法1&#xff1a;回溯法 代码实现 解法2&#xff1a;动态规划 代码实现 LeetCode494.目标和 题目链接 题目描述 给定一个非负整数数组&#xff0c;a1, a2, ..., an, 和一个目标数&#xff0c;S。现在你有两个符号 和 -。对于数组中…...

力扣 188. 买卖股票的最佳时机 IV

题目来源&#xff1a;https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/description/ C题解&#xff1a;动态规划 思路同力扣 123. 买卖股票的最佳时机 III-CSDN博客&#xff0c;只是把最高2次换成k次。如果思路不清晰&#xff0c;可以将k从0写到4等找找规律…...

【Go语言】Go项目工程管理

GO 项目工程管理&#xff08;Go Modules&#xff09; Go 1.11 版本开始&#xff0c;官方提供了 Go Modules 进行项目管理&#xff0c;Go 1.13开始&#xff0c;Go项目默认使用 Go Modules 进行项目管理。 使用 Go Modules的好处是不再需要依赖 GOPATH&#xff0c;可以在任意位…...

美容小程序:让预约更简单,服务更贴心

在当今繁忙的生活节奏中&#xff0c;美容预约常常令人感到繁琐和疲惫。为了解决这个问题&#xff0c;许多美容院和SPA中心已经开始采用美容小程序来简化预约流程&#xff0c;并提供更加贴心的服务。在这篇文章中&#xff0c;我们将引导您了解如何制作一个美容小程序&#xff0c…...

【递归】:原理、应用与案例解析 ,助你深入理解递归核心思想

递归 1.基础简介 递归在计算机科学中&#xff0c;递归是一种解决计算问题的方法&#xff0c;其中解决方案取决于同一类问题的更小子集 例如 递归遍历环形链表 基本情况&#xff08;Base Case&#xff09;&#xff1a;基本情况是递归函数中最简单的情况&#xff0c;它们通常是递…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问&#xff08;基础概念问题&#xff09; 1. 请解释Spring框架的核心容器是什么&#xff1f;它在Spring中起到什么作用&#xff1f; Spring框架的核心容器是IoC容器&#…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

Qt的学习(一)

1.什么是Qt Qt特指用来进行桌面应用开发&#xff08;电脑上写的程序&#xff09;涉及到的一套技术Qt无法开发网页前端&#xff0c;也不能开发移动应用。 客户端开发的重要任务&#xff1a;编写和用户交互的界面。一般来说和用户交互的界面&#xff0c;有两种典型风格&…...

前端调试HTTP状态码

1xx&#xff08;信息类状态码&#xff09; 这类状态码表示临时响应&#xff0c;需要客户端继续处理请求。 100 Continue 服务器已收到请求的初始部分&#xff0c;客户端应继续发送剩余部分。 2xx&#xff08;成功类状态码&#xff09; 表示请求已成功被服务器接收、理解并处…...

怎么开发一个网络协议模块(C语言框架)之(六) ——通用对象池总结(核心)

+---------------------------+ | operEntryTbl[] | ← 操作对象池 (对象数组) +---------------------------+ | 0 | 1 | 2 | ... | N-1 | +---------------------------+↓ 初始化时全部加入 +------------------------+ +-------------------------+ | …...