Windows编程系列:进程遍历的几种方法
在应用层下,进程遍历有多种方式,这里介绍几种常用的方式:进程快照、NtQuerySystemInformation、EnumProcesses函数、WMI等。
在C#中Process类提供了一个GetProcesses()函数,这个函数内部就是调用的NtQuerySystemInformation进行获取。
进程快照
这种方式比较简单方便。主要涉及以下几个函数
CreateToolhelp32Snapshot(TlHelp32.h)函数,获取进程信息为指定的进程、进程使用的堆、模块、线程建立一个快照。
函数声明如下:
1 HANDLE 2 WINAPI 3 CreateToolhelp32Snapshot( 4 DWORD dwFlags, 5 DWORD th32ProcessID 6 );
参数说明:
dwFlags
值 | 含义 |
---|---|
|
指示快照句柄是可继承的。 |
|
包括系统中的所有进程和线程,以及 th32ProcessID 中指定的进程的堆和模块。 等效于指定使用 OR 操作 (“|”组合的TH32CS_SNAPHEAPLIST、TH32CS_SNAPMODULE、TH32CS_SNAPPROCESS和TH32CS_SNAPTHREAD值) 。 |
|
包括快照 th32ProcessID 中指定的进程的所有堆。 若要枚举堆,请参阅 Heap32ListFirst。 |
|
包括快照 th32ProcessID 中指定的进程的所有模块。 若要枚举模块,请参阅 Module32First。 如果函数失败并 出现ERROR_BAD_LENGTH,请重试该函数,直到成功。
64 位 Windows: 在 32 位进程中使用此标志包括 th32ProcessID 中指定的进程的 32 位模块,而在 64 位进程中使用它包括 64 位模块。 若要从 64 位进程包括 th32ProcessID 中指定的进程的 32 位模块,请使用 TH32CS_SNAPMODULE32 标志。 |
|
从 64 位进程调用时,包括快照中 th32ProcessID 中指定的进程的所有 32 位模块。 此标志可以与 TH32CS_SNAPMODULE 或 TH32CS_SNAPALL结合使用。 如果函数失败并 出现ERROR_BAD_LENGTH,请重试该函数,直到成功。 |
|
包括系统中快照中的所有进程。 若要枚举进程,请参阅 Process32First。 |
|
包括快照系统中的所有线程。 若要枚举线程,请参阅 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 tagPROCESSENTRY32W 2 { 3 DWORD dwSize; // size 4 DWORD cntUsage; // obselete(keep 0) 5 DWORD th32ProcessID; // this process 6 ULONG_PTR th32DefaultHeapID; // obselete(keep 0) 7 DWORD th32ModuleID; // associated exe 8 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
参考资料:
http://www.rohitab.com/discuss/topic/40504-using-ntquerysysteminformation-to-get-process-list/
https://learn.microsoft.com/zh-cn/windows/win32/psapi/enumerating-all-processes
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构