做者:曹群
原文:https://mp.weixin.qq.com/s/Mp...
欢迎关注学而思网校技术团队公众号:html
Qt 是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既能够开发GUI程序,也可用于开发非GUI程序,好比控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,而且容许真正地组件编程。Qt是跨平台开发框架,支持Windows、Linux、MacOS等不一样平台;Qt有大量的开发文档和丰富的API,给开发者带来了很大的方便;Qt的使用者也愈来愈多,有不少优秀的产品都基于Qt开发,如:WPS Offic 、Opera浏览器、Qt Creator等。Qt的核心机制就是信号和槽,接下来咱们经过源代码分析一下实现原理。编程
下面是咱们要分析的Demo代码:
// MainWindow.h数组
// MainWindow.cpp浏览器
咱们能够建立一个Qt工程,名称为Demo,编写上面的代码,进行构建,在VS下能够把Qt工程导成VS工程,编译生成,运行结果以下:安全
点击中间的按钮,咱们能够看到控制台打印以下信息:服务器
咱们分析代码,能够看到在头文件Test和MainWindow类中,都有Q_OBJECT这样的宏,而后咱们能够看到上面的可执行文件夹下多出来一个moc_MainWindow.cpp文件,那么咱们能够尝试把这两个宏去掉,再进行构建,发现加上了信号和槽的就没法编译过去,咱们去掉这些信号和槽后,就不会生成moc开头的这个文件了,固然咱们就没法实现信号和槽机了,那么这个宏究竟是什么,有了它编译器又会作什么?让咱们看看这个宏:数据结构
原来这个宏就是一些静态方法和虚方法,可是若是咱们加入到类中,不进行实现,那必定会报错的,为何还能够正常运行呢?原来Qt帮咱们作了不少事情,在编译器编译Qt代码以前,Qt先将Qt自身扩展的语法进行翻译,这个操做是经过moc(Meta-Object Compiler)又称“元对象编译器”完成的。首先moc会分析源代码,把包含Q_OBJECT的头文件生成为一个C++源文件,这个文件的名字会是源文件名前面加上moc_,以后和原文件一块儿经过编译器处理,那咱们想到,这个moc开头的cpp中必定实现了上面宏里面的方法,以及数据的赋值;接下来咱们看看moc_MainWindow.cpp这个文件:框架
咱们从上面的代码中能够看到,是对Q_OBJECT中的静态数据进行了赋值,而且实现了那些方法,这些都是Qt的moc编译器帮咱们生成的,对代码进行了分析,对信号和槽生成了符号,以及特定的数据结构,下面这个主要是记录了类、信号、槽的引用计数、大小、偏移,后面会用到。函数
经过把QT_MOC_LITERAL这个宏进行替换后,获得以下数据 :工具
接下来咱们看看下面qt_meta_data_MainWindow这个数组结构:content有两列,第一列是总数,第二列是在这个数组中描述开始的索引,如1, 14, // methods,说明有一个methods,咱们能够看到slots就是从索引14开始的。
从最上面的源代码中咱们能够看到再关联信号和槽的时候,用到了SIGNAL和SLOT这两个宏,那么这两个宏到底有什么做用呢?咱们分析一下:
从上面咱们能够看到其实这两个就是一个字符串拼接的宏,会在信号(signal)前面拼接"2",如”2clean()“;会在槽(slots)前面拼接"1",如”1onClean()“; 其中,qFlagLocation这个方法主要是把method存储在QThreadData里面FlaggedDebugSignatures中的const char* locations[Count];表中,用于定位代码对应的行信息。
预编译后以下:
经过上面的一些基本宏、数据结构的介绍,咱们知道Qt给咱们作了不少工做,帮咱们生成了moc代码,给咱们提供了一些宏,让咱们开发简洁方便,那么Qt又是如何把信号和槽进行关联的呢,就是两个不一样的实例,又是如何进行经过信号槽机制进行通讯的呢?接下来咱们看看信号和槽关联的实现原理:
这个方法就是咱们上面moc_MainWindow.cpp中。
咱们根据调试能够看到QObject::d_ptr->metaObject是空的,因此这样smeta就是上面这个staticMetaObject变量了。
// 首先咱们得了解一下这个QMetaObject 和 QMetaObjectPrivate 的定义:
在Qt中为了实现二进制兼容性,通常会定义一个私有类,QMetaObjectPrivate就是QMetaObject的私有类,QMetaObject负责一些接口实现,QMetaObjectPrivate具体进行实现,这两个类通常是经过P指针和D指针进行组合式的访问,有一个宏:
咱们看上面的staticMetaObject是一个QMetaObject类型的变量,其中QMetaObject进行了赋值:
其中QMetaObject 是对外的结构,里面的connect方法最终调用的仍是QMetaObjectPrivate里面的connect进行实现的。QMetaObject里的d成员填充了上面的staticMetaObject数据,而QMetaObjectPrivate里面的成员填充qt_meta_stringdata_Test数组中的数据,咱们能够看到填充前14个数据,这也是moc生成methodData时以14为基数的缘由了,转换方法以下:
具体实现以下:
其中int handle = priv(m->d.data)->methodData + 5i; 咱们能够分析,其实就是14+5i ,那为何是5呢?由于:
// signals: name, argc, parameters, tag, flags
1, 0, 24, 2, 0x06 / Public /,
// slots: name, argc, parameters, tag, flags
3, 0, 25, 2, 0x08 / Private /,
咱们能够看到每个signals或者slots都有5个整形表示。
// MethodFlags是一个枚举类型,咱们能够看到MethodSignal = 0x04, MethodSlot = 0x08;
// slots: name, argc, parameters, tag, flags
3, 0, 25, 2, 0x08 / Private /,
enum ConnectionType { AutoConnection, DirectConnection, QueuedConnection, BlockingQueuedConnection, UniqueConnection = 0x80 };
咱们介绍一些链接类型:
最后到了信号和槽关联核心的地方了:
首先,咱们先得了解如下数据结构:
上面的这三个数据结构很重要,QObject是咱们最熟悉的基类,QObjectPrivate是它的私有类,进行具体实现,QObjectPrivate继承自QObjectData,在QObject里面以组合的形式也进行P指针和D指针的方式进行访问的。在信号和槽关联过程当中,数据结构Connection是很重要的数据结构,下面的这个结构是ConnectionList的一个Vector:
有了上面的数据结构,咱们就能够分析下面的连接过程了, 咱们看到下面的先是调用的QMetaObjectPrivate的connect, 以后又用QMetaObject::Connection进行了指针包装:
QObjectPrivate::get(s) 方法其实就是获取了一个QObjec里面的QObjectPrivate实例,以后调用addConnection方法添加到链表中:
结构以下:
分析:
// 而后进入真正的QMetaObject::activate
咱们的例子是Autoconntion模式,因此就会执行下面的代码进行回调:
咱们终于看到了,函数进行了回调到moc_MainWindow.cpp里面,而后调用对应的槽onClean ;
最终调用到这里后,打印输出:"MainWindow::onClean"
最后就是调用完后,会回到onDestory这里:
注意:若是咱们在onClean中进行了对m_testWidget对象的释放操做(delete m_testWidget),再到onDestory()中 emit clean(); 后面进行访问成员,那么必定崩溃,因此要注意。
一、https://woboq.com/blog/how-qt...
二、Qt5.7源码
三、本身用C++实现的信号和槽demo:http://note.youdao.com/notesh...