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,需要你自己去试探获取。下面的例子就是一个典型的试探类型代码。
cpp
#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 pids
auto pids_pool = std::make_unique<DWORD[]>(maxCount);
int real_pid_size = 0; // real size of the PIDS
while (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 max
maxCount *= 2; // Try Larger
pids_pool = std::make_unique<DWORD[]>(maxCount);
}
printf("The Pid pool size: %d", real_pid_size);
return 0;
}
使用OpenProcess来访问线程句柄
OpenProcess
是 Windows API(在 Windows.h
中声明)的一个函数,用于以指定访问权限打开另一个进程并返回其句柄。常见用途包括:查询进程信息、读取/写入进程内存(调试或监控工具)、等待进程结束或向其发送控制操作。注意:访问受限的进程(系统、受保护进程或高权限进程)需要更高权限或管理员权限,滥用可能触发安全/隐私问题。
下面的内容是笔者从MSDN文档中摘录出来的------
cpp
#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)
以释放资源。
cpp
#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;
}
基于这个思路,咱们就可以开始枚举进程的基本信息了,如下所示:
cpp
#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 pids
auto pids_pool = std::make_unique<DWORD[]>(maxCount);
int real_pid_size = 0; // real size of the PIDS
while (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 max
maxCount *= 2; // Try Larger
pids_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 done
continue;
}
... // using the pid to collect relative info
CloseHandle(current_process_handle);
}
return 0;
}
利用GetProcessTimes获取进程相关的统计时间
GetProcessTimes可以用来获取进程相关的统计时间
cpp
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)
cpp// FILETIME typedef struct _FILETIME { DWORD dwLowDateTime; DWORD dwHighDateTime; } FILETIME; // SYSTEMTIME typedef struct _SYSTEMTIME { WORD wYear; WORD wMonth; WORD wDayOfWeek; // Sunday = 0 WORD 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
配合使用来读取路径信息。
cpp
// 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
)。
整理一下咱们的代码
cpp
#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 pids
auto pids_pool = std::make_unique<DWORD[]>(maxCount);
int real_pid_size = 0; // real size of the PIDS
while (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 max
maxCount *= 2; // Try Larger
pids_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
:枚举系统所有线程
cpp
#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
cpp
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);
}