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

可以发现,左侧的编辑框先是获得了焦点(获得焦点的时候会选中全部文字),然后很快被抢走了。
因为之前在开发其它功能时也遇到过焦点被命令编辑框(软件底部中央的编辑框)抢走的情况,所以经过一定的观察后,断定跟上次是一样的问题。由于之前遇到的那个问题是通过其它方式解决的,这次的问题不能用相同的方式解决,于是建议同事跟客户反馈一下,让平台同事帮忙处理一下抢焦点的问题。
再次调查
过了一天,客户那边反馈可以使用某个命令禁止命令编辑框抢焦点。执行这个命令后,发现命令编辑框确实不抢焦点了,但是焦点还是会被抢走。
跟同事了解完相关代码逻辑后,找到了被抢走焦点的编辑框类。该类中包含一个响应函数 OnKillFocus(CWnd* pNewWnd)
。从名字可以看出,该函数是处理失去焦点事件的,该函数只有一个参数,从名字看该参数是新获得焦点的窗口对象指针。于是,果断在这里设置断点,并且设置中断时输出调试信息后继续执行代码。经过几次观察,可以发现,焦点总是被某个固定的窗口抢走。

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

保存调用栈
虽然没能通过 spyxx
找到对应的窗口,但是却发现了一条非常有用的信息 —— 每次焦点都会被同一个窗口抢走。那么可以在 OnKillFocus()
函数内部设置条件断点,当 pNewWnd
是特定值时才中断。这样就可以在目标编辑框失去焦点时中断下来。中断下来后,简单查看调用栈,由于缺少调试符号,没有发现什么有用信息。保存一份完整转储文件,后面可以发给平台同事,让他们帮忙查看具体原因。
提示: 转储文件是程序某个时刻的快照。调试时保存转储文件个非常好的习惯,尤其是那种不轻易重现的问题。好不容易抓到一次,一定要先拍个照。防止调试器意外退出,追悔莫及。懂得都懂,对吧。
继续折腾
之前做读屏软件开发的时候了解到,除了 spyxx
,还有其它工具可以查看窗口信息。比如,可以通过 Inspect, UISpy, accevent
等工具以追踪焦点的方式自动获取当前获得焦点的窗口。但是,这次在使用这几个工具的过程中,发现很大概率会导致主程序发生 StackOverflow
异常(在这里浪费了很长时间)。最后发现,使用 accevent32
,并且只监听 OBJ_FOCUS
事件的时候,不会发生异常。

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

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

这次,终于知道是哪个窗口抢走了焦点,但是为什么主窗口会抢焦点,还是需要平台同事帮忙调查原因了。
为什么 spyxx 找不到窗口
为什么之前在 spyxx
里输入窗口句柄,但是却找不到对应的窗口呢?在 vs
输出窗口中显示的窗口句柄是 0x0000000000541376
,在 spyxx
中输入的也是 0x0000000000541376
,没道理找不到。但是在 accevent
里看到的窗口句柄是 00541376
,难道在spyxx
中输入 00541376
就可以查找到了?赶紧试试。

果然,输入 00541376
是可以找到对应的窗口的。但是当我尝试使用 0x00541376
查找窗口时,却无法找到匹配的窗口。看来,在 spyxx
中通过输入句柄查找窗口时不能输入 0x
前缀。
反思
在整个过程中有一点做的特别不好:本来可以修改 OnKillFocus()
函数中的代码,多打印一些信息,比如获取窗口范围,窗口类名等信息。这样就可以直接在代码里定位到是哪个窗口抢走了焦点。但是根据之前的经验,编译一次比较耗时,所以潜意识里不想修改代码,只是想着通过为断点设置输出信息的方式打印了一些辅助信息。
总结
Inspect, UISpy, accevent
等工具也是一种可以查看窗口信息的工具,在某些情况下可能非常有用。
在 spyxx
中通过输入窗口句柄查找窗口时不能输入 0x
前缀,否则会出现找不到窗口的情况。
更多【记一次有教益的焦点窗口查找过程—— 一定注意 spy++ 的这个坑】相关视频教程:www.yxfzedu.com