项目:32桌面程序,在部分64位系统下,客户端程序中部分模块, 在拖拽或改变窗口大小变化时,出现残影现象。
排查过程历程如下:
测试反馈该问题,自己自测没有问题,很正常, 这点,就思考一下:是不是环境不一样,或者账号不一样,操作不一样,环境不一样等, 这些均要排查,便于问题排查;
不能凭主观和经验主义, 这个很不好!
1,跟进以往经验,怀疑是排版问题,可能某些子控件没有处理里好排版原因,导致残影现象; 以此排查相关控件的累的OnSize()接口时否处理完毕;
排查之后,代码逻辑上均没有任务问题, 并且该处理代码逻辑已经很久没有动过,最后通过带日志,在每个OnSize()里面打上日志, 在自己电脑很正常,日志也都正常,最后和测试确认发现,其出现在64位系统下面;
2, 最后,根据日志确和Spy++ 的分析,确认有些Onsize没有进去,真是奇怪, 居然Spy++使用,看如下链接:
https://blog.csdn.net/dpsying/article/details/51913947;
最后寻求"度娘", 发现这个问题很少有人提及,不过确实有人询问,后来查到官方的描述, 并排查代码逻辑发现如下:
1, 由于部分模块业务比较复杂,导致窗口层数比较深;
2, 为了便于统计用户行为, 采用钩子,获取相关控件的操纵记录;
因为业务模块,比较复杂,按官方介绍的介绍方案,改动比较多, 最后尝试替换钩子的方式,如下:
将之前的SetWindowsHookEX 换成Detours, 替换之后解决该问题!
Windows Hook原理与实现相关链接:
https://blog.csdn.net/m0_37552052/article/details/81453591
官方链接如下:
https://support.microsoft.com/zh-cn/help/2664641/recursive-calls-to-window-manager-functions-may-fail-unexpectedly:
原文有道翻译:
症状:
递归调用USER32导出的窗口管理器函数。DLL可以返回而不执行请求的操作,也不设置错误代码。这通常发生在嵌套很深的窗口层次结构的应用程序中。
你可能会有以下症状:
1,具有深度嵌套窗口层次结构的应用程序在调整框架窗口的大小时无法正确调整子窗口的大小。通过调用MoveWindow、SetWindowPos或DeferWindowPos来移动和/或调整窗口的大小;
2,窗口消息不按预期传播到父窗口或子窗口。DefWindowProc可能无法成功地将消息传播到接收消息的窗口的父窗口或子窗口;
3,通过调用SendMessage、SendMessageTimeout或SendMessageCallback发送到窗口的窗口消息不会被指定窗口接收;
此外,如果在拥有窗口的应用程序的线程上设置了WH_CALLWNDPROC或WH_CALLWNDPROCRET窗口钩子,否则正常运行的应用程序也可能会出现上述症状。通过调用SetWindowsHookEx函数,可以在特定线程或所有UI线程上设置窗口钩子。
导致:
此行为是由于Windows无法增长调用线程的内核堆栈以执行所请求的操作。由于x64 Windows环境中需要额外的内核堆栈处理过程,在对USER32.DLL导出的窗口管理器函数进行递归调用时,可以以比x86 Windows环境更快的速度使用内核堆栈。尽管本文描述的症状更可能出现在x64 Windows平台上,但是在x86 Windows平台上,递归调用可能会使用线程的内核堆栈。
方案:
以下解决方案可用于解决此问题
1,当处理WM_WINDOWPOSCHANGED窗口消息而不是将消息传递给DefWindowProc时,请调整子窗口的大小。
2,在处理WM_WINDOWPOSCHANGED或WM_SIZE窗口消息时,当父窗口被调整大小时,而不是调整子窗口的大小时,异 步地调整子窗口的大小。
3,重新设计应用程序UI以减少嵌套窗口的深度。
更多的信息
Win32子系统的一部分是在内核模式的设备驱动程序(WIN32K.SYS)中实现的。调用USER32导出的函数。DLL改变一个窗口的状态,包括它的大小和位置,将调用到WIN32K。执行所请求的操作。
修改窗口状态的函数通常会将窗口消息发送到正在修改的窗口(其中包含WIN32K)。SYS发出一个用户模式的callout来调用被修改窗口的窗口过程。
例如:
WIN32K。当通过调用SetWindowPos函数修改窗口的大小和/或位置时,SYS将发送一个窗口一个WM_WINDOWPOSCHANGED窗口消息和一个WM_WINDOWPOSCHANGED窗口消息。当使用WM_WINDOWPOSCHANGED消息调用时,DefWindowProc将向指定的窗口发送一条WM_SIZE消息,并且窗口的大小已经更改。当父窗口接收到WM_WINDOWPOSCHANGED或WM_SIZE窗口消息时,应用程序通常会调整子窗口的大小,这将导致对WIN32K进行递归调用。用于深度嵌套的窗口层次结构。
在进程中的线程上设置WH_CALLWNDPROC或WH_CALLWNDPROCRET钩子时,正常运行的应用程序也可能会出现本文描述的症状。这是由于WIN32K会消耗额外的内核堆栈空间。SYS处理钩子子程的调用。
调用SendMessage向调用线程所拥有的窗口发送窗口消息通常会调用接收消息的窗口的窗口过程,而不需要调用WIN32K.SYS。然而,SendMessage将调用WIN32K。如果调用线程上设置了WH_CALLWNDPROC钩子或WH_CALLWNDPROCRET钩子,如WIN32K。SYS管理钩子并处理调用钩子过程。
如上所述,在使用WM_WINDOWPOSCHANGED消息调用指定窗口时,DefWindowProc将发送一个WM_SIZE消息,并且窗口的大小已经更改。WH_CALLWNDPROC钩子或WH_CALLWNDPROCRET将导致SendMessage调用DefWindowProc转换到内核模式,以便调用钩子过程。当处理WM_WINDOWPOSCHANGED窗口消息而不是WM_SIZE窗口消息时,调整子窗口的大小将减少内核堆栈的使用,因为不需要为了调用钩子子程而将SendMessage转换为内核模式。