enumprocessmodules 获取模块句柄后需配合 getmodulefilenameex(全路径)或 getmodulebasename(仅文件名)获取名称,须确保 openprocess 权限充足、定义 psapi_version=1 和 _win32_winnt≥0x0501,并容错处理空名及无效句柄。

Windows下用 EnumProcessModules 获取模块句柄再查名字
直接调用 EnumProcessModules 是最常用且可靠的方式,但它返回的是模块句柄(HMODULE),不是名字。必须配合 GetModuleFileNameEx 或 GetModuleBaseName 才能拿到路径或文件名。注意:前者返回完整路径(如 C:\Windows\System32\kernel32.dll),后者只返回文件名(如 kernel32.dll)。
需要链接 psapi.lib,并确保在项目中定义 PSAPI_VERSION=1(否则 GetModuleFileNameEx 可能返回空)。如果目标系统是 Windows XP 或更老版本,还得用 psapi.dll 动态加载——但现代开发基本可忽略这点。
- 必须先用
OpenProcess获取当前进程句柄,权限至少含PROCESS_QUERY_INFORMATION和PROCESS_VM_READ -
EnumProcessModules第二个参数是HMODULE数组,大小单位是字节,别传错(常见错误:传sizeof(HMODULE) * count而不是缓冲区总长度) - 某些系统 DLL(如
ntdll.dll)可能因权限或加载方式特殊,在低完整性进程里被GetModuleFileNameEx返回空字符串,此时可 fallback 到GetModuleBaseName
GetModuleFileNameEx 返回空?检查权限和模块有效性
返回空字符串或长度为 0 是高频问题,不是函数写错了,而是调用上下文不对。最常见原因是:OpenProcess 拿到的句柄权限不足,或者传入的 HMODULE 已失效(比如模块已卸载但没及时清理句柄数组)。
验证方法:对每个模块句柄调用 GetLastError(),若返回 ERROR_ACCESS_DENIED,说明权限不够;若返回 ERROR_INVALID_HANDLE,说明该模块句柄无效(可能是已卸载,也可能是枚举时竞态导致)。
- 确保编译时定义
_WIN32_WINNT=0x0501或更高(推荐0x0601即 Windows 7+) - 不要复用其他线程获取的模块句柄——每个
EnumProcessModules调用都应配对新的OpenProcess和缓冲区 - 如果只关心“已加载的 DLL 文件名”,用
GetModuleBaseName更轻量,且对大多数用户态 DLL 都有效
替代方案:EnumProcessModulesEx + GET_MODULE_INFORMATION
如果需要区分“已加载”和“延迟加载”的模块,或者想跳过内核态模块(如 ntdll.dll、kernelbase.dll),EnumProcessModulesEx 更合适。它支持过滤标志,比如 LIST_MODULES_32BIT 或 LIST_MODULES_64BIT(在 WoW64 进程里有用),还能配合 GetMappedFileName 获取映射路径。
但要注意:这个 API 在 Windows Vista+ 才可用,且需链接 psapi.lib,头文件仍为 psapi.h。它不解决名字为空的问题,只是提供了更细粒度的控制能力。
- 传
LIST_MODULES_ALL才能拿到所有模块(默认只返回用户模块) - 返回的模块列表顺序不保证稳定,别依赖顺序做逻辑判断
- 和
EnumProcessModules一样,仍需配合GetModuleFileNameEx或GetModuleBaseName提取名称
64位进程读取自身模块名时的坑
在 64 位 Windows 上,32 位进程调用 EnumProcessModules 只能看到 32 位模块;64 位进程则能看到全部。但如果一个 64 位进程尝试用 GetModuleFileNameEx 查询某个 32 位兼容层模块(比如通过 WoW64 加载的),会失败——因为地址空间不一致。
实际开发中,除非你明确在写调试器或系统工具,否则只需关注当前进程架构对应的模块。多数情况下,直接用 GetCurrentProcess() 替代 OpenProcess 更安全(省去权限检查),但前提是不跨进程操作。
- 不要假设
GetModuleFileNameEx对所有句柄都成功,加if (len == 0) continue;跳过异常项 - 模块名字符串用
std::wstring接收,避免 ANSI 截断(尤其路径含中文时) - 频繁调用建议缓存结果,因为每次枚举都要遍历 PE LDR 链表,开销不小
实际跑通的关键就三点:开对权限、选对函数组合、容错处理空名。模块名不是“查一下就有”,而是“查了再验、验了再转、转了再存”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











