EnumWindows 遍历顶级窗口需先用 IsWindow 和 IsWindowVisible 校验有效性,再用 GetWindowTextLengthW 和 GetWindowTextW(宽字符缓冲区)获取标题,匹配时用 wcsstr 等宽字符函数,回调中避免耗时操作和跨线程 UI 调用。

用 EnumWindows 遍历所有顶级窗口
Windows 系统中没有“获取所有窗口标题”的单次调用 API,必须靠 EnumWindows 主动枚举。它会按 Z 顺序(从顶层到底层)逐个回调你提供的函数,参数是窗口句柄 HWND 和用户传入的 lParam。关键点在于:它只枚举**顶级窗口**(即 GetParent(hWnd) == NULL),且默认包含不可见、已销毁但未清理的窗口句柄——所以后续必须手动过滤。
常见错误是直接对每个 hWnd 调用 GetWindowText 不加判断,结果拿到空字符串或乱码,甚至触发 GDI 资源泄漏。正确做法是先用 IsWindowVisible 和 IsWindow 双重校验:
-
IsWindow排除已销毁但句柄未释放的“幽灵窗口” -
IsWindowVisible过滤掉隐藏窗口(如托盘程序主窗、后台工具窗口) - 再调用
GetWindowTextLength判断标题长度是否 > 0,避免空标题干扰
用 GetWindowText 获取标题时注意编码和缓冲区
Windows 窗口标题本质是 Unicode(UTF-16),C++ 中应统一用宽字符处理。若用 GetWindowTextA,在中文系统下可能返回乱码;若用 GetWindowTextW 但目标缓冲区声明为 char buf[256],会导致栈溢出或截断——因为每个 wchar_t 占 2 字节。
实操建议:
- 声明缓冲区为
wchar_t title[512],长度单位是wchar_t个数,不是字节数 - 调用前用
GetWindowTextLengthW(hWnd)获取实际长度,确认不超限 - 不要依赖
GetWindowTextW(hWnd, title, _countof(title))的返回值判断成功与否——它返回的是写入的字符数,0 可能是无标题,也可能是失败,必须结合GetLastError()判断
特征字符串匹配建议用 wcsstr 而非 strstr
既然标题是宽字符串,特征字符串也必须是 const wchar_t*,否则用 strstr 会按字节逐个比对,遇到中文字符必然错位。例如搜索 L"记事本",用 strstr 处理 L"记事本 - 记事本.txt" 会失败,因为 UTF-16 编码中“记”是两个字节 0x8E80,strstr 把它拆成两个独立字节去匹配。
正确方式:
- 确保特征串是宽字面量:
L"notepad"或std::wstring L"Chrome" - 用
wcsstr(title, pattern)做子串查找,它按wchar_t单位比较 - 若需忽略大小写,用
_wcsicmp或std::wstring::find配合std::tolower转换,别用stristr(它不支持宽字符)
回调函数里别做耗时操作或跨线程 UI 调用
EnumWindows 是同步阻塞调用,它的回调函数运行在当前线程上下文中。如果在里面执行文件 I/O、网络请求、或调用 SendMessage 向其他窗口发消息,会导致整个枚举卡顿,甚至死锁(比如目标窗口正在处理消息循环,而你又在它消息队列里塞新消息)。
安全做法:
- 回调里只做轻量操作:检查可见性 → 读标题 → 匹配特征 → 存入
std::vector<hwnd></hwnd>或std::vector<:wstring></:wstring> - 匹配结果留到回调结束后再处理,比如批量打印、启动进程、或 PostMessage 到主线程窗口
- 避免在回调中调用
ShowWindow、SetForegroundWindow等影响 UI 状态的函数
最易被忽略的是窗口生命周期问题:回调拿到的 HWND 在函数返回后可能已被销毁。如果后续要操作该窗口(比如激活、关闭),务必再次调用 IsWindow 校验有效性。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











