Windows 10系统编程——进程专题:枚举我们进程的状态

Windows 10系统编程------进程专题:枚举我们进程的状态

前言

​ 接下来的部分更多涉及到一些代码实战的部分。比如说我们很快会尝试枚举一下咱们进程的状态。

Psapi.h

​ EnumProcess不在Windows.h中,他在另一个扩展的模块Psapi.h中,Psapi的全程是Process Status API,也就是获取咱们的进程的状态的API。需要注意的是(太操蛋了Windows SDK),Psapi.h需要在Windows.h之后被包含,要不然会出现找不到符号的问题。

​ PsAPI下的函数能做的事情非常的多

Psapi.hWindows 平台的进程状态和性能信息 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_INFORMATIONPROCESS_VM_READPROCESS_VM_WRITEPROCESS_TERMINATE、或 PROCESS_ALL_ACCESS 等)。
  • bInheritHandle:子进程是否可继承该句柄(通常为 FALSE)。
  • dwProcessId:目标进程的 PID(进程 ID)。

返回值与错误处理

  • 成功返回有效 HANDLE,失败返回 NULL。失败时可用 GetLastError() 获取错误码(例如 ERROR_ACCESS_DENIEDERROR_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_INFORMATIONPROCESS_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_BUFFERERROR_ACCESS_DENIEDERROR_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);
}
相关推荐
sealaugh322 小时前
AI(学习笔记第九课) 使用langchain的MultiQueryRetriever和indexing
人工智能·笔记·学习
bawangtianzun2 小时前
树的重心与直径 性质
数据结构·c++·学习·算法
少陵野小Tommy2 小时前
C语言计算行列式的值
c语言·开发语言·学习·学习方法
张书名2 小时前
《强化学习数学原理》学习笔记7——从贝尔曼最优方程得到最优策略
笔记·学习
报错小能手2 小时前
linux学习笔记(11)fork详解
linux·笔记·学习
我命由我123452 小时前
Photoshop - Photoshop 工具栏(1)移动工具
笔记·学习·ui·职场和发展·求职招聘·职场发展·photoshop
鄃鳕3 小时前
C++坑系列,C++ std::atomic 拷贝构造函数问题分析与解决方案
java·javascript·c++
CHANG_THE_WORLD3 小时前
Windows程序字符串处理与逆向分析
windows·stm32·单片机
code monkey.4 小时前
【探寻C++之旅】第十六章:unordered系列的认识与模拟实现
数据结构·c++·stl