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

news2025/1/19 23:18:51

目录

前言

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。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

运筹系列89:anylogic仿真软件入门

1. agent-based simulation 这里概述一下help文档中Supply chain GIS model例子的要点&#xff1a;触发事件的agent和执行任务的agent。 在这个案例中&#xff0c;触发事件的agent是retailer&#xff0c;不断有订单生成&#xff1b;而执行任务的agent是vehicle&#xff0c;不断…

成功解决:× python setup.py egg_info did not run successfully.

执行pip install -r requirements.txt报错 错误信息如下&#xff1a; error: subprocess-exited-with-error python setup.py egg_info did not run successfully.│ exit code: 1╰─> [93 lines of output]解决办法&#xff1a; 更新 setuptools 和 pip pip install -…

比特浏览器bit_selenium3bit_selenium4使用

bit_selenium3 from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.keys import Keys from selenium.webdriver.chrome.options import Options from bit_api import *# /browser/open 接口会返回 selen…

我不允许你还不知道!这些求职常用黑话!

我不允许你还不知道&#xff01;这些求职常用黑话&#xff01; 一. HC、BG、BU、JD、OD、OT&#xff08;公司相关&#xff09;二、岗位相关1、base2、JD3、RD4、QA5、PM6、PR7、PD 三、求职或者薪资1、OC/意向书2、开奖3、泡池子4、保温5、A&#xff08;argue&#xff09;6、总…

数据库的备份模式(完全备份,增量备份,差异备份)

数据库的备份 备份原因 数据的丢失 数据的删除 备份目标 数据的一致性 数据的可用性 备份技术 物理备份/冷备份 直接复制数据库文件&#xff0c;适用于大型数据库环境&#xff0c;不受存储引擎的限制&#xff0c;但不能恢复到不同的MySQL版本。 常用的冷备份工具 ta…

电阻负载柜是什么?

电阻负载柜是用于模拟实际负载的设备&#xff0c;主要用于电力系统、电气设备和电子设备的测试、调试和维护。它通过调节电阻值来改变负载的大小&#xff0c;以满足不同场合的需求。电阻负载柜在电力系统、电气设备和电子设备的测试、调试和维护过程中发挥着重要作用。 电阻负载…

BI 数据分析,数据库,Office,可视化,数据仓库

AIGC ChatGPT 职场案例 AI 绘画 与 短视频制作 PowerBI 商业智能 68集 Mysql 8.0 54集 Oracle 21C 142集 Office 2021实战应用 Python 数据分析实战&#xff0c; ETL Informatica 数据仓库案例实战 51集 Excel 2021实操 100集&#xff0c; Excel 2021函数大全 80集 Excel 2021…

外贸人大部分都复工了吧

这几天是属于国家规定的节后上班时间&#xff0c;估计大部分人都已经开始复工了。作为粤西地区小伙伴中的一员&#xff0c;表示虽然身在广州&#xff0c;心却还在高州&#xff0c;毕竟年例在这些天才刚刚开始&#xff0c;我们那边每年最热闹的时候就是年例了&#xff01; 由于…

XCharts——Unity上最好用的免费开源图表插件!(一)基本介绍

只讲实用干货&#xff01;&#xff01;&#xff01;&#xff08;过于细节的或是未提及到的可直接问&#xff09; 目录 XCharts介绍 插件简介 插件下载 XCharts基本使用 类型介绍 1.折线图&#xff08;LineChart&#xff09; 2.柱形图&#xff08;BarChart&#xff09; …

IBM Spectrum LSF Process Manager 在共享分布式计算环境中运行和管理业务关键工作流程

IBM Spectrum LSF Process Manager 设计、记录和运行复杂的计算工作流 亮点 ● 快速创建复杂的分布式工作流 ● 开发可重复的最佳实践 ● 自信地运行关键工作流程 ● 提高流程可靠性 IBM Spectrum LSF Process Manager 使您能够设计和自动化计算或分析流程&#xff0c; 捕获…

力扣(LeetCode)数据结构练习题(2)

今天又写了两道关于链表的练习题&#xff0c;来给大家分享一下。巩固一下上一篇学到的链表知识&#xff0c;题目可以然我们更清楚的认识链表。 目录 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表 给你单链表的头结点 head &#xff0c;请…

论文阅读-基于动态权重的一致性哈希微服务负载均衡优化

论文名称&#xff1a;基于动态权重的一致性哈希微服务负载均衡优化 摘要 随着互联网技术的发展&#xff0c;互联网服务器集群的负载能力正面临前所未有的挑战。在这样的背景下&#xff0c;实现合理的负载均衡策略变得尤为重要。为了达到最佳的效率&#xff0c;可以利用一致性…

从理论到实践:车间精益生产培训的全面应用指南

精益生产培训在车间的应用通常会通过以下几个步骤进行实践&#xff1a; 理论培训&#xff1a;首先&#xff0c;需要对车间的员工进行精益生产的基本理论培训&#xff0c;让他们理解精益生产的核心理念&#xff0c;比如价值流、流程优化、减少浪费、持续改进等。 现场诊断&am…

刚开工,就用Python兼职赚了5w!

前言 今天是节后上班第一天&#xff0c;祝大家开工大吉&#xff01; 先说个好消息&#xff1a;每年春节后&#xff0c;会迎来Python圈内兼职接单的小高潮。近期可以很轻松地&#xff0c;接到爬虫类和数据分析类的私活&#xff0c;需求大报酬高。 往年春节开工后的几天&#…

Spring Boot与LiteFlow:轻量级流程引擎的集成与应用含完整过程

点击下载《Spring Boot与LiteFlow&#xff1a;轻量级流程引擎的集成与应用含完整过程》 1. 前言 本文旨在介绍Spring Boot与LiteFlow的集成方法&#xff0c;详细阐述LiteFlow的原理、使用流程、步骤以及代码注释。通过本文&#xff0c;读者将能够了解LiteFlow的特点&#xff…

鸿蒙原生应用元服务实战-Serverless华为账户认证登录需尽快适配

一、ArkTS\API9&#xff0c;服务器端基于serverless开发的应用与元服务华为账号注册登录功能暂时是不支持的 二、3月1日后的审核要求 3月1日的时间是快到了。 三、会导致的结果 使用了ArkTS\API9&#xff0c;服务器端基于serverless开发的应用与元服务&#xff0c;如果要…

devc++跑酷小游戏底层讲解

以3.0.0为例 采集按键&#xff1a; 我们需要一个函数来采集用户按下的按键以便我们执行相应的代码&#xff0c;不能有回显&#xff08;输入的字符会显示在控制台程序上&#xff09;&#xff0c;不用回车也可以读取到 cin&#xff0c;scanf&#xff1a; 输入的类型为char&am…

ETL数据集成工具DataX、Kettle、ETLCloud特点对比

ETL数据集成工具 对于数据仓库&#xff0c;大数据集成类应用&#xff0c;通常会采用ETL工具辅助完成。ETL&#xff0c;是英文 Extract-Transform-Load 的缩写&#xff0c;用来描述将数据从来源端经过抽取(extract) 、交互转换(transform) 、加载(load)至的端的过程当前的很多应…

算法练习-每日气温【单调栈】(思路+流程图+代码)

难度参考 难度&#xff1a;困难 分类&#xff1a;单调栈 难度与分类由我所参与的培训课程提供&#xff0c;但需 要注意的是&#xff0c;难度与分类仅供参考。且所在课程未提供测试平台&#xff0c;故实现代码主要为自行测试的那种&#xff0c;以下内容均为个人笔记&#xff0c;…

npm run dev运行出现NODE_OPTIONS=--max_old_space_size=4096 vite --mode dev --host?

问题描述 PS E:\AWorkDataease\DataEase\core\core-frontend> npm run dev dataease0.0.0 dev NODE_OPTIONS–max_old_space_size4096 vite --mode dev --host 0.0.0.0 ‘NODE_OPTIONS’ 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 解决方案 遇到…