最常见原因是传入错误hwnd或窗口不可见/已销毁;应先用iswindow校验,多线程下需加锁或禁用窗口;uwp/沙箱进程受uipi限制返回空;优先用getwindowtextw;缓冲区宜先调getwindowtextlengthw再动态分配;findwindow易因标题为空或动态变化而失效,推荐enumwindows+getclassname组合。

GetWindowTextA / GetWindowTextW 为什么返回空字符串
最常见的情况是传入了错误的 HWND,或者目标窗口不可见、已被销毁但句柄未失效。Windows API 不校验句柄有效性,GetWindowTextA 在遇到无效句柄或无权访问的窗口时会静默失败,返回长度 0。调用前务必用 IsWindow 做一次确认:
if (!IsWindow(hwnd)) { /* 句柄已失效 */ }- 多线程环境下,
HWND可能在获取后瞬间被销毁,建议加锁或使用EnableWindow(hwnd, FALSE)临时禁用(仅限自己创建的窗口) - UWP 应用、某些沙箱进程(如 edge 浏览器新标签页)的窗口标题受保护,即使句柄有效,
GetWindowText也返回空 —— 这不是 bug,是 Windows 的 UIPI(用户界面特权隔离)机制限制
ANSI 和 Unicode 版本该选哪个
Windows 自 NT 起内部全用 UTF-16,GetWindowTextA 实际会先转成宽字符再转回 ANSI,中间丢字符;而 GetWindowTextW 直接读取窗口的原始文本缓冲区。除非你明确只处理 ASCII 窗口标题(比如老旧控制台程序),否则必须用 GetWindowTextW:
- 项目设置为 Unicode(默认 VS 新建项目)时,
GetWindowText宏自动展开为GetWindowTextW - 若手动调用,声明缓冲区用
wchar_t title[256],而非char title[256] - 注意:
GetWindowTextW返回的是字符数(非字节数),lstrlenW(title)才是真实长度
获取标题时缓冲区大小设多少才安全
Windows 对窗口标题长度没有硬性上限,但实际中极少超过 1024 字符。盲目设大(如 8192)浪费栈空间,设太小(如 32)容易截断。稳妥做法是两步走:
- 先调用
GetWindowTextLengthW(hwnd)获取所需字符数 - 动态分配缓冲区:
std::vector<wchar_t> buf(len + 1); GetWindowTextW(hwnd, buf.data(), (int)buf.size());</wchar_t> - 如果只是简单判断标题是否含某关键词(如 L”notepad”),可先用小缓冲(128)试探,失败再重试 —— 大多数常规窗口标题都在 64 字符内
FindWindow + GetWindowText 组合为何有时找不到目标窗口
FindWindow 匹配的是类名(lpClassName)或窗口名(lpWindowName),而后者正是标题栏文字。但很多程序在创建窗口时不设标题(如后台服务窗口),或运行时动态修改标题(如网页加载中显示“正在连接…”)。更可靠的方式是:
立即学习“C++免费学习笔记(深入)”;
- 用
EnumWindows遍历所有顶级窗口,对每个HWND调用IsWindowVisible+GetWindowTextLengthW快速过滤 - 结合
GetClassName判断窗口类型(例如记事本是Notepad,微信主窗口是WXMainWindow) - 避免依赖标题文字做唯一标识 —— 用户可能改了窗口标题,或多个同名窗口并存(如开 3 个 cmd 窗口,标题都是“命令提示符”)
真正难的不是调用 API,而是理解 Windows 窗口模型的异步性和权限边界。比如一个 HWND 在 EnumWindows 回调里有效,回到主线程时可能已被销毁;又比如以低完整性级别运行的程序,根本无法读取高完整性级别窗口的标题 —— 这些细节不写进日志,也不报错,只能靠经验排查。