必须先调用GetForegroundWindow获取当前前台窗口句柄,再传给GetWindowText读取标题;缓冲区需用StringBuilder且足够大(≥256),声明需指定CharSet.Unicode,避免ANSI乱码或截断。

GetForegroundWindow 和 GetWindowText 配合用才拿得到当前窗口标题
很多人直接调用 GetWindowText 却返回空字符串,是因为它需要一个有效的窗口句柄(HWND),而你没给对。当前“焦点窗口”不是靠遍历猜出来的,得用 GetForegroundWindow 拿句柄,再传给 GetWindowText。
常见错误现象:GetWindowText 返回长度 0,或只拿到几个乱码字符;或者拿到的是上一个已失焦窗口的标题。
- 必须先调用
GetForegroundWindow()获取当前最前窗口句柄,不能假设Process.GetCurrentProcess().MainWindowHandle -
GetWindowText的缓冲区要足够大(至少 256 字符),且必须用StringBuilder,不能用string - 某些 UWP 应用(如邮件、设置)或以高完整性级别运行的进程(如任务管理器)会拒绝返回标题,这是系统限制,不是代码问题
EnumWindows 遍历所有活动窗口时,过滤掉不可见/无标题/系统窗口
直接 EnumWindows 会扫出几百个窗口,包括桌面图标容器、后台通知区域、甚至被隐藏的 IE 内核窗口。不加过滤,结果根本没法用。
使用场景:做窗口管理工具、快捷切换面板、录屏前自动识别目标窗口。
- 回调函数里对每个
hWnd调用IsWindowVisible和IsIconic排除最小化和隐藏窗口 - 用
GetWindowTextLength先查长度,为 0 就跳过——很多系统级窗口(如Shell_TrayWnd)有句柄但没标题 - 用
GetWindowThreadProcessId获取所属进程 ID,再用Process.GetProcessById判断是否是用户可见应用(排除svchost、winlogon等)
C# P/Invoke 声明里,string 参数必须用 MarshalAs(UnmanagedType.LPStr) 或 LPWStr
标题含中文时,如果声明成 string 且没指定字符集,GetWindowText 会按 ANSI 解释 Unicode 字符,结果全是问号或方块。
性能影响:用 LPWStr(对应 UTF-16)是 Windows 默认,零拷贝;LPStr 触发编码转换,慢且易丢字。
- 正确声明示例:
[DllImport("user32.dll", CharSet = CharSet.Unicode)],然后参数写StringBuilder lpString - 绝对不要写
string lpString—— 这会让 .NET 尝试自动封送,失败率极高 - 如果硬要用
LPStr(比如对接旧 ANSI DLL),必须确保系统区域设置匹配,否则中文直接崩
64 位程序调用 32 位兼容性窗口句柄时,IntPtr 大小没问题,但需注意 HWND 是 HANDLE 类型
有人在 x64 下发现 EnumWindows 回调里的 hWnd 看起来像负数,或强制转 int 后高位丢失——这不是 bug,是 IntPtr 在 64 位下占 8 字节,而 HANDLE 本质就是指针大小整数。
容易踩的坑:把 hWnd 存进 int 或 uint 变量,一到 64 位环境就截断;或用 hWnd == 0 判断无效句柄,但其实 IntPtr.Zero 才是标准写法。
- 始终用
IntPtr接收和传递窗口句柄,别降级 - 判断无效句柄用
hWnd == IntPtr.Zero,不是hWnd == 0 -
IsWindow(hWnd)是最稳妥的句柄有效性验证,比任何数值判断都可靠
窗口句柄不是进程 ID,也不是线程 ID;标题不是窗口名,也不是类名;拿到句柄后立刻读标题,中间别穿插其他 UI 操作——焦点可能已经切走了。











