唯一可靠方式是调用ntdll.dll中的ntquerysysteminformation并传入systemhandleinformation(16),需提权、动态分配缓冲区、按os版本解析结构体,统计句柄条目个数。

Windows 下直接读取 ntdll.dll 的 NtQuerySystemInformation 是唯一可靠方式
系统级句柄总数(Global Handle Count)不暴露在 Win32 API 中,GetProcessHandleCount 只返回当前进程的句柄数,对全局无意义。必须调用 NT 内核导出函数 NtQuerySystemInformation 并传入 SystemHandleInformation(值为 16)才能拿到全系统句柄快照。
关键点:
-
NtQuerySystemInformation在ntdll.dll中,无需链接库,但需GetProcAddress动态获取地址 - 返回结构体是
SYSTEM_HANDLE_INFORMATION(旧版)或SYSTEM_HANDLE_INFORMATION_EX(Win10+),字段偏移和大小不同,必须按 OS 版本分支处理 - 首次调用会返回所需缓冲区大小,需循环重试分配内存,不能硬编码缓冲区
- 该调用需要
SeDebugPrivilege权限,普通用户权限下会返回STATUS_ACCESS_DENIED(错误码 0xC0000022)
如何安全调用 NtQuerySystemInformation 避免崩溃或权限失败
常见错误是直接声明函数指针后硬调用,结果触发 AV(访问冲突)或返回空数据。正确流程必须包含三步:提权 → 分配足够缓冲区 → 解析句柄数组。
实操建议:
- 用
OpenProcessToken+LookupPrivilegeValue+AdjustTokenPrivileges提升SeDebugPrivilege,失败则直接退出,不降级尝试 - 第一次调用时传入
nullptr和0,检查返回值是否为STATUS_INFO_LENGTH_MISMATCH(0xC0000004),再按ReturnLength分配内存 - 解析时注意:Windows 10 1809+ 返回的是
SYSTEM_HANDLE_INFORMATION_EX,开头多出NumberOfHandles字段;旧版需手动遍历直到Handle字段为 0 或越界 - 不要用
std::vector<char></char>直接接收原始数据——结构体内存布局非标准,必须用malloc或VirtualAlloc分配页对齐内存(尤其 Win11 启用 CFG 后更敏感)
SYSTEM_HANDLE_INFORMATION 结构体字段怎么看、怎么统计
返回的数据是一块连续内存,起始处是句柄条目数组,每个条目长度固定(旧版 16 字节,新版 24 字节),但字段含义易混淆:
-
ProcessId:所属进程 ID,不是 HANDLE;统计全局总数时只需计数,但若要做归属分析,需用它关联CreateToolhelp32Snapshot查进程名 -
ObjectTypeNumber:内核对象类型编号(如 0x1e 是 Event,0x1f 是 Section),不是字符串;查表需用NtQueryObject+ObjectNameInformation才能拿到类型名,开销大,监控场景建议只做编号聚合 -
HandleAttributes和HandleValue:前者含继承/保护位,后者才是实际句柄值(低 16 位有效),统计总数时这两个字段可忽略 - 真正要累加的只有条目个数:旧版靠循环遍历判断
ProcessId == 0截断;新版直接读首字段NumberOfHandles
性能与稳定性陷阱:每秒多次采集会导致系统卡顿吗?
会,而且非常明显。这不是 CPU 密集型操作,而是内核同步锁竞争型操作——NtQuerySystemInformation 在采集句柄表时会短暂挂起所有进程的对象管理器路径,高频调用(如 csrss.exe 延迟升高、桌面响应变慢、甚至远程桌面断连。
生产环境必须遵守:
- 监控周期不低于 3 秒;告警阈值触发后可临时缩至 1 秒,持续超时 10 秒后强制恢复原频次
- 避免在服务主循环中直接调用;应放到独立低优先级线程,且使用
SetThreadPriority设为THREAD_PRIORITY_BELOW_NORMAL - 不要把结果直接打印到控制台或写文件——格式化字符串和磁盘 I/O 会放大延迟;先存入环形缓冲区,由另一线程批量落盘或上报
- 如果只是检测泄漏,比对相邻两次差值比绝对值更有意义;突然增长 5000+ 句柄/分钟比总数破 5 万更值得告警
句柄表本身无缓存机制,每次调用都是全量扫描,这是 Windows 内核设计决定的,绕不过去。想“轻量”就只能少调、异步调、带上下文调。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











