排错实战 | 记一次有教益的焦点窗口查找过程

缘起

前一阵子,同事遇到了一个诡异的 bug,新版本发出来后之前运行好好的功能不好使了。原来的逻辑是:点击板上某个埋件的时候,会弹出四个定位编辑框,其中的一个编辑框需要获得焦点,方便用户直接修改,按 tab 会切换到下一个编辑框。但是新程序的行为发生了变化——点击埋件的时候,四个编辑框没有一个获得焦点。本文记录了使用 spyxxaccevent.exe 定位此问题的过程。

初步调查

同事演示了一下现象,大概现象如下:

抢焦点

可以发现,左侧的编辑框先是获得了焦点(获得焦点的时候会选中全部文字),然后很快被抢走了。

因为之前在开发其它功能时也遇到过焦点被命令编辑框(软件底部中央的编辑框)抢走的情况,所以经过一定的观察后,断定跟上次是一样的问题。由于之前遇到的那个问题是通过其它方式解决的,这次的问题不能用相同的方式解决,于是建议同事跟客户反馈一下,让平台同事帮忙处理一下抢焦点的问题。

再次调查

过了一天,客户那边反馈可以使用某个命令禁止命令编辑框抢焦点。执行这个命令后,发现命令编辑框确实不抢焦点了,但是焦点还是会被抢走。

跟同事了解完相关代码逻辑后,找到了被抢走焦点的编辑框类。该类中包含一个响应函数 OnKillFocus(CWnd* pNewWnd)。从名字可以看出,该函数是处理失去焦点事件的,该函数只有一个参数,从名字看该参数是新获得焦点的窗口对象指针。于是,果断在这里设置断点,并且设置中断时输出调试信息后继续执行代码。经过几次观察,可以发现,焦点总是被某个固定的窗口抢走。

OnKillFocus

既然焦点总被固定的窗口抢走,说明这个窗口是固定存在的,不是临时窗口。既然是固定存在的窗口,就可以通过 spyxx 查看到底是哪个窗口。但是当我在 spyxx 中输入这个窗口句柄时,却怎么也找不到这个句柄对应的窗口!真是怪事!

can-not-find-this-window

保存调用栈

虽然没能通过 spyxx 找到对应的窗口,但是却发现了一条非常有用的信息 —— 每次焦点都会被同一个窗口抢走。那么可以在 OnKillFocus() 函数内部设置条件断点,当 pNewWnd 是特定值时才中断。这样就可以在目标编辑框失去焦点时中断下来。中断下来后,简单查看调用栈,由于缺少调试符号,没有发现什么有用信息。保存一份完整转储文件,后面可以发给平台同事,让他们帮忙查看具体原因。

提示: 转储文件是程序某个时刻的快照。调试时保存转储文件个非常好的习惯,尤其是那种不轻易重现的问题。好不容易抓到一次,一定要先拍个照。防止调试器意外退出,追悔莫及。懂得都懂,对吧。

其实,这个问题调查到这一步已经够了。因为这个问题已经很明确了(焦点被其它窗口抢走了),而且外网没有相关代码,只能等平台同事处理。但是,我特别好奇到底是哪个窗口把焦点抢走了,为什么在调试输出中看到焦点每次都被同一个窗口抢走,但是在 spyxx 中却查不到这个窗口。

继续折腾

之前做读屏软件开发的时候了解到,除了 spyxx,还有其它工具可以查看窗口信息。比如,可以通过 Inspect, UISpy, accevent 等工具以追踪焦点的方式自动获取当前获得焦点的窗口。但是,这次在使用这几个工具的过程中,发现很大概率会导致主程序发生 StackOverflow 异常(在这里浪费了很长时间)。最后发现,使用 accevent32,并且只监听 OBJ_FOCUS 事件的时候,不会发生异常。

accevent32-setting

按上图设置好之后,点击 OK 按钮,即可监听焦点变化事件。终于抓到了这个偷抢焦点的窗口。

accevent-watch-focused-window

好像抢焦点的是主窗口?赶紧用 spyxx 查看主窗口句柄,果然主窗口的句柄是 00541376,与使用 accevent 捕获到的窗口句柄是一样的。view-top-window-by-spyxx

这次,终于知道是哪个窗口抢走了焦点,但是为什么主窗口会抢焦点,还是需要平台同事帮忙调查原因了。

为什么 spyxx 找不到窗口

为什么之前在 spyxx 里输入窗口句柄,但是却找不到对应的窗口呢?在 vs 输出窗口中显示的窗口句柄是 0x0000000000541376,在 spyxx 中输入的也是 0x0000000000541376,没道理找不到。但是在 accevent 里看到的窗口句柄是 00541376,难道在spyxx 中输入 00541376 就可以查找到了?赶紧试试。

find-window-by-spyxx

果然,输入 00541376 是可以找到对应的窗口的。但是当我尝试使用 0x00541376 查找窗口时,却无法找到匹配的窗口。看来,在 spyxx 中通过输入句柄查找窗口时不能输入 0x 前缀。

反思

在整个过程中有一点做的特别不好:本来可以修改 OnKillFocus() 函数中的代码,多打印一些信息,比如获取窗口范围,窗口类名等信息。这样就可以直接在代码里定位到是哪个窗口抢走了焦点。但是根据之前的经验,编译一次比较耗时,所以潜意识里不想修改代码,只是想着通过为断点设置输出信息的方式打印了一些辅助信息。

总结

  • Inspect, UISpy, accevent 等工具也是一种可以查看窗口信息的工具,在某些情况下可能非常有用。

  • spyxx 中通过输入窗口句柄查找窗口时不能输入 0x 前缀,否则会出现找不到窗口的情况。

BianChengNan wechat
扫描左侧二维码关注公众号,扫描右侧二维码加我个人微信:)
0%