Windows 10系统编程——进程专题:枚举我们进程的状态
Windows 10系统编程——进程专题:枚举我们进程的状态
前言
接下来的部分更多涉及到一些代码实战的部分。比如说我们很快会尝试枚举一下咱们进程的状态。
Psapi.h
EnumProcess不在Windows.h中,他在另一个扩展的模块Psapi.h中,Psapi的全程是Process Status API,也就是获取咱们的进程的状态的API。需要注意的是(太操蛋了Windows SDK),Psapi.h需要在Windows.h之后被包含,要不然会出现找不到符号的问题。
PsAPI下的函数能做的事情非常的多
Psapi.h
是 Windows 平台的进程状态和性能信息 API 头文件,它是 Process Status API 的一部分。
- 提供了 查询系统进程、模块、内存信息等功能 的接口。
- 通常配合
Psapi.lib
链接使用。- 可以用来实现类似任务管理器的功能:获取进程列表、模块列表、内存占用信息等。
我们稍后会介绍更加现代的ToolHelp32来取代比较反人类的EnumProcess,但是这里算是编程练习了,先放在这里。
函数 | 用途 |
---|---|
EnumProcesses | 获取系统中所有进程的 PID 列表 |
EnumProcessModules | 获取指定进程加载的模块句柄列表 |
GetModuleBaseName | 获取模块(通常是 EXE/DLL)的名称 |
GetModuleFileNameEx | 获取模块的完整路径 |
GetModuleInformation | 获取模块的基本信息(基址、大小等) |
GetProcessMemoryInfo | 获取进程内存占用信息 |
EmptyWorkingSet | 清空指定进程的工作集,释放内存(谨慎使用) |
QueryWorkingSet | 获取进程的工作集信息 |
EnumDeviceDrivers | 获取系统加载的驱动程序列表 |
GetDeviceDriverFileName | 获取驱动程序文件路径 |
使用EnumProcess来获取进程的PID
获取进程的所有PID,咱们就来使用EnumProcess来解决这个小问题。
EnumProcess是试探性的,也就是说——他不计划告诉你到底系统有多少个API,需要你自己去试探获取。下面的例子就是一个典型的试探类型代码。
#include <Windows.h>
#include <Psapi.h>
#include <iostream>
#include <memory>static constexpr const int fromPidMemoryPoolSize(const DWORD d) {return d / sizeof(DWORD);
}static constexpr const DWORD toPidMemoryPoolSize(const int s) {return s * sizeof(DWORD);
}int main()
{int maxCount = 256; // try to make a array of pidsauto pids_pool = std::make_unique<DWORD[]>(maxCount);int real_pid_size = 0; // real size of the PIDSwhile (true) {DWORD actual_buffer_size;if (!EnumProcesses(pids_pool.get(),toPidMemoryPoolSize(maxCount),&actual_buffer_size)) {// function invoke failed!std::cout << "function EnumProcesses invoke failed!" << std::endl;return -1;}real_pid_size = toPidMemoryPoolSize(actual_buffer_size);if (real_pid_size < maxCount) break; // OK, we reach the maxmaxCount *= 2; // Try Largerpids_pool = std::make_unique<DWORD[]>(maxCount);}printf("The Pid pool size: %d", real_pid_size);return 0;
}
使用OpenProcess来访问线程句柄
OpenProcess
是 Windows API(在 Windows.h
中声明)的一个函数,用于以指定访问权限打开另一个进程并返回其句柄。常见用途包括:查询进程信息、读取/写入进程内存(调试或监控工具)、等待进程结束或向其发送控制操作。注意:访问受限的进程(系统、受保护进程或高权限进程)需要更高权限或管理员权限,滥用可能触发安全/隐私问题。
下面的内容是笔者从MSDN文档中摘录出来的——
#include <windows.h>HANDLE OpenProcess(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwProcessId
);
dwDesiredAccess
:所需权限(如PROCESS_QUERY_INFORMATION
、PROCESS_VM_READ
、PROCESS_VM_WRITE
、PROCESS_TERMINATE
、或PROCESS_ALL_ACCESS
等)。bInheritHandle
:子进程是否可继承该句柄(通常为FALSE
)。dwProcessId
:目标进程的 PID(进程 ID)。
返回值与错误处理
- 成功返回有效
HANDLE
,失败返回NULL
。失败时可用GetLastError()
获取错误码(例如ERROR_ACCESS_DENIED
、ERROR_INVALID_PARAMETER
等)。 - 使用完句柄后必须调用
CloseHandle(hProcess)
以释放资源。
#include <windows.h>
#include <iostream>int main() {DWORD pid = 1234; // 目标进程 PID(示例)HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);if (!h) {std::cerr << "OpenProcess failed, error: " << GetLastError() << '\n';return 1;}// 示例:检查进程是否仍然存在(等待 0 毫秒)DWORD wait = WaitForSingleObject(h, 0);if (wait == WAIT_TIMEOUT) {std::cout << "Process " << pid << " is running.\n";} else {std::cout << "Process " << pid << " is not running or terminated.\n";}CloseHandle(h);return 0;
}
基于这个思路,咱们就可以开始枚举进程的基本信息了,如下所示:
#include <Windows.h>
#include <Psapi.h>
#include <iostream>
#include <memory>
#include <vector>
#include <map>
#include <tchar.h>
static constexpr const int fromPidMemoryPoolSize(const DWORD d) {return d / sizeof(DWORD);
}static constexpr const DWORD toPidMemoryPoolSize(const int s) {return s * sizeof(DWORD);
}int main()
{int maxCount = 256; // try to make a array of pidsauto pids_pool = std::make_unique<DWORD[]>(maxCount);int real_pid_size = 0; // real size of the PIDSwhile (true) {DWORD actual_buffer_size;if (!EnumProcesses(pids_pool.get(),toPidMemoryPoolSize(maxCount),&actual_buffer_size)) {// function invoke failed!std::cout << "function EnumProcesses invoke failed!" << std::endl;return -1;}real_pid_size = toPidMemoryPoolSize(actual_buffer_size);if (real_pid_size < maxCount) break; // OK, we reach the maxmaxCount *= 2; // Try Largerpids_pool = std::make_unique<DWORD[]>(maxCount);}for (int i = 0; i < real_pid_size; i++) {HANDLE current_process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pids_pool[i]);if (current_process_handle == NULL) // Not a valid handle{... // error donecontinue;}... // using the pid to collect relative infoCloseHandle(current_process_handle);}return 0;
}
利用GetProcessTimes获取进程相关的统计时间
GetProcessTimes可以用来获取进程相关的统计时间
BOOL GetProcessTimes([in] HANDLE hProcess,[out] LPFILETIME lpCreationTime,[out] LPFILETIME lpExitTime,[out] LPFILETIME lpKernelTime,[out] LPFILETIME lpUserTime
);
MSDN对下面的参数有着这些注解:
[in] hProcess
要获取其计时信息的进程的句柄。 句柄必须具有 PROCESS_QUERY_INFORMATION 或 PROCESS_QUERY_LIMITED_INFORMATION 访问权限。 有关详细信息,请参阅 进程安全和访问权限。
Windows Server 2003 和 Windows XP: 句柄必须具有 PROCESS_QUERY_INFORMATION 访问权限。
[out] lpCreationTime
指向 FILETIME 结构的指针,该结构接收进程的创建时间。
[out] lpExitTime
指向 FILETIME 结构的指针,该结构接收进程的退出时间。 如果进程尚未退出,则此结构的内容未定义。
[out] lpKernelTime
指向 FILETIME 结构的指针,该结构接收进程在内核模式下执行的时间。 确定进程的每个线程在内核模式下执行的时间,然后将所有这些时间相加以获取此值。
[out] lpUserTime
指向 FILETIME 结构的指针,该结构接收进程在用户模式下执行的时间。 确定进程的每个线程在用户模式下执行的时间,然后将所有这些时间相加以获取此值。
补充:FILETIME 与 SYSTEMTIME(Windows 时间相关结构)
- FILETIME:Windows 内部常用的时间表示,表示自 1601-01-01(UTC) 起经过的 100 纳秒(0.1µs)间隔数。它是一个 64 位值(在 API 中用两个 32 位字段表示),适合做精确的时间算术、排序与存储。
- SYSTEMTIME:可读的“拆分时间”结构(年、月、日、时、分、秒、毫秒),便于显示与格式化。它本身不携带时区信息;通常用来表示 UTC(通过
GetSystemTime
)或本地时间(通过GetLocalTime
)。结构定义(来自 Windows SDK)
// FILETIME typedef struct _FILETIME {DWORD dwLowDateTime;DWORD dwHighDateTime; } FILETIME;// SYSTEMTIME typedef struct _SYSTEMTIME {WORD wYear;WORD wMonth;WORD wDayOfWeek; // Sunday = 0WORD wDay;WORD wHour;WORD wMinute;WORD wSecond;WORD wMilliseconds; } SYSTEMTIME;
SYSTEMTIME 是分解后的字段,便于显示/输入,但不便于直接做差值计算(先转为 FILETIME)。
一些可以知道的常用 API(转换与获取)
GetSystemTimeAsFileTime(&ft)
:以 UTC 返回当前时间的 FILETIME。GetSystemTime(&st)
/GetLocalTime(&st)
:以拆分字段返回当前 UTC / 本地时间。SystemTimeToFileTime(const SYSTEMTIME*, FILETIME*)
:SYSTEMTIME → FILETIME(结果为 UTC)。FileTimeToSystemTime(const FILETIME*, SYSTEMTIME*)
:FILETIME → SYSTEMTIME(结果为 UTC)。FileTimeToLocalFileTime(const FILETIME*, FILETIME*)
与LocalFileTimeToFileTime
:在 FILETIME(UTC)与本地 FILETIME(含时区/夏令时)之间转换。
基于上述事实,咱们很容易编写获取时间的代码:
HANDLE current_process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pids_pool[i]);if (current_process_handle == NULL) // Not a valid handle{...continue;}FILETIME start_time {0}, dummy;GetProcessTimes(current_process_handle, &start_time, &dummy, &dummy, &dummy);FileTimeToLocalFileTime(&start_time, &start_time);SYSTEMTIME system_time;FileTimeToSystemTime(&start_time, &system_time);
利用QueryFullProcessImageName获取执行文件名称
QueryFullProcessImageName
是 Windows API(kernel32
)提供的用于获取指定进程可执行映像完整路径的函数。它比早期的 GetModuleFileNameEx
/ GetProcessImageFileName
更现代,且在可用权限受限的场景下可与 PROCESS_QUERY_LIMITED_INFORMATION
配合使用来读取路径信息。
// Unicode 版本
BOOL QueryFullProcessImageNameW(HANDLE hProcess,DWORD dwFlags,LPWSTR lpExeName,PDWORD lpdwSize
);
参数说明
hProcess
:目标进程的句柄。通常通过OpenProcess
获取(见下面示例)。dwFlags
:决定返回路径的格式:0
:以 Win32 风格路径返回(例如C:\Program Files\...
)。PROCESS_NAME_NATIVE
(值1
):以原生 NT 风格路径返回(例如\Device\HarddiskVolumeX\...
)。
lpExeName
:调用者提供的缓冲区,用于接收路径(对 Unicode 版本为WCHAR[]
)。lpdwSize
:输入时表示缓冲区的容量(以字符数为单位);返回时写回实际写入的字符数(不包括终止 NUL)。调用者需准备足够大的缓冲区,或按需重试。
返回值与错误
- 返回非零表示成功;失败返回
0
,可调用GetLastError()
获取错误码(常见错误:ERROR_INSUFFICIENT_BUFFER
、ERROR_ACCESS_DENIED
、ERROR_PARTIAL_COPY
等)。在某些特殊目标(例如 WSL 或某些驱动映射的进程)上,使用不同dwFlags
可能导致不同错误/不同行为。
所需访问权限 & 兼容性
- 推荐用
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, ...)
(Windows Vista 及以后支持)。在旧版(如 Windows XP)上该常量可能不受支持,函数本身也在 Vista 及之后的平台更常见。若目标是高权限/受保护进程,可能仍需更高权限(如SeDebugPrivilege
)。
整理一下咱们的代码
#include <Windows.h>
#include <Psapi.h>
#include <iostream>
#include <memory>
#include <vector>
#include <map>
#include <tchar.h>
static constexpr const int fromPidMemoryPoolSize(const DWORD d) {return d / sizeof(DWORD);
}static constexpr const DWORD toPidMemoryPoolSize(const int s) {return s * sizeof(DWORD);
}int main()
{int maxCount = 256; // try to make a array of pidsauto pids_pool = std::make_unique<DWORD[]>(maxCount);int real_pid_size = 0; // real size of the PIDSwhile (true) {DWORD actual_buffer_size;if (!EnumProcesses(pids_pool.get(),toPidMemoryPoolSize(maxCount),&actual_buffer_size)) {// function invoke failed!std::cout << "function EnumProcesses invoke failed!" << std::endl;return -1;}real_pid_size = toPidMemoryPoolSize(actual_buffer_size);if (real_pid_size < maxCount) break; // OK, we reach the maxmaxCount *= 2; // Try Largerpids_pool = std::make_unique<DWORD[]>(maxCount);}printf("The Pid pool size: %d", real_pid_size);std::map<DWORD, std::vector<DWORD>> error_pids;struct DumpInfo {DumpInfo(const SYSTEMTIME& sys_start, const TCHAR exeName[MAX_PATH], const DWORD pid) {file_start_system = sys_start;// 复制 exeName 内容_tcsncpy_s(this->exeName, exeName, MAX_PATH);this->pid = pid;}SYSTEMTIME file_start_system;TCHAR exeName[MAX_PATH];DWORD pid;};std::vector<DumpInfo> dumps;for (int i = 0; i < real_pid_size; i++) {HANDLE current_process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pids_pool[i]);if (current_process_handle == NULL) // Not a valid handle{error_pids[GetLastError()].emplace_back(pids_pool[i]);continue;}FILETIME start_time {0}, dummy;GetProcessTimes(current_process_handle, &start_time, &dummy, &dummy, &dummy);FileTimeToLocalFileTime(&start_time, &start_time);SYSTEMTIME system_time;FileTimeToSystemTime(&start_time, &system_time);TCHAR exeName[MAX_PATH];DWORD size = MAX_PATH;DWORD count = ::QueryFullProcessImageName(current_process_handle, 0, exeName, &size);dumps.emplace_back(system_time, exeName, pids_pool[i]);CloseHandle(current_process_handle);}for (const auto& each : error_pids) {printf("The errno pids: %d occurs in the pids: ", each.first);int sz = each.second.size();for (int i = 0; i < sz; i++) {if (i % 5 == 0) printf("\n");printf("%d\t", each.second[i]);}}printf("\n");for (const auto& each : dumps) {printf("PID: %5d, Start: %d/%d/%d %02d:%02d:%02d Image: %ws\n",each.pid, each.file_start_system.wDay, each.file_start_system.wMonth,each.file_start_system.wYear, each.file_start_system.wHour, each.file_start_system.wMinute, each.file_start_system.wSecond,each.exeName);}return 0;
}
利用现代的CreateToolhelp32Snapshot来枚举进程
CreateToolhelp32Snapshot
是 Windows 提供的用于捕获系统当前进程/线程/模块等信息“快照”的函数。结合 Thread32First
/ Thread32Next
可以遍历系统中当前存在的线程列表,常用于进程监控、诊断工具或简单的任务管理器实现中。但是麻烦的一点在于CreateToolhelp32Snapshot是快照版本的,他只会保存调用的那一瞬间的进程快照的内容
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
TH32CS_SNAPPROCESS
:枚举系统所有进程TH32CS_SNAPTHREAD
:枚举系统所有线程
#include <Windows.h>
#include <TlHelp32.h>
#include <iostream>
int main()
{HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if (hSnapshot == INVALID_HANDLE_VALUE) {std::cerr << "failed to involk CreateToolhelp32Snapshot";return -1;}PROCESSENTRY32 pe;pe.dwSize = sizeof(pe);if (!Process32First(hSnapshot, &pe)) {std::cerr << "failed to involk Process32First";return -1;}do {printf("PID:%6d (PPID:%6d): %ws (Threads=%d) (Priority=%d)\n",pe.th32ProcessID, pe.th32ParentProcessID, pe.szExeFile,pe.cntThreads, pe.pcPriClassBase);} while (::Process32Next(hSnapshot, &pe));::CloseHandle(hSnapshot);return 0;
}
WTS 枚举
Windows 提供了一组 WTS(Windows Terminal Services)函数,用于查询、管理和控制会话(Session)及其相关进程。这类函数通常用于远程桌面管理、会话监控或系统管理工具中。
函数 | 功能 |
---|---|
WTSOpenServer | 打开一个远程或本地服务器的句柄(可用 WTS_CURRENT_SERVER_HANDLE 代替本地) |
WTSEnumerateSessions | 枚举服务器上的所有会话 |
WTSQuerySessionInformation | 查询指定会话的详细信息(用户名、域、登录时间等) |
WTSTerminateSession | 终止指定会话 |
WTSCloseServer | 关闭通过 WTSOpenServer 打开的句柄 |
WTSEnumerateProcesses | 枚举指定会话或服务器上的进程 |
所有 WTS API 都在
Wtsapi32.dll
中实现,头文件为WtsApi32.h
,使用时需链接Wtsapi32.lib
PWTS_PROCESS_INFO pProcessInfo = nullptr;
DWORD processCount = 0;// 枚举服务器所有会话的进程
if (WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pProcessInfo, &processCount)) {for (DWORD i = 0; i < processCount; ++i) {WTS_PROCESS_INFO pi = pProcessInfo[i];std::wcout << L"PID: " << pi.ProcessId<< L" SessionID: " << pi.SessionId<< L" Image: " << pi.pProcessName << L"\n";}WTSFreeMemory(pProcessInfo);
}