逆向通达信 x 逆向微信 x 逆向Qt

本篇在博客园地址https://www.cnblogs.com/bbqzsl/p/18252961

本篇内容包括:

win32窗口嵌入Qt UI。反斗玩转signal-slot。最后 通达信 x 微信 x Qt 做手术。

Qt Alien Widget是一种广义的DirectUI。

在UI技术中,DirectUI和Alien Widget的概念有所重叠,但具体实现和应用场景有所不同。为了更好地理解它们之间的关系,让我们先定义清楚这几个概念。

DirectUI

DirectUI 是一种界面绘制技术,主要特点是通过直接绘制UI元素,而不是依赖操作系统提供的原生控件。这种方式有以下几个特点:

  • 跨平台一致性:不依赖操作系统的控件,因此在不同操作系统上可以实现一致的外观和行为。
  • 高性能:直接绘制可以优化绘制路径,减少不必要的开销,从而提高性能。
  • 高自由度:开发者可以完全控制UI元素的绘制和交互,灵活性更高。

Native Widget

Native Widget 是由操作系统提供和管理的控件,这些控件直接使用操作系统的窗口和绘图资源。

  • 系统控件:直接由操作系统管理和绘制,提供与操作系统一致的外观和行为。
  • 平台相关:在不同的操作系统上,控件的行为和外观可能不同。

Alien Widget

Alien Widget 是指那些不依赖于操作系统提供的控件,而是由应用程序自身实现和管理的控件。在Qt中,默认控件(如QPushButton, QLineEdit等)都是Alien Widget。

  • 跨平台一致性:在不同操作系统上可以实现一致的外观和行为。
  • 独立性:不依赖操作系统的控件。
  • 灵活性和定制性:开发者可以完全控制控件的外观和行为。

Qt的Alien Widget和DirectUI的关系

Qt的Alien Widget可以看作是DirectUI的一种实现,因为它们都强调不依赖操作系统的原生控件,通过直接绘制实现跨平台一致性和高自由度。

主要特点:

  1. 绘制机制:Qt的Alien Widget是通过Qt的绘图系统(如QPainter)来进行绘制的,这类似于DirectUI技术中直接绘制的概念。
  2. 跨平台一致性:由于Qt的Alien Widget不依赖于操作系统的控件,它们在不同操作系统上提供了一致的外观和行为。
  3. 高自由度和定制性:开发者可以使用Qt提供的丰富API来自定义控件的外观和行为。

结论

可以认为Qt的Alien Widget是DirectUI技术的一种实现形式。两者都不依赖于操作系统的原生控件,通过直接绘制来实现跨平台一致性和高自由度。Qt的Alien Widget利用Qt框架的强大功能,实现了与DirectUI类似的目标。因此,从这个角度来看,Qt的Alien Widget确实可以被视为一种DirectUI技术。

Qt界面不必一定依赖QApplication::exec才能运行

Native Widget 和 QWindow 在 Qt 的图形用户界面编程中有不同的用途和特性。以下是对它们的详细对比和解释:

Native Widget

  • 定义:Native Widget 是由操作系统提供和管理的控件。这些控件直接使用操作系统的窗口和绘图资源,不依赖于 Qt 的绘图系统。
  • 特性
    • 系统控件:直接由操作系统管理和绘制,提供与操作系统一致的外观和行为。
    • 不依赖 Qt 事件循环 :可以在不依赖于 QApplication::exec() 的情况下运行,因为它们由操作系统管理事件循环。
    • 平台相关:在不同的操作系统上,控件的行为和外观可能不同。
  • 使用场景:需要与系统深度集成,或使用特定平台功能时使用。例如嵌入系统控件到 Qt 应用程序中。

QWindow

  • 定义:QWindow 是 Qt 的一个抽象类,表示一个独立的窗口。它提供了一个轻量级的窗口对象,可以用于显示内容,但不提供高级的控件功能。
  • 特性
    • Qt 窗口对象:QWindow 是 Qt 提供的窗口类,可以与 Qt 的绘图系统和事件系统无缝集成。
    • 依赖 Qt 事件循环 :QWindow 依赖于 QApplication::exec()QGuiApplication::exec() 来运行事件循环。
    • 跨平台一致性:Qt 提供了一致的 API 和行为,不论运行在什么平台上,Qt 窗口的行为和外观都是一致的。
  • 使用场景:用于创建自定义窗口、渲染 OpenGL/Vulkan 内容或其他需要低级别窗口访问的场景。

比较和联系

  • 事件循环 :Native Widget 不依赖 Qt 的事件循环,可以独立于 QApplication::exec() 运行。而 QWindow 需要 Qt 的事件循环才能正常工作。
  • 管理和绘制:Native Widget 由操作系统管理和绘制,提供原生外观和行为。QWindow 由 Qt 管理,可以使用 Qt 的绘图系统来绘制内容。
  • 灵活性和集成:Native Widget 适用于需要直接访问操作系统功能和外观一致性的场景。QWindow 提供更高的灵活性和跨平台一致性,适用于需要使用 Qt 提供的高级功能和自定义绘图的场景。

结论

Native Widget 和 QWindow 是 Qt 中不同层次的组件,分别适用于不同的使用场景。Native Widget 更适合需要深度集成操作系统功能的应用,而 QWindow 则更适合需要使用 Qt 的绘图和事件系统的应用。两者的选择应根据具体的需求和应用场景来决定。


以上是AI提供的分析。

由上述可知,Alien Widget可以作为一种dui方案使用,为保证跨平台一致性,请使用QApplication::exec()。

复制代码
main()
{
  QApplication app;
  QMainWindow mainWin;
  mainWin.show();
  app.exec();  
}

QApplication跟QMainWindow为固定定式。这个定式能够保证跨平台 一致性。

但是,在windows平台的软件,就有混合使用UI框架的需求。

所以针对windows平台还有一个特殊解决方案:https://github.com/qtproject/qt-solutions/tree/master/qtwinmigrate。

里面包含了三种迁移方案,分别是QMfcApp,QWinHost,QWinWidget。

QMfcApp是方便QApplication跟QMainWindow定式的使用,将QApplication跟CWinApp整合在一起,CWinApp::Run时绑定QApplication::exec。

QWinHost能够将其它框架(不限于MFC)的OS native window嵌入到QWidget。

QWinWidget能够QWidget嵌入到OS native window,以一个native widget hwnd挂到父hwnd。

native widget是由platforms/plugin/qwindows.dll支持的。代码路径在qtbase/src/plugins/platforms/windows,其中qwindowswindow.cpp实现了native window的WndProc,qWindowsWndProc。并将消息转换QtWindows::WindowsEventType由QWindowsContext::windowsProc进行处理。

通达信用的是qt5.5。

QPlatformWindow, qtbase/src/gui/kernel/qplatformwindow.h

QWindowsWindow, qtbase/src/plugins/platforms/windows/qwindowswindow.h

QWindow, qtbase/src/gui/kernel/qwindow.h

QMainWindow, qtbase/src/widgets/widgets/qmainwindow.h

QWidgetWindow, qtbase/src/widgets/kernel/qwidgetwindow_qpa_p.h

|-------------|---------------|------------------|
| QWidget | QSurface | QPlatformSurface |
| QMainWindow | QWindow | QPlatformWindow |
| | QWidgetWindow | QWindowsWindow |
| | "yes, i do." | "you own me?" |

尽管这几个都叫Window,单从名字好容易迷惑,它们不尽是QWindow。QWindow与QPlatformWindow是窗口的两个面,QWindow是跨平台的封装,QPlatformWindow则是底层支持。QWidgetWindow是一个私有类,将QWidget与QWindow能够联系在一起,QWidget从而能够依附在一个底层窗口分派事件。

QApplication::exec只是一个幌,隐藏了许多真相。QWidget分派事件需要的窗口过程是由QPlatformWindow提供支持的,但是Qt事件分派器却依赖QApplication,QApplication是一个QObject,QObject设计成绑定于一个线程上运行。所以使得QWidget依赖于QApplication所绑定的线程。因此一个进程范围内只允许有一个Qt的UI线程,而这个线程跟QApplication绑定在一起。

真相是,只有在QApplication绑定的线程上,QPlatformWindow才能在native窗口的消息循环中正常分派Qt事件。所以运行Qt UI的必要条件是QPlatformWindow,QApplication,并且在同一线程,这个线程上有消息循环。QApplication::exec是充分条件但不是必要的。

QPlatformWindow对应一个系统窗口,在跨平台层对应一个QWindow,其中它的继承类QWidgetWindow可以承载QWidget。QMainWindow是一个QWidget并且自带QWidgetWindow。所以QWindow对应着一个系统窗口。自然地QMainWindow也对应着一个系统窗口,同时成为所有子QWidget的窗口设备根基。迁移解决方案的QWinWidget就是用来替代QMainWindow的位置,为QWidget提供窗口设备根基。

QPlatformWindow由平台相关的qwindows.dll进行支持,在windows平台就是QWindowsWindow。必须依赖QApplication进行Object event filter。而QApplication只能绑定在一个线程上。同一进程同一时间,只能有一个线程上的QPlatformWindow以及Widgets可以正常运行。如果在QApplication之外的线程创建的QPlatformWindow以及Widgets是不能正常分派事件的,当它们需要响应事件的时候,QApplication就会警告"Object event filter cannot be in a different thread."而不能响应。

下图演示,在两个线程分别创建QWinWidget,只有跟随QApplication同一个线程的QWidget能够正常响应事件。

为了演示,我将通达信君的tdxzdview100.dll插入到微信MM的体内,在微信MM体内分别诞生QWinWidget,QWindow,还有QMainWindow。微信MM体内是个没有被MFC污染的洁净环境。演示过程展示了只要有消息循环,就可以运行qt框架的UI。

接下来就是逆向Qt。

Qt以metaObject行了类型反射之实。

定义

反射(Reflection)是一种程序能够在运行时自我检查和操作其结构和行为的机制。通过反射,程序可以动态地获取类型信息,并操作对象、方法、属性等。

涵盖的操作

反射技术涵盖了以下几类操作:

  1. 类型检查和获取元数据

    • 获取类型信息:通过反射,可以在运行时获取对象的类型信息,如类名、方法、属性、字段等。
    • 检查类型成员:检查类型的成员(如方法、属性、事件、构造函数等)的名称、参数、返回类型等信息。
  2. 动态创建对象

    • 实例化类型:通过反射,可以动态地创建类型的实例,而不需要在编译时指定类型。
  3. 调用方法

    • 动态调用方法:可以在运行时调用对象的方法,而不需要在编译时知道方法的名称。
  4. 访问和修改属性和字段

    • 动态访问属性和字段:在运行时读取或修改对象的属性和字段。
  5. 检索和操作特性(注解)

    • 获取和操作特性(注解):通过反射,可以获取和操作应用在类型、方法、属性等上的特性(如C#中的属性,Java中的注解)。

Meta Object可以看作一个小型数据库,提供类型信息。

metacast负责反射类型转换。

metacall是所有反射操作的接口函数。原型类似于ioctl, fcntl。接受包括方法调用,属性访问等。

对于signal-slot的场合,本篇的metacall专指(QMetaObject::Call == InvokeMetaMethod)。

声明过signal跟slot的方法,本质上就是能够通过反射进行调用的方法,我们需要将QObject具体类的成员方法声明为signal或slot,然后还要用moc生成相关的metaobject代码,才能注册到反射。

signal-slot本质就变成了metacall的传递。connect就是对象的metacall连接成拓扑关系,使得metacall可以按拓扑关系进行传递。

signal-slot字面上局限了想象。connect must from signal to slot。然而connect实质是accept from signal to method which can be slot or signal as well.

我们换一下表达式,signal [, signal, ...] - slot,这样就开阔了。

反射不仅提供了类型信息,也可以修改类型信息。简直就是送上门让人逆向。

下图演示通过metaobject查询类型信息,对象方法

下图演示通过跟踪QObject::connect,收集所有signal-slot。这种方法在各Qt版本中比较通用。比较好忽略版本的差异。

下图演示,通过类型反射直接调用对象的slot方法。

下图演示几种修改signal-slot连接关系的方法。通过dynamic meta,或者修改connection,将qt按钮连接到微信按钮,将qt编辑框连接到微信按钮,通过反射来调用按钮的clicked(),调用编辑框的textChanged(QString)。

对于逆向,不需要编译器,不需要头文件,不需要因为slot而去找代码moc编译QObject。

反射的一个特性就是动态修改,那就直接修改。

插入我的slot到signal。signal-slot定式限制了想象,好像只能有一条出路,我必须要有一个slot,方法类型需要对上signal。我必须要写一个QObject达成一个slot,用moc去生成代码。

但是,正如我在上面转换的定式,signal [,signal,... ] -slot,这样一来,signal的下一跳不一定必要是slot,而可以是signal。

拿QPushButton举例,signal clicked() 连接到一个影子QPushButton的signal clicked(),就可以除去一大票工作。

思路一, 通过dynamic meta。

要是我将QPushButton的metacall修改了signal clicked()的处理,必然导致全部QPushButton不能正常工作。意外地,Qt贴心地为我提供了Dynamic MetaObject,解决了我的烦恼。那么我需要moc编译另一个MetaObject吗?不,我以经将moc丢进垃圾桶了。我们可以直接将QPushButton的MetaObject连根复制一份。正所谓CopyOnWrite。我们只要修改副本的qt_static_metacall,将signal clicked()处理修改即可。最后将副本放到影子QPushButton的Dynamic MetaObject。手术完成。缺点是要将其它signal的处理也在同一个qt_static_metacall中修改,不灵活。另外,像clicked()是在父类QAbstractButton中声明为signal的,当其作为connect的receiver端时,并不是用QPushButton的MetaObject::qt_static_metacall分派,而是用QAbstractButton::qt_static_metacall进行分派。十分不方便。这个可以通过QMetaObjectPrivate::connect方法获知继承树中哪个类的MetaObject被选用。

思路二,通过Connection。

Qt5又一贴心设计,QObject::connect返回的不再是冰冷的bool,取而代之是热气腾腾的Connection。不同版本有所差异。但基本三要素没有变,sender, recver, metacall。That's good. Connection还直接指明了metacall函数指针。思路一的工作都可以直接删减了。一个Connection的method_index是固定,所以自已写一个metacall修改起来就简单得多,单一处理,灵活性大大提高。

思路三,自已跟自已。

经过前两次思路进化后,最后我又注意到,影子QPushButton还必要吗?nai, hitsuyu ga nai. 自已跟自已connect,直接省事。谨记,要将修改操作原子地在sender同一线程上进行,不然sender抢先signal一下,也就小狗追尾巴直到天荒地老了。如果需要代码在自已希望的线程上分派,那么还得依靠一个影子QPushButton绑定一个线程。

思路四,Qt5.5也可以connect到Functor。

虽然,Qt5.7才支持connect到Functor。但是在编程时,应用上面的思路,完全可以实现达到目标。

看啦,就这么简单好玩,祝大家也玩得开心。

本篇到这里,下一篇再见。

逆向WeChat(四,mars)

逆向WeChat(三, EventCenter)

逆向WeChat (二, WeUIEngine)

我还有逆向通达信系列

我还有一个K线技术工具项目KTL可以用C++14进行公式,QT,数据分析等开发。

相关推荐
唐诺1 小时前
几种广泛使用的 C++ 编译器
c++·编译器
mahuifa2 小时前
混合开发环境---使用编程AI辅助开发Qt
人工智能·vscode·qt·qtcreator·编程ai
冷眼看人间恩怨2 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客2 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin2 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos4 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室4 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0014 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我584 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
fpcc5 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存