C++进程遍历的几种方法

在应用层下,进程遍历有多种方式,这里介绍几种常用的方式:进程快照、NtQuerySystemInformation、EnumProcesses函数、WMI等。

在C#中Process类提供了一个GetProcesses()函数,这个函数内部就是调用的NtQuerySystemInformation进行获取。

进程快照

这种方式比较简单方便。主要涉及以下几个函数

CreateToolhelp32Snapshot(TlHelp32.h)函数,获取进程信息为指定的进程、进程使用的堆、模块、线程建立一个快照。

函数声明如下:

1 HANDLE
2 WINAPI
3 CreateToolhelp32Snapshot(
4     DWORD dwFlags,
5     DWORD th32ProcessID
6     );

参数说明:

dwFlags

含义

TH32CS_INHERIT

0x80000000

指示快照句柄是可继承的。

TH32CS_SNAPALL

包括系统中的所有进程和线程,以及 th32ProcessID 中指定的进程的堆和模块。 等效于指定使用 OR 操作 (“|”组合的TH32CS_SNAPHEAPLIST、TH32CS_SNAPMODULE、TH32CS_SNAPPROCESS和TH32CS_SNAPTHREAD值) 。

TH32CS_SNAPHEAPLIST

0x00000001

包括快照 th32ProcessID 中指定的进程的所有堆。 若要枚举堆,请参阅 Heap32ListFirst。

TH32CS_SNAPMODULE

0x00000008

包括快照 th32ProcessID 中指定的进程的所有模块。 若要枚举模块,请参阅 Module32First。 如果函数失败并 出现ERROR_BAD_LENGTH,请重试该函数,直到成功。

TH32CS_SNAPMODULE32

0x00000010

从 64 位进程调用时,包括快照中 th32ProcessID 中指定的进程的所有 32 位模块。 此标志可以与 TH32CS_SNAPMODULE 或 TH32CS_SNAPALL结合使用。 如果函数失败并 出现ERROR_BAD_LENGTH,请重试该函数,直到成功。

TH32CS_SNAPPROCESS

0x00000002

包括系统中快照中的所有进程。 若要枚举进程,请参阅 Process32First。

TH32CS_SNAPTHREAD

0x00000004

包括快照系统中的所有线程。 若要枚举线程,请参阅 Thread32First。

若要标识属于特定进程的线程,请在枚举线程时将其进程标识符与 THREADENTRY32 结构的 th32OwnerProcessID 成员进行比较。

枚举进程的话使用TH32CS_SNAPPROCESS就可以了,也可以枚举其它的内容,可以自行尝试。

th32ProcessID

指定将要快照的进程ID。如果该参数为零,则表示快照当前进程。该参数只有在设置了TH32CS_SNAPHEAPLIST或者

TH32CS_SNAPMODULE后才有效,在其他情况下应忽略该参数,快照所有的进程。

返回值:

成功,返回快照句柄

失败,返回INVALID_HANDLE_VALUE

注意,进程枚举完成后需要CloseHandle关闭快照句柄。

Process32First函数,用于检索系统快照中遇到的第一个进程信息。在调用CreateToolhelp32Snapshot成功后,就可以调用这个函数来获取第一个进程信息。函数声明如下:

1 BOOL
2 WINAPI
3 Process32FirstW(
4     HANDLE hSnapshot,
5     LPPROCESSENTRY32W lppe
6     );

参数说明:

hSnapshot

CreateToolhelp32Snapshot函数返回的快照句柄

lppe

指向PROCESSENTRY32结构的指针。

PROCESSENTRY32结构定义如下:

 1 typedef struct tagPROCESSENTRY32W2 {3     DWORD   dwSize;                     // size4     DWORD   cntUsage;                   // obselete(keep 0)5     DWORD   th32ProcessID;              // this process6     ULONG_PTR th32DefaultHeapID;        // obselete(keep 0)7     DWORD   th32ModuleID;               // associated exe8     DWORD   cntThreads;                 // The number of execution threads started by the process.9     DWORD   th32ParentProcessID;        // this process's parent process
10     LONG    pcPriClassBase;             // Base priority of process's threads
11     DWORD   dwFlags;                    // obselete(keep 0)
12     WCHAR   szExeFile[MAX_PATH];        // Path
13 } PROCESSENTRY32W;

返回值:

TRUE,进程列表的第一个条目已经复制到缓冲区

FALSE,失败,调用GetLastError()获取错误信息

Process32Next函数,检索系统快照中记录的下一个进程信息。这个函数的声明和Process32First一样,使用方法也一样,这是在第一个进程的基础上,继续向下遍历 。

返回值:

TRUE,进程列表的下一个条目已经复制到缓冲区

FALSE,如果不存在任何进程或者快照不包含进程信息,则GetLastError函数会返回ERROR_NO_MORE_FILES错误值。

遍历流程比较简单:

1、调用CreateToolhelp32Snapshot创建进程快照,设置标志为TH32CS_SNAPPROCESS

2、调用Process32First函数获取第一个进程信息

3、使用一个while循环来调用Process32Next函数获取后面的进程信息

4、当Process32Next返回FALSE,退出循环

5、关闭快照句柄

示例代码如下:

 1 int main()2 {3     PROCESSENTRY32 pe32{};4     pe32.dwSize = sizeof(PROCESSENTRY32);5 6     HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);7 8     if (INVALID_HANDLE_VALUE == hProcessSnap)9     {
10         return 0;
11     }
12 
13     BOOL bRet = Process32First(hProcessSnap, &pe32);
14 
15     while (bRet)
16     {
17         //对进程进行操作
18 
19         bRet = Process32Next(hProcessSnap, &pe32);
20     }
21     CloseHandle(hProcessSnap);
22     return 0;
23 }

NtQuerySystemInformation

NtQuerySystemInformation用于检索指定的系统信息,它可以检索多种系统信息,它的类型在SYSTEM_INFORMATION_CLASS 枚举中定义。我们这里只演示如何获取进程信息。

函数声明如下:

1 NTSTATUS WINAPI NtQuerySystemInformation(
2   _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
3   _Inout_   PVOID                    SystemInformation,
4   _In_      ULONG                    SystemInformationLength,
5   _Out_opt_ PULONG                   ReturnLength
6 );

参数说明:

SystemInformationClass

要检索的系统信息的类型。 此参数可以是 SYSTEM_INFORMATION_CLASS 枚举类型的值之一。

这里我们只使用SystemProcessInformation值

SystemInformation

指向接收请求信息的缓冲区的指针,这个类型的大小和结构会因SystemInformationClass参数而异。

也就是说,对应的类型需要对应类型的缓冲区。我们这里SYSTEM_INFORMATION_CLASS使用的是SystemProcessInformation值,

所以这个参数传递的是SYSTEM_PROCESS_INFORMATION类型,定义如下:

这个类型在winternl.h中定义了

 1 typedef struct _SYSTEM_PROCESS_INFORMATION {2     ULONG NextEntryOffset;3     ULONG NumberOfThreads;4     BYTE Reserved1[48];5     UNICODE_STRING ImageName;6     KPRIORITY BasePriority;7     HANDLE UniqueProcessId;8     PVOID Reserved2;9     ULONG HandleCount;
10     ULONG SessionId;
11     PVOID Reserved3;
12     SIZE_T PeakVirtualSize;
13     SIZE_T VirtualSize;
14     ULONG Reserved4;
15     SIZE_T PeakWorkingSetSize;
16     SIZE_T WorkingSetSize;
17     PVOID Reserved5;
18     SIZE_T QuotaPagedPoolUsage;
19     PVOID Reserved6;
20     SIZE_T QuotaNonPagedPoolUsage;
21     SIZE_T PagefileUsage;
22     SIZE_T PeakPagefileUsage;
23     SIZE_T PrivatePageCount;
24     LARGE_INTEGER Reserved7[6];
25 } SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

SystemInformationLength 

SystemInformation 参数指向的缓冲区的大小(以字节为单位)。

ReturnLength

指向函数写入所请求信息的实际大小的位置的可选指针。 如果该大小小于或等于 SystemInformationLength 参数,该函数会将信息复制到 SystemInformation 缓冲区中;否则,它将返回 NTSTATUS 错误代码,并在 ReturnLength 中返回接收请求的信息所需的缓冲区大小。

说明:在实际使用中,最后一个参数我们可以不去使用,但是前提是分配的空间够大。

返回值

NTSTATUS,需要查看DDK中的Ntstatus.h来查看错误代码,可以用NT_SUCCESS宏来判断是否成功。

按照官方文档的建议,使用NtQuerySystemInformation需要自己从Ntdll.dll中导入这个函数。

遍历流程如下:

1、导入函数

2、调用NtQuerySystemInformation,将SystemInformationClass参数设置为SystemProcessInformation

3、获取第一个进程信息

4、创建一个while循环,通过SYSTEM_PROCESS_INFORMATION的NextEntryOffset值来获取下一个进程信息的偏移量,通过计算得出下一个进程信息

5、当NextEntryOffset为NULL,退出循环,结束遍历 

示例代码如下:

 1 #include <Windows.h>2 #include <winternl.h>3 #include<strsafe.h>4 #include<iostream>5 6 typedef long(__stdcall* funNtQuerySystemInformation)(UINT, PVOID, ULONG, PULONG);7 8 int main()9 {
10     NTSTATUS status;
11     PVOID buffer;
12     PSYSTEM_PROCESS_INFORMATION pspi;
13 
14     //分配足够大的空间
15     buffer = VirtualAlloc(NULL, 1024 * 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 
16 
17     if (!buffer)
18     {
19         //分配内存失败
20         //GetLastError
21         return 0;
22     }
23 
24     HMODULE hModule = LoadLibrary(L"ntdll.dll");
25 
26     if (!hModule)
27     {
28         //加载dll失败
29         //GetLastError
30         return 0;
31     }
32 
33     funNtQuerySystemInformation NtQuerySystemInformation = (funNtQuerySystemInformation)GetProcAddress(hModule, "NtQuerySystemInformation");
34 
35     pspi = (PSYSTEM_PROCESS_INFORMATION)buffer;
36 
37     if (!NT_SUCCESS(status = NtQuerySystemInformation(SystemProcessInformation, pspi, 1024 * 1024, NULL)))
38     {
39         //查询进程列表失败
40         VirtualFree(buffer, 0, MEM_RELEASE);
41         return 0;
42     }
43 
44     while (pspi->NextEntryOffset) // Loop over the list until we reach the last entry.
45     {
46         //在这里添加处理逻辑
47         //......
48         TCHAR buf[128]{};
49         StringCchCopy(buf, pspi->ImageName.Length, pspi->ImageName.Buffer);
50         std::wcout << buf << std::endl;
51 
52         //计算下一条目的地址
53         pspi = (PSYSTEM_PROCESS_INFORMATION)((LPBYTE)pspi + pspi->NextEntryOffset); 
54     }
55 
56     //释放分配的空间
57     VirtualFree(buffer, 0, MEM_RELEASE); 
58 
59     return 0;
60 }

EnumProcesses函数

使用EnumProcesses函数可以枚举全部进程的id,然后通过pid再获取进程详细信息。

EnumProcesses声明如下:

1 BOOL EnumProcesses(
2   [out] DWORD   *lpidProcess,
3   [in]  DWORD   cb,
4   [out] LPDWORD lpcbNeeded
5 );

参数说明:

lpidProcess

指向接收进程标识符列表的数组(pProcessIds )的指针。

cb

pProcessIds 数组的大小(以字节为单位)。

lpcbNeeded

pProcessIds 数组中返回的字节数。

返回值:

非0,成功

0,失败,调用GetLastError来获取详细信息

示例代码如下:

 1 #include <iostream>2 #include<Windows.h>3 #include<tchar.h>4 #include<Psapi.h>5 6 int main()7 {8     DWORD processesArray[1024], cbNeeded, processesCount;9     
10     if (!EnumProcesses(processesArray, sizeof(processesArray), &cbNeeded))
11     {
12         return 0;
13     }
14 
15     processesCount = cbNeeded / sizeof(DWORD);
16 
17 
18     for (int i = 0; i < processesCount; i++)
19     {
20         TCHAR szProcessName[MAX_PATH] = L"<unknown>";
21 
22         HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
23             FALSE, processesArray[i]);
24 
25         if (NULL != hProcess)
26         {
27             HMODULE hMod;
28 
29             if (EnumProcessModulesEx(hProcess, &hMod, sizeof(hMod), &cbNeeded, LIST_MODULES_ALL))
30             {
31                 GetModuleBaseName(hProcess, hMod, szProcessName, 
32                     sizeof(szProcessName) / sizeof(TCHAR));
33 
34                 std::wcout << szProcessName << std::endl;
35 
36                 CloseHandle(hProcess);
37             }
38 
39         }
40     }
41 }

使用WMI

WMI的使用可以参考我前面的文章

https://www.cnblogs.com/zhaotianff/p/14764740.html

  1 #include <iostream>2 #include <WbemIdl.h>3 #include<Windows.h>4 #include <comdef.h>5 #include<vector>6 7 #pragma comment(lib,"wbemuuid.lib")8 9 int main()10 {11     HRESULT hr;12     std::vector<VARIANT> bb{};13 14     hr = CoInitializeEx(0, COINIT_MULTITHREADED);15     if (FAILED(hr))16     {17         std::cout << "COM初始化失败.错误码 = 0x" << std::hex << hr << std::endl;18         return 0;19     }20 21     hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);22     if (FAILED(hr))23     {24         std::cout << "初始化安全性失败.错误码 = 0x" << std::hex << hr << std::endl;25     }26 27     IWbemLocator* pLoc = NULL;28     hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);29     if (FAILED(hr))30     {31         std::cout << "创建IWbemLocator对象失败.错误码 = 0x" << std::hex << hr << std::endl;32         CoUninitialize();33         return 0;34     }35 36     IWbemServices* pSvc = NULL;37 38     hr = pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &pSvc);39     if (FAILED(hr))40     {41         std::cout << "连接WMI失败.错误码 = 0x" << std::hex << hr << std::endl;42         pLoc->Release();43         CoUninitialize();44         return 0;45     }46 47     IEnumWbemClassObject* pEnumerator = NULL;48     hr = pSvc->ExecQuery(_bstr_t(L"WQL"), _bstr_t(L"Select * from win32_process"),49         WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,50         NULL,51         &pEnumerator);52 53     if (FAILED(hr))54     {55         std::cout << "查询win32_process失败.错误码 = 0x" << std::hex << hr << std::endl;56         pSvc->Release();57         pLoc->Release();58         CoUninitialize();59         return 0;60     }61 62     IWbemClassObject* pclsObj = NULL;63     ULONG uReturn = 0;64 65     while (pEnumerator)66     {67         hr = pEnumerator->Next(WBEM_INFINITE, 1,68             &pclsObj, &uReturn);69 70         if (0 == uReturn)71         {72             break;73         }74 75         if (pclsObj->BeginEnumeration(WBEM_FLAG_NONSYSTEM_ONLY) == WBEM_S_NO_ERROR)76         {77             BSTR name = NULL;78             VARIANT vtProp;79             VariantInit(&vtProp);80 81             while (pclsObj->Next(0, &name, &vtProp, 0, 0) == WBEM_S_NO_ERROR)82             {83                 bb.push_back(vtProp);84 85                 //这里暂时只输出字符串86                 if (vtProp.vt == VT_NULL || vtProp.vt != VT_BSTR)87                     continue;88 89                 //std::wcout << name << "\t" << vtProp.bstrVal << std::endl;90             }91 92             SysFreeString(name);93             VariantClear(&vtProp);94         }95         pclsObj->Release();96     }97 98     pEnumerator->Release();99     pSvc->Release();
100     pLoc->Release();
101     CoUninitialize();
102 
103     return 0;
104 }

示例代码

https://github.com/zhaotianff/WindowsProgramming/tree/master/Process

https://github.com/zhaotianff/WindowsProgramming/tree/master/Process_EnumProcesses

https://github.com/zhaotianff/WindowsProgramming/tree/master/Process_ZwQuerySystemInformation

https://github.com/zhaotianff/WindowsProgramming/tree/master/Process_WMI

参考资料

Using NtQuerySystemInformation to get process list - Programming - rohitab.com - Forums

枚举所有进程 - Win32 apps | Microsoft Learn

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

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

相关文章

基于STM32瑞士军刀--【FreeRTOS开发】学习笔记(二)|| 堆 / 栈

堆和栈 1. 堆 堆就是空闲的一块内存&#xff0c;可以通过malloc申请一小块内存&#xff0c;用完之后使用再free释放回去。管理堆需要用到链表操作。 比如需要分配100字节&#xff0c;实际所占108字节&#xff0c;因为为了方便后期的free&#xff0c;这一小块需要有个头部记录…

电子加密狗的定义与功能

电子加密狗&#xff0c;也称为加密锁、硬件锁或USB密钥&#xff0c;是一种用于软件保护和授权管理的硬件设备。它通常是一个外部设备&#xff0c;插入到计算机的USB接口上&#xff0c;通过加密算法和技术来确保软件的安全性和防止非法复制、盗版以及未经授权的使用。以下是关于…

软件测试面试准备工作

1、 什么是数据库? 答&#xff1a;数据库是按照某种数据模型组织起来的并存放二级存储器中的数据集合。 2、 什么是关系型数据库? 答&#xff1a;关系型数据库是建立在关系数据库模型基础上的数据库&#xff0c; 借助集合代数等概念和方法处理数据库中的数据。目前主流的关…

AR 眼镜之-蓝牙电话-实现方案

目录 &#x1f4c2; 前言 AR 眼镜系统版本 蓝牙电话 来电铃声 1. &#x1f531; 技术方案 1.1 结构框图 1.2 方案介绍 1.3 实现方案 步骤一&#xff1a;屏蔽原生蓝牙电话相关功能 步骤二&#xff1a;自定义蓝牙电话实现 2. &#x1f4a0; 屏蔽原生蓝牙电话相关功能 …

[linux] seqeval安装报错

新建一个新的环境 然后安装&#xff1a; # 不能拷贝别人的环境再安mebert_wash的环境。有冲突。我需要重新安一个空的conda环境&#xff0c;再安装。 # conda create -n wash python3.10 ipykernel python -m pip install --upgrade setuptools python -m pip install --upgr…

【Unity】关于Luban的简单使用

最近看了下Luban导出Excel数据的方式&#xff0c;来记录下 【Unity】关于Luban的简单使用 安装Luban开始使用UnityLubanC# 扩展 安装Luban Luban文档&#xff1a;https://luban.doc.code-philosophy.com/docs/beginner/quickstart 1.安装dotnet sdk 8.0或更高版本sdk 2.githu…

ViewPager2实现原理分析

ViewPager2 是 Android 开发中用于实现水平滑动视图的组件&#xff0c;它是 ViewPager 的一个改进版&#xff0c;提供了更多的功能和更好的性能。下面&#xff0c;我们将结合源码来简要分析 ViewPager2 的实现原理。 1. 基本架构 ViewPager2 的主要架构基于 RecyclerView&…

Activiti 6 兼容openGauss数据库bytes类型不匹配

当前有个项目需要做国产调研&#xff0c;需要适配高斯数据库&#xff0c;项目启动的时候&#xff0c;提示column "bytes_" is type bytea but expression is of type blob byte_字段是act_ge_bytearray表的&#xff0c;openGauss里的类型是bytea&#xff0c;类型是匹…

Mysql或MariaDB数据库的用户与授权操作——实操保姆级教程

一、问题描述 在日常的工作中,我们需要给不同角色的人员创建不同的账号,他们各自可访问的数据库或权限不一样,这时就需要创建用户和赋予不同的权限内容了。 二、问题分析 1、创建不同的角色账号; 2、给这些账号授予各自可访问数据库的权限。 三、实现方法 Centos8安装…

房子装修完显得大的一些

雅静说房子装修完怎么让它显得大一些      说七点,给大家总结装修三十年的经验      1,把阳台纳入大厅里来,拆掉开发商给的推拉门,换个大点但不影响通风的窗户      视觉上的通透感就会显得空间更大      2,全屋通铺,在瓦工阶段跟师父交代好,      直接通铺…

Java Generic练习(2024.7.25)

GenericExercise1 package GenericExercise20240725;import java.util.ArrayList; import java.util.List;public class GenericExercise1 {public static void main(String[] args) {// 泛型是JDK5以后引入的新的特性&#xff0c;主要目的是为了提供编译时的类型安全检测机制…

STM32——GPIO(LED闪烁)

一、什么是GPIO&#xff1f; GPIO&#xff08;通用输入输出接口&#xff09;&#xff1a; 1.GPIO 功能概述 GPIO 是通用输入/输出&#xff08;General Purpose I/O&#xff09;的简称&#xff0c;既能当输入口使用&#xff0c;又能当输出口使用。端口&#xff0c;就是元器件…

Java 代码规范if嵌套

在Java编程中&#xff0c;过度的if嵌套会使代码难以阅读和维护。为了遵循良好的代码规范&#xff0c;我们应尽量减少嵌套的深度。这通常可以通过重新组织代码或使用其他结构&#xff08;如switch语句&#xff0c;或者将逻辑封装到单独的方法中&#xff09;来实现。 以下是一个…

android settings提示音开关状态与修改(一)

android系统&#xff0c;settings提示音类型&#xff1a; 提示音开关默认状态&#xff0c;定义文件&#xff1a; frameworks/base/packages/SettingsProvider/res/values/defaults.xml 提示音默认定义&#xff1a; // 锁屏提示音 <integer name"def_lockscreen_sounds_…

Kylin自定义函数全解:释放数据分析的无限潜能

Kylin自定义函数全解&#xff1a;释放数据分析的无限潜能 Apache Kylin是一款高性能的分布式分析引擎&#xff0c;专为大规模数据集提供快速的SQL查询能力。Kylin的一个强大特性是支持自定义函数&#xff0c;这使得用户能够根据自己的业务需求扩展Kylin的数据处理能力。本文将…

2025第十九届中国欧亚国际军民两用技术及西安国防电子航空航天暨无人机展

2025第十九届中国欧亚国际军民两用技术及西安国防电子航空航天暨无人机展 时间&#xff1a;2025年3月14-16日 地点&#xff1a;西安国际会展中心 详询主办方陆先生 I38&#xff08;前三位&#xff09; I82I&#xff08;中间四位&#xff09; 9I72&#xff08;后面四位&am…

MySQL C API的介绍

1. MySQL C API是什么&#xff1f; MYSQL是最流行的SQL数据库管理系统&#xff0c;MySQL C API是一个基于C语言的API&#xff0c;用C语言编写的客户端应用程序可以使用它与MySQL服务器进行通信。 2. MySQL C API的发布 MYSQL C API代码随MySQL一起发布&#xff0c;并在libmy…

Nacos 2.x 新增 grpc 端口,Nginx 需要配置TCP端口转发的注意事项

Nacos 2.x 开始&#xff0c;最大的变化就是端口。在默认主端口 8848 之外又新增了三个端口&#xff0c;新增端口是在配置的主端口 server.port 的基础上&#xff0c;进行一定偏移量自动生成。 8848&#xff08;主端口&#xff0c;默认8848&#xff09;web页面端口及相关http接口…

导航网站WP主题/WP黑格导航主题BlackCandy-简约酷黑色高逼格+焕然一新的UI设计

源码简介&#xff1a; 导航网站WP主题-WP黑格导航主题BlackCandy&#xff0c;它有着简约酷黑色高逼格&#xff0c;而且有焕然一新的UI设计。它是一个简约漂亮的 WordPress 自媒体主题。黑格网址导航主题&#xff0c;自适应电脑端和手机端。 BlackCandy-V2.0这次全新升级了&am…

vite构建vue3项目hmr生效问题踩坑记录

vite构建vue3项目hmr生效问题踩坑记录 hmr的好处 以下是以表格形式呈现的前端开发中HMR&#xff08;热模块替换&#xff09;带来的好处&#xff1a; 好处描述提升开发效率允许开发者在不刷新整个页面的情况下实时更新修改的代码&#xff0c;减少等待时间保持应用状态在模块替…