排查自模拟QWheelEvent事件不能向上传递排查

排查自模拟QWheelEvent消息不能向上传递

在这里插入图片描述

1、事情起因

因为我们的项目需要做一次大升级。从Qt 5.5升级到Qt 5.15.2.0,这个大版本的升级真是把我折腾的够呛。

在我们的项目中,自己模拟了一个鼠标滚轮消息的。利用QApplication::postEvent()函数,向消息队列中发送一个QWheelEvent消息。这样就可以控件就可以自动滚动了。

但是项目升级到Qt 5.15.2.0之后发现不行了。

擦了擦额头的汗,这个bug怎么修啊?无从下手的感觉。

去请教别人?万一,别人觉得我很low怎么办。关键是大佬时间也很宝贵。

没办法,只有自己慢慢的探索了。需要准备好Qt 5.15.2.0 pdb文件和源码。

2、解决方案

我先说原因:

因为升级到高版本之后,Qt的对QWheelEvent事件处理发生了变化。在Qt 5.5中会自动将QEvent::spont属性设置true

而到了高版本之后,则去掉了这样的处理。导致事件不会继续向上处理,所以父控件没有收到消息。

在其文档也进行了说明。

只有系统触发的event,才会设置为true,否则就是false.

Returns true if the event originated outside the application (a system event); otherwise returns false.

接下来就是方案,有两种:

  1. 重写 QApplication::notify(QObject* pObject, QEvent* pEvent)函数中拦截QEvent::Wheel事件,强制设置pEvent->spont属性为true
    case QEvent::Wheel:
    {
        // 检查Qt的版本号
        if (QT_VERSION == QT_VERSION_CHECK(5,15,2))
        {
            //TODO:
            // 此处hack pEvent的数据成员,如果要升级项目此处一定要注意,可能QEvent内存布局发生变化
            // 因为项目升级到5.15.5.0,源码发生很大的变化。
            // 在Qt 5.5中会自动将pEvent->spont 设置为true。(此成员未提供public方法修改)
            // 在Qt 5.15.2.0 只会转发pEvent数据,不做任何的修改。
            // 所以找到pEvent地址,找到成员变量的偏移地址,再修改成员变量的值
            // address = (char *)pEvent + sizeof(vtable ptr) + sizeof(QEventPrivate *d) + sizeof(ushort)
            // 从源码中可以找到QEvent的成员变量的分布
            if (!pEvent->spontaneous())
            {
                char* flag = ((char*)pEvent + sizeof(char *)*2 + sizeof(ushort));
                // 强制设置flag的第二个字节位为1
                *flag |= 0b10;
            }
        }
    }
  1. 重新编译Qt的源码,将设置该属性的函数设置为public。

但是在项目中我没有选择这种方式,因为时间成本问题。

3、排查过程

如果上面的方案解决不了你的问题。你可能需要看下我排查的过程了。

万事开头难,刚开始时,我是一点头绪都没有。

所以我采取的方法就是把项目相关的源码通读一遍,遇到看不懂的地方就跳过去,不重要的也跳过去。

1.确定是自己的bug还是Qt的bug?

​ 因为这个父控件是我们的自定义控件,而且为了适应我们的应用场景,内部逻辑十分复杂。

​ 所以我只能将环境一点点的简化,就是排除法去验证是谁的问题。

  • 当我发现我用鼠标直接去滚轮,而不是模拟鼠标滚动,发现居然是可以的。那我基本排除控件应该没有问题的。但是我不是很肯定,需要再进一步的验证。

  • 我又在父控件中对拦截QWheelEvent消息处下了断点,发现居然没有拦截下来。这几乎可以判断父控件没有问题,就是没有收到QWheelEvent消息,但是问题是为什么收到应有的消息?

​ 当我知道这个原因之后就立马去google下,因为我觉得肯定有人遇到了,但是一无所获。

​ 我觉得这个应该是Qt的bug,所以又去的Qt report bug网站上关键词搜了下,也没有结果。

2.源码和pdb准备

​ 求人不如求己吧。准备跟踪下调用堆栈,看看有没有区别。

​ 我刚开始时根据堆栈不断的去溯源,我就是想看看这个QWheelEvent是从哪里发出来的。它的源头从哪里来的。

​ 这个花了很多的时间,最后我发现就是windows的底层消息,经过Qt的包装一层,不断的往上传递。

​ 其实我也是看的云里雾里的,一知半解。但是大概过程我是了解的。

​ 这种鬼见愁的堆栈,一看就头大。因为消息的发送和处理,完全是异步的过程。

​ 这是自己模拟滚轮消息处理堆栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6juq8ycT-1652922577870)(.\qwheelevent_bug\01_rounder.png)]

​ 在这个过程中,我就在思考这要是Qt的bug,我TM的怎么修复啊。对整个框架的不清楚,就修这个bug,大概率又衍生出其他的 bug。

​ 我开始对比我们自己发送的QWheelEvent消息和系统发送的消息到底有没有什么不同。

​ 我心生一计,我就在QAapplication::notify里面对QWheel拦截,看看这自己发送和系统生成两个消息到底有什么不同。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-isYUcGIS-1652922549741)(.\qwheelevent_bug\05_event_compare_rounder.png)]

​ 经过对比之后,我的确发现这两个消息有些地方是不一样的。但是我没法确切的知道到底是哪两个不一样造成的。

​ 同时,因为不知道到底是哪个不一样。我还去对比了他们的父类数据。这不是重点我就不展开说了。

​ 那就用最笨也是最有用的方法,一个个的试。

​ 我就按照系统发送的消息值,一个个的去设置自模拟的值。经过我艰苦卓绝的实验,发现就只有一个值设置和系统的一样就成功了。

​ 那就是QWheelEvent中的spont属性设置为true。

​ 嘿嘿……高兴极了,于是我就开始查文档,这个属性值调用函数设置下就OK了。

​ 一查文档就傻眼了,这个属性是父类QEvent中的,Qt就没有提供public接口给我设置。这就说明这个属性就是内部使用。

​ 经过短暂的懵逼状态,我想直接把你hack了吧。第二种方案要不和领导申请下编译下这个版本的源码吧,但是这个时间成本较高,直接 被我放弃了。

​ 事情到了本该差不多结束了。

3.再跟源码,到底是哪里不一样。

​ 我虽然修复了这个bug,但是我还是不知道具体的原因。到底是怎么造成的,没有找到更具体的原因,我始终不甘心。

​ 更何况这种修复方案,肯定要和领导说明的。要是没有说服力的证据,很难让领导相信这种方案更好。说不定稳妥起见,可能会编译源 码。

​ 先看Qt 5.5源码堆栈:

​ 在Qt 5.5中最迷惑的就是,在第一次的时候你会发现QEvent::spont属性也是0.只有第二次才是我们想要的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nz7aDs8o-1652922549744)(.\qwheelevent_bug\06_sendspont_rounder.png)]

从堆栈中我看到一个非常重要的函数QCoreApplication::sendSpontaneousEvent,定位到源码看一看。

嚯,一切了然了。原来在这里帮我设置了这个属性值为1(true)了。

inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
{ if (event) event->spont = true; return self ? self->notifyInternal(receiver, event) : false; }

在看Qt 5.15.2.0的。为了看的更清楚,我把自定义的消息堆栈和系统消息的堆栈放在一起对比下。

在这里插入图片描述
​ 从这张图就能明显的看出来,系统发送的消息和我们自定义的消息多调了一个函数QCoreApplication::sendSpontaneousEvent

​ 至此,我才觉得心中出了一口气,原来系统调用了一个函数来设置spont值。我猜测之所以这样做,可能就是为了区别这个消息是不是别人模拟的假消息,而在早期的版本并没有这样的区别。

4.设置spont属性的原理

对于上述为什么需要这样的设置,我解释下。但是你如果熟悉c++对象的内存模型,就应该很好理解。如果不理解,可以先看看我的《C++幕后故事》系类文章。

我画一张内存图方便理解。

这些信息都是从源码获取的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-roE3PXM5-1652922549750)(.\qwheelevent_bug\08_qevent_memory_layout_rounder.png)]
最后,我虽然修复了这个问题。但是我始终心有不安,这样做到底有没有其他的问题。我进行了仔细的分析。

  • 如果你迁移了项目,这个成员变量发生了变化,那么后果不堪设想。

    所以我加上了对版本的判断,如果是其他的版本需要再验证下成员变量分布是否有变化。

  • 指针的大小和ushort大小,我们知道在不同的平台,这些可能不一样大的。

    所以在计算偏移量的时候,我都不是用固定值,而是采取动态计算的方法。

5.反思与总结

排查这个问题,花了几天的时间。

我觉得如何找到切入点是个非常重要的事情。我期间花了很多的时间走了弯路,比如:试图搞清楚整个消息的来龙去脉,结果搞得我灰头土脸,还是太高估自己了。

一定要有全局观,适当的放弃一些细节问题。只有纵览全局,才能不被一些细枝末节缠住。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值