QObject这个class是QT对象模型的核心,绝大部分的 QT 类都是从这个类继承而来。这个模型的中心特征就是一个叫做信号和槽(signal and slot)的机制来实现对象间的通讯,你可以把一个信号和另一个槽通过 connect(„) 方法连接起来,并可以使用 disconnect(„) 方法来断开这种连接,你还可以通过调用 blockSignal(„) 这个方法来临时的阻塞信号.
QObject 把它们自己组织在对象树中。当你创建一个 QObject 并使用其它对象作为父对象时,这个对象会自动添加到父对象的
children() list 中。父对象拥有这个对象,比如,它将在它的析构函数中自动删除它所有的 child 对象。你可以通过 findChild() 或者 findChildren()函数来查找一个对象。
每个对象都有一个对象名称(objectName())和类名称(class name), 他们都可以通过相应的 metaObject 对象来获得。你还可以通过 inherits()方法来判断一个对象的类是不是从另一个类继承而来。
当对象被删除时,它发出destroyed()信号。你可以捕获这个信号来避免对QObject的无效引用。
QObject可以通过event()接收事件并且过滤其它对象的事件。详细情况请参考installEventFilter()和 eventFilter()。 对于每一个实现了信号、槽和属性的对象来说,Q_OBJECT 宏都是必须要加上的。
QObject 实现了这么多功能,那么,它是如何做到的呢?让我们通过它的 Source Code 来解开这个秘密吧。 QObject类的实现文件一共有四个.
qobject.h,QObject class 的基本定义,也是我们一般定义一个类的头文件. qobject.cpp,QObject class 的实现代码基本上都在这个文件.
qobjectdefs.h,这个文件中最重要的东西就是定义了 QMetaObject class,这个class是为了实现 signal、slot、properties,的核心部分。
qobject_p.h,这个文件中的 code 是辅助实现 QObject class 的,这里面最重要的东西是定义了一个 QObjectPrivate 类来存储 QOjbect 对象的成员数据。
理解这个 QObjectPrivate class 又是我们理解 QT kernel source code 的基础,这个对象包含了每一个 QT 对象中的数据成员,好了,让我们首先从理解 QObject 的数据存储代码开始我们的 QT Kernel Source Code 之旅。
二:元对象系统(Meta-Object System)
从本节开始,我们讲解 QT Meta-Object System 的功能,以及实现。
在使用 QT 开发的过程中,大量的使用了 signal 和 slot. 比如,响应一个 button 的 click 事件,我们一般都写如下的代码:
class MyWindow : public QWidget {
Q_OBJECT public:
MyWindow(QWidget* parent) : QWidget(parent) {
QPushButton* btnStart = new QPushButton(“start”, this);
connect(btnStart, SIGNAL(clicked()), this, SLOT(slotStartClicked())); }
private slots:
void slotStartClicked(); };
void MyWindow:: slotStartClicked() {
// 省略 }
在这段代码中,我们把 btnStart 这个 button 的clicked() 信号和 MyWindow 的 slotStartClicked() 这个槽相连接,当 btnStart 这个 button 被用户按下(click)的时候,就会发出一个 clicked() 的信号,然后,MyWindow:: slotStartClicked() 这个 slot 函数就会被调用用来响应 button 的 click 事件。
这段代码是最为典型的 signal/slot 的应用实例,在实际的工作过程中,signal/slot 还有更为广泛的应用。准确的说,signal/slot 是QT提供的一种在对象间进行通讯的技术,那么,这个技术在QT 中是如何实现的呢?
这就是 QT 中的元对象系统(Meta Object System)的作用,为了更好的理解它,让我先来对它的功能做一个回顾,让我们一起来揭开它神秘的面纱。
Meta-Object System 的基本功能
Meta Object System 的设计基于以下几个基础设施:
QObject 类
作为每一个需要利用元对象系统的类的基类 Q_OBJECT 宏,
定义在每一个类的私有数据段,用来启用元对象功能,比如,动态属性,信号和槽 元对象编译器moc (the Meta Object Complier),
moc 分析C++源文件,如果它发现在一个头文件(header file)中包含Q_OBJECT 宏定义,然后动态的生成另外一个C++源文件,这个新的源文件包含 Q_OBJECT 的实现代码,这个新的 C++ 源文件也会被编译、链接到这个类的二进制代码中去,因为它也是这个类的完整的一部分。通常,这个新的C++ 源文件会在以前的C++ 源文件名前面加上 moc_ 作为新文件的文件名。其具体过程如下图所示: Moc分析时,需要判断源代码中的Q_OBJECT、Q_PROPERTY、Q_ENUMS、Q_CLASSINFO、slots、slot、emit等信息,并生成元对象;但这些关键字大部分C++编译器并不认识(Q_OBJECT除外),他们都被define为空。 Qt的信号槽机制其实就是按照名称查表
除了提供在对象间进行通讯的机制外,元对象系统还包含以下几种功能:
QObject::metaObject() 方法
它获得与一个类相关联的 meta-object QMetaObject::className() 方法
在运行期间返回一个对象的类名,它不需要本地C++编译器的RTTI(run- time type information)支持 QObject::inherits() 方法
它用来判断生成一个对象类是不是从一个特定的类继承出来,当然,这必须是在QObject 类的直接或者间接派生类当中 QObject::tr() and QObject::trUtf8() 这两个方法为软件的国际化翻译字符串
QObject::setProperty() and QObject::property() 这两个方法根据属性名动态的设置和获取属性值
除了以上这些功能外,它还使用qobject_cast()方法在QObject类之间提供动态转换,qobject_cast()方法的功能类似于标准 C++的dynamic_cast(),但是qobject_cast()不需要RTTI的支持,
在一个QObject类或者它的派生类中,我们可以不定义Q_OBJECT宏。如果我们在一个类中没有定义Q_OBJECT宏,那么在这里所提到的相应的功能在这个类中也不能使用,从meta-object的观点来说,一个没有定义Q_OBJECT宏的类与它最接近的那个祖先类是相同的,那就是所,QMetaObject::className()方法所返回的名字并不是这个类的名字,而是与它最接近的那个祖先类的名字。所以,我们强烈建议,任何从QObject继承出来的类都定义Q_OBJECT 宏。
下一节,我们来了解另一个重要的工具:Meta-Object Compiler
三:元对象编译器 - Meta Object Compiler (moc)
元对象编译器用来处理QT的C++扩展,moc 分析C++源文件,如果它发现在一个头文件(header file)中包含Q_OBJECT 宏定义,然后动态的生成另外一个C++源文件,这个新的源文件包含 Q_OBJECT 的实现代码,这个新的 C++源文件也会被编译、链接到这个类的二进制代码中去,因为它也是这个类的完整的一部分。通常,这个新的C++ 源文件会在以前的C++源文件名前面加上 moc_ 作为新文件的文件名。 如果使用qmake工具来生成Makefile文件,所有需要使用moc的编译规则都会给自动的包含到Makefile文件中,所以对程序员来说不需要直接的使用moc
除了处理信号和槽之外,moc还处理属性信息,Q_PROPERTY()宏定义类的属性信息,而Q_ENUMS()宏则定义在一个类中的枚举类型列表。 Q_FLAGS()宏定义在一个类中的flag枚举类型列表,Q_CLASSINFO()宏则允许你在一个类的meta信息中插入name/value 对。 由moc所生成的文件必须被编译和链接,就象你自己写的另外一个C++文件一样,否则,在链接的过程中就会失败。
Code example:
class MyClass : public QObject {
Q_OBJECT
Q_PROPERTY(Priority priority READ priority WRITE setPriority) Q_ENUMS(Priority)
Q_CLASSINFO(\"Author\ Q_CLASSINFO(\"Status\
public:
enum Priority { High, Low, VeryHigh, VeryLow };
MyClass(QObject *parent = 0); virtual ~MyClass();
void setPriority(Priority priority); Priority priority() const; };
四:Signal; Slot
本节介绍Signal和slot的基本知识。
信号和槽是用来在对象间通讯的方法,当一个特定事件发生的时候,signal会被 emit 出来,slot 调用是用来响应相应的 signal 的。 QT 对象已经包含了许多预定义的 signal,但我们总是可以在派生类中添加新的 signal。
QT 对象中也已经包含了许多预定义的 slog,但我们可以在派生类中添加新的 slot 来处理我们感兴趣的 signal signal 和 slot 机制是类型安全的,signal 和 slot必须互相匹配(实际上,一个solt的参数可以比对应的signal的参数少,因为它可以忽略多余的参数)。signal 和 slot是松散的配对关系,发出signal的对象不关心是那个对象链接了这个signal,也不关心是那个或者有多少slot链接到了这个 signal。QT的signal 和 slot机制保证了,如果一个signal和slot相链接,slot会在正确的时机被调用,并且是使用正确的参数。Signal和slot都可以携带任何数量和类型的参数,他们都是类型安全的。
所有从QObject直接或者间接继承出来的类都能包含信号和槽,当一个对象的状态发生变化的时候,信号就可以被emit出来,这可能是某个其它的对象所关心的。这个对象并不关心有那个对象或者多少个对象链接到这个信号了,这是真实的信息封装,它保证了这个对象可以作为一个软件组件来被使用。
槽(slot)是用来接收信号的,但同时他们也是一个普通的类成员函数,就象一个对象不关心有多少个槽链接到了它的某个信号,一个对象也不关心一个槽链接了多少个信号。这保证了用QT创建的对象是一个真实的的软件组件。
一个信号可以链接到多个槽,一个槽也可以链接多个信号。同时,一个信号也可以链接到另外一个信号。
所有使用了信号和槽的类都必须包含 Q_OBJECT 宏,而且这个类必须从QObject类派生(直接或者间接派生)出来,
当一个signal被emit出来的时候,链接到这个signal的slot会立刻被调用,就好像是一个函数调用一样。当这件事情发生的时候,signal和slot机制与GUI的事件循环完全没有关系,当所有链接到这个signal的slot执行完成之后,在 emit 代码行之后的代码会立刻被执行。当有多个slot链接到一个signal的时候,这些slot会一个接着一个的、以随机的顺序被执行。
Signal代码会由 moc 自动生成,开发人员一定不能在自己的C++代码中实现它,并且,它永远都不能有返回值。 Slot其实就是一个普通的类函数,并且可以被直接调用,唯一特殊的地方是它可以与signal相链接。
C++的预处理器更改或者删除 signal, slot, emit 关键字,所以,对于C++编译器来说,它处理的是标准的C++源文件。
五:Meta Object Class
前面我们介绍了Meta Object的基本功能,和它支持的最重要的特性之一:Signal & Slot的基本功能。现在让我们来进入 Meta Object 的内部,看看它是如何支持这些能力的。
Meta Object 的所有数据和方法都封装在一个叫QMetaObject 的类中。它用来查询一个 QT 类的 meta 信息,meta信息包含以下几种,: * 信号表(signal table),其中有这个对应的 QT 类的所有Signal的名字 * 槽表(slot table),其中有这个对应的QT类中的所有Slot的名字。 * 类信息表(class info table),包含这个QT类的类型信息
* 属性表(property table),其中有这个对应的QT类中的所有属性的名字。 * 指向parent meta object的指针(pointers to parent meta object)
QMetaObject 对象与 QT 类之间的关系:
每一个QMetaObject对象包含了与之相对应的一个QT类的元信息
每一个QT类(QObject 以及它的派生类) 都有一个与之相关联的静态的(static) QMetaObject 对象(注:class的定义中必须有 Q_OBJECT 宏,否则就没有这个Meta Object)
每一个 QMetaObject 对象保存了与它相对应的 QT 类的父类的 QMetaObject 对象的指针。或者,我们可以这样说:“每一个QMetaObject对象都保存了一个其父亲(parent)的指针”.注意:严格来说,这种说法是不正确的,最起码是不严谨的。
Q_OBJECT宏
Meta Object 的功能实现,这个宏立下了汗马功劳。首先,让我们来看看这个宏是如何定义的:
#define Q_OBJECT \\ public: \\
Q_OBJECT_CHECK \\
static const QMetaObject staticMetaObject; \\ virtual const QMetaObject *metaObject() const; \\ virtual void *qt_metacast(const char *); \\ QT_TR_FUNCTIONS \\
virtual int qt_metacall(QMetaObject::Call, int, void **); \\
private:
这里,我们先忽略Q_OBJECT_CHECK 和QT_TR_FUNCTIONS 这两个宏。
我们看到,首先定义了一个静态类型的类变量staticMetaObject,然后有一个获取这个对象指针的方法metaObject()。这里最重要的就是类变量staticMetaObject 的定义。这说明所有的 QObject 的对象都会共享这一个staticMetaObject 类变量,靠它来完成所有信号和槽的功能,所以我们就有必要来仔细的看看它是怎么回事了。
六:QMetaObject class data members
我们来看一下QMetaObject的定义,我们先看一下QMetaObject对象中包含的成员数据。
1. struct Q_CORE_EXPORT QMetaObject 2. {
3. // ......
4. struct { // private data
5. const QMetaObject *superdata; 6. const char *stringdata; 7. const uint *data;
8. const void *extradata; 9. } d; 10.};
上面的代码就是QMetaObject类所定义的全部数据成员。就是这些成员记录了所有signal,slot,property,class information这么多的信息。下面让我们来逐一解释这些成员变量:
const QMetaObject *superdata:
这个变量指向与之对应的QObject类的父类,或者是祖先类的QMetaObject对象。
如何理解这一句话呢?我们知道,每一个QMetaObject对象,一定有一个与之相对应的QObject类(或者由其直接或间接派生出的子类),注意:这里是类,不是对象。
那么每一个QObject类(或其派生类)可能有一个父类,或者父类的父类,或者很多的继承层次之前的祖先类。或者没有父类(QObject)。那么 superdata 这个变量就是指向与其最接近的祖先类中的QMetaObject对象。对于QObject类QMetaObject对象来说,这是一个NULL指针,因为 QObject没有父类。
下面,让我们来举例说明:
1. class Animal : public QObject 2. {
3. Q_OBJECT
4. //............. 5. }; 6.
7. class Cat : public Animal 8. {
9. Q_OBJECT
10. //............. 11.}
那么,Cat::staticMetaObject.d.superdata 这个指针变量指向的对象是 Animal::staticMetaObject 而Animal::staticMetaObject.d.superdata 这个指针变量指向的对象是 QObject::staticMetaObject. 而 QObject::staticMetaObject.d.superdat 这个指针变量的值为 NULL。
但如果我们把上面class的定义修改为下面的定义,就不一样了:
1. class Animal : public QObject 2. {
3. // Q_OBJECT,这个 class 不定义这个 4. //............. 5. }; 6.
7. class Cat : public Animal
8. {
9. Q_OBJECT
10. //............. 11.}
那么,Cat::staticMetaObject.d.superdata 这个指针变量指向的对象是 QObject::staticMetaObject 因为 Animal::staticMetaObject 这个对象是不存在的。
const char *stringdata:
顾名思义,这是一个指向string data的指针。但它和我们平时所使用的一般的字符串指针却很不一样,我们平时使用的字符串指针只是指向一个字符串的指针,而这个指针却指向的是很多个字符串。那么它不就是字符串数组吗?哈哈,也不是。因为C++的字符串数组要求数组中的每一个字符串拥有相同的长度,这样才能组成一个数组。那它是不是一个字符串指针数组呢?也不是,那它到底是什么呢?让我们来看一看它的具体值,还是让我们以QObject这个class的QMetaObject为例来说明 吧。
下面是QObject::staticMetaObject.d.stringdata指针所指向的多个字符串数组,其实它就是指向一个连续的内存区,而这个连续的内存区中保存了若干个字符串。
1. static const char qt_meta_stringdata_QObject[] = 2. {
3. \"QObject\\0\\0destroyed(QObject*)\\0destroyed()\\0\" 4. \"deleteLater()\\0_q_reregisterTimers(void*)\\0\"
5. \"QString\\0objectName\\0parent\\0QObject(QObject*)\\0\" 6. \"QObject()\\0\" 7. };
这个字符串都是些什么内容呀?有,Class Name, Signal Name, Slot Name, Property Name。看到这些大家是不是觉得很熟悉呀,对啦,他们就是Meta System所支持的最核心的功能属性了。
既然他们都是不等长的字符串,那么Qt是如何来索引这些字符串,以便于在需要的时候能正确的找到他们呢?第三个成员正式登场了。 const uint *data;
这个指针本质上就是指向一个正整数数组,只不过在不同的object中数组的长度都不尽相同,这取决于与之相对应的class中定义了多少 signal,slot,property。
这个整数数组的的值,有一部分指出了前一个变量(stringdata)中不同字符串的索引值,但是这里有一点需要注意的是,这里面的数值并不是直接标明了每一个字符串的索引值,这个数值还需要通过一个相应的算法计算之后,才能获得正确的字符串的索引值。
下面是QObject::staticMetaObject.d.data指针所指向的正整数数组的值。
1. static const uint qt_meta_data_QObject[] = 2. {
3. // content:
4. 2, // revision 5. 0, // classname 6. 0, 0, // classinfo 7. 4, 12, // methods 8. 1, 32, // properties 9. 0, 0, // enums/sets 10. 2, 35, // constructors 11.
12.// signals: signature, parameters, type, tag, flags 13. 9, 8, 8, 8, 0x05, 14. 29, 8, 8, 8, 0x25, 15.
16.// slots: signature, parameters, type, tag, flags 17. 41, 8, 8, 8, 0x0a, 18. 55, 8, 8, 8, 0x08, 19.
20.// properties: name, type, flags 21. 90, 82, 0x0a095103,
22.
23.// constructors: signature, parameters, type, tag, flags 24. 108, 101, 8, 8, 0x0e, 25. 126, 8, 8, 8, 0x2e, 26.
27. 0 // eod 28.};
简单的说明一下,
第一个section,就是 //content 区域的整数值,这一块区域在每一个QMetaObject的实体对象中数量都是相同的,含义也相同,但具体的值就不同了。专门有一个struct定义了这 个section,其含义在上面的注释中已经说的很清楚了。
1. struct QMetaObjectPrivate 2. {
3. int revision; 4. int className;
5. int classInfoCount, classInfoData; 6. int methodCount, methodData; 7. int propertyCount, propertyData; 8. int enumeratorCount, enumeratorData; 9. int constructorCount, constructorData; 10.};
这个 struct 就是定义第一个secton的,和上面的数值对照一下,很清晰,是吧?
第二个section,以 // signals 开头的这段。这个section中的数值指明了QObject这个class包含了两个signal,
第三个section,以 // slots 开头的这段。这个section中的数值指明了QObject这个class包含了两个slot。
第四个section,以 // properties 开头的这段。这个section中的数值指明了QObject这个class包含有一个属性定义。
第五个section,以 // constructors 开头的这段,指明了QObject这个class有两个constructor。
const void *extradata;
这是一个指向QMetaObjectExtraData数据结构的指针,关于这个指针,这里先略过。
对于每一个具体的整数值与其所指向的实体数据之间的对应算法,实在是有点儿麻烦,这里就不讲解细节了,有兴趣的朋友自己去读一下源代码,一定会有很多发现。
七:connect, 幕后的故事
我们都知道,把一个signal和slot连接起来,需要使用QObject类的connect方法,它的作用就是把一个object的 signal和另外一个object的slot连接起来,以达到对象间通讯的目的。
connect 在幕后到底都做了些什么事情?为什么emit一个signal后,相应的slot都会被调用?好了,让我们来逐一解开其中的谜团。
SIGNAL 和 SLOT 宏定义
我们在调用connect方法的时候,一般都会这样写:
obj.connect(&obj, SIGNAL(destroyed()), &app, SLOT(aboutQt()));
我们看到,在这里signal和slot的名字都被包含在了两个大写的SIGNAL和SLOT中,这两个是什么呢?原来SIGNAL 和 SLOT 是Qt定义的两个宏。好了,让我们先来看看这两个宏都做了写什么事情:
这里是这两个宏的定义:
# define SLOT(a) \"1\"#a # define SIGNAL(a) \"2\"#a
原来Qt把signal和slot都转化成了字符串,并且还在这个字符串的前面加上了附加的符号,signal前面加了’2’,slot前面加了’1’。 也就是说,我们前面写了下面的connect调用,在经过编译器预处理之后,就便成了: obj.connect(&obj, \"2destroyed()\
当connect函数被调用了之后,都会去检查这两个参数是否是使用这两个宏正确的转换而来的,它检查的根据就是这两个前置数字,是否等于1或者是2,如果不是,connect函数当然就会失败啦!
然后,会去检查发送signal的对象是否有这个signal,方法就是查找这个对象的class所对应的staticMetaObject对象中所包含 的
d.stringdata所指向的字符串中是否包含这个signal的名字,在这个检查过程中,就会用到d.data所指向的那一串整数,通过这些整数值来计算每一个具体字符串的起始地址。同理,还会使用同样的方法去检查slot,看响应这个signal的对象是否包含有相应的slot。这两个检查的任何一个如果失败的话,connect函数就失败了,返回false.
前面的步骤都是在做一些必要的检查工作,下一步,就是要把发送signal的对象和响应signal的对象关联起来。在QObject的私有数据类 QObjectPrivate中,有下面这些数据结构来保存这些信息:
1. class QObjectPrivate : public QObjectData 2. {
3. struct Connection 4. {
5. QObject *receiver; 6. int method;
7. uint connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking 8. QBasicAtomicPointer 11. typedef QList 13. QObjectConnectionListVector *connectionLists; 14. 15. struct Sender 16. { 17. QObject *sender; 18. int signal; 19. int ref; 20. }; 21. 22. QList 在发送signal的对象中,每一个signal和slot的connection,都会创建一个 QObjectPrivate::Connection对象,并且把这个对象保存到connectionList这个Vector里面去。 在响应signal的对象中,同样,也是每一个signal和slot的connection,都会一个创建一个Sender对象,并且把这个对象附加在 Senders这个列表中。 以上就是connect的过程,其中,创建QObjectPrivate::Connection对象和Sender对象的过程有一点点复杂,需要仔细思考才可以,有兴趣的朋友可以去读一下源代码。 八:emit,幕后的故事 当我们写下一下emit signal代码的时候,与这个signal相连接的slot就会被调用,那么这个调用是如何发生的呢?让我们来逐一解开其中的谜团。 让我们来看一段例子代码: 1. class ZMytestObj : public QObject 2. { 3. Q_OBJECT 4. signals: 5. void sigMenuClicked(); 6. void sigBtnClicked(); 7. }; MOC编译器在做完预处理之后的代码如下: 1. // SIGNAL 0 2. void ZMytestObj::sigMenuClicked() 3. { 4. QMetaObject::activate(this, &staticMetaObject, 0, 0); 5. } 6. 7. // SIGNAL 1 8. void ZMytestObj::sigBtnClicked() 9. { 10. QMetaObject::activate(this, &staticMetaObject, 1, 0); 11.} 哈哈,看到了吧,每一个signal都会被转换为一个与之相对应的成员函数。也就是说,当我们写下这样一行代码: emit sigBtnClicked(); 当程序运行到这里的时候,实际上就是调用了void ZMytestObj::sigBtnClicked() 这个函数。 大家注意比较这两个函数的函数体, void ZMytestObj::sigMenuClicked() void ZMytestObj::sigBtnClicked(), 它们唯一的区别就是调用 QMetaObject::activate 函数时给出的参数不同,一个是0,一个是1,它们的含义是什么呢?它们表示是这个类中的第几个signal被发送出来了,回头再去看头文件就会发现它们就是在这个类定义中,signal定义出现的顺序,这个参数可是非常重要的,它直接决定了进入这个函数体之后所发生的事情。 当执行流程进入到QMetaObject::activate函数中后,会先从connectionLists这个变量中取出与这个signal相对应的 connection list,它根据的就是刚才所传入进来的signal index。这个connection list中保存了所有和这个signal相链接的slot的信息,每一对connection(即:signal 和 slot 的连接)是这个list中的一项。 在每个一具体的链接记录中,还保存了这个链接的类型,是自动链接类型,还是队列链接类型,或者是阻塞链接类型,不同的类型处理方法还不一样的。这里,我们就只说一下直接调用的类型。 对于直接链接的类型,先找到接收这个signal的对象的指针,然后是处理这个signal的slot的index,已经是否有需要处理的参数,然后就使用这些信息去调用receiver的qt_metcall 方法。 在qt_metcall方法中就简单了,根据slot的index,一个大switch语句,调用相应的slot函数就OK了。 九:Qt信号和槽机制源码分析 1.示例代码: #include int main(int argc, char *argv[]) { QApplication app(argc, argv); QPushButton quit(\"Quit\"); quit.resize(100, 30); quit.show(); QObject::connect(&quit, SIGNAL(clicked()), &app, SLOT(quit())); return app.exec(); } 2.元数据表如下: static const uint qt_meta_data_QPushButton[] = { // content: 1, // revision 0, // classname 0, 0, // classinfo 2, 10, // methods 3, 20, // properties 0, 0, // enums/sets // slots: signature, parameters, type, tag, flags 13, 12, 12, 12, 0x0a, 24, 12, 12, 12, 0x08, // properties: name, type, flags 44, 39, 0x01095103, 56, 39, 0x01095103, , 39, 0x01095103, 0 // eod }; static const char qt_meta_stringdata_QPushButton[] = { \"QPushButton\\0\\0showMenu()\\0popupPressed()\\0bool\\0autoDefault\\0default\\0\" \"flat\\0\" }; const QMetaObject QPushButton::staticMetaObject = { { &QAbstractButton::staticMetaObject, qt_meta_stringdata_QPushButton, qt_meta_data_QPushButton, 0 } }; 在这里我们看到了静态成员staticMetaObject被填充了 const QMetaObject *superdata;//这是元数据代表的类的基类的元数据,被填充为基类的元数据指针&QAbstractButton::staticMetaObject const char *stringdata;//这是元数据的签名标记,被填充为qt_meta_stringdata_QPushButton const uint *data;//这是元数据的索引数组的指针,被填充为qt_meta_data_QPushButton const QMetaObject **extradata;//这是扩展元数据表的指针,一般是不用的,被填充为0 首先应该看qt_meta_data_QPushButton,因为这里是元数据的主要数据,它被填充为一个整数数组,正因为这里只有整数,不能有任何字符串存在,因此才有qt_meta_stringdata_QPushButton发挥作用的机会,可以说真正的元数据应该是qt_meta_data_QPushButton加上qt_meta_stringdata_QPushButton,那么为什么不把这两个东西合在一起呢?估计是两者都不是定长的结构,合在一起反而麻烦吧。 qt_meta_data_QPushButton实际上是以以下结构开头的 struct QMetaObjectPrivate { int revision; int className; int classInfoCount, classInfoData; int methodCount, methodData; int propertyCount, propertyData; int enumeratorCount, enumeratorData; }; 一般使用中是直接使用以下函数做个转换 static inline const QMetaObjectPrivate *priv(const uint* data) { return reinterpret_cast static const uint qt_meta_data_QPushButton[] = { // content: 1, // revision 版本号是1 0, // classname 类名存储在qt_meta_stringdata_QPushButton中,索引是0,因此就是QPushButton了 0, 0, // classinfo 类信息数量为0,数据也是0 2, 10, // methods QPushButton有2个自定义方法,方法数据存储在qt_meta_data_QPushButton中,索引是10,就是下面的slots:开始的地方 3, 20, // properties QPushButton有3个自定义属性,属性数据存储在qt_meta_data_QPushButton中,索引是20,就是下面的 properties:开始的地方 0, 0, // enums/sets QPushButton没有自定义的枚举 // slots: signature, parameters, type, tag, flags 13, 12, 12, 12, 0x0a, 第一个自定义方法的签名存储在qt_meta_stringdata_QPushButton中,索引是13,就是showMenu()了 24, 12, 12, 12, 0x08, 第二个自定义方法的签名存储在qt_meta_stringdata_QPushButton中,索引是24,popupPressed()了 // properties: name, type, flags 44, 39, 0x01095103, 第一个自定义属性的签名存储在qt_meta_data_QPushButton中,索引是44,就是autoDefault了 第一个自定义属性的类型存储在qt_meta_data_QPushButton中,索引是39,就是bool 56, 39, 0x01095103, 第二个自定义属性的签名存储在qt_meta_data_QPushButton中,索引是56,就是default了 第二个自定义属性的类型存储在qt_meta_data_QPushButton中,索引是39,就是bool , 39, 0x01095103, 第三个自定义属性的签名存储在qt_meta_data_QPushButton中,索引是,就是flat了 第三个自定义属性的类型存储在qt_meta_data_QPushButton中,索引是39,就是bool 0 // eod 元数据的结束标记 }; static const char qt_meta_stringdata_QPushButton[] = { \"QPushButton\\0\\0showMenu()\\0popupPressed()\\0bool\\0autoDefault\\0default\\0\" \"flat\\0\" }; QPushButton\\\\showMenu()\\popupPressed()\\bool\\autoDefault\\default\\flat\\ 这里把\\0直接替换为\\是为了数数的方便 当然我们还可以看看QPushButton的基类QAbstractButton的元数据 static const uint qt_meta_data_QAbstractButton[] = { // content: 1, // revision 0, // classname 0, 0, // classinfo 12, 10, // methods 9, 70, // properties 0, 0, // enums/sets // signals: signature, parameters, type, tag, flags 17, 16, 16, 16, 0x05, 27, 16, 16, 16, 0x05, 46, 38, 16, 16, 0x05, 60, 16, 16, 16, 0x25, 70, 38, 16, 16, 0x05, // slots: signature, parameters, type, tag, flags , 84, 16, 16, 0x0a, 113, 108, 16, 16, 0x0a, 131, 16, 16, 16, 0x2a, 146, 16, 16, 16, 0x0a, 154, 16, 16, 16, 0x0a, 163, 16, 16, 16, 0x0a, 182, 180, 16, 16, 0x1a, // properties: name, type, flags 202, 194, 0x0a095103, 213, 207, 0x45095103, 224, 218, 0x15095103, 246, 233, 0x4c095103, 260, 255, 0x01095103, 38, 255, 0x01195103, 270, 255, 0x01095103, 281, 255, 0x01095103, 295, 255, 0x01094103, 0 // eod }; static const char qt_meta_stringdata_QAbstractButton[] = { \"QAbstractButton\\0\\0pressed()\\0released()\\0checked\\0clicked(bool)\\0\" \"clicked()\\0toggled(bool)\\0size\\0setIconSize(QSize)\\0msec\\0\" \"animateClick(int)\\0animateClick()\\0click()\\0toggle()\\0setChecked(bool)\\0\" \"b\\0setOn(bool)\\0QString\\0text\\0QIcon\\0icon\\0QSize\\0iconSize\\0\" \"QKeySequence\\0shortcut\\0bool\\0checkable\\0autoRepeat\\0autoExclusive\\0\" \"down\\0\" }; QAbstractButton00pressed()0released()0checked0clicked(bool)0clicked()0toggled(bool)0size0setIconSize(QSize)0msec0animateClick(int)0animateClick()0click()0toggle()0setChecked(bool)0b0setOn(bool)0QString0text0QIcon0icon0QSize0iconSize0QKeySequence0shortcut0bool0checkable0autoRepeat0autoExclusive0down0 基本上都是大同小异的 3.connect实现: QObject::connect(&quit, SIGNAL(clicked()), &app, SLOT(quit())); // connect函数是连接信号和槽的桥梁,非常关键 bool QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type) { „„ // 不允许空输入 if (sender == 0 || receiver == 0 || signal == 0 || method == 0) return false; //检查是否是信号标记 if (!check_signal_macro(sender, signal, \"connect\ return false; // 得到元数据类 const QMetaObject *smeta = sender->metaObject(); ++signal; //skip code跳过信号标记,直接得到信号标识 // 得到信号的索引 int signal_index = smeta->indexOfSignal(signal); if (signal_index < 0) { // check for normalized signatures QByteArray tmp_signal_name; tmp_signal_name = QMetaObject::normalizedSignature(signal).prepend(*(signal - 1)); signal = tmp_signal_name.constData() + 1; signal_index = smeta->indexOfSignal(signal); if (signal_index < 0) { #ifndef QT_NO_DEBUG err_method_notfound(QSIGNAL_CODE, sender, signal, \"connect\"); err_info_about_objects(\"connect\#endif return false; } } QByteArray tmp_method_name; int membcode = method[0] - '0'; #ifndef QT_NO_DEBUG // 检查是否是槽,用QSLOT_CODE 1标记 if (!check_method_code(membcode, receiver, method, \"connect\")) return false; #endif ++method; // skip code // 得到元数据类 const QMetaObject *rmeta = receiver->metaObject(); int method_index = -1; // 这里是一个case,信号即可以和信号连接也可以和槽连接 switch (membcode) { case QSLOT_CODE: // 得到槽的索引 method_index = rmeta->indexOfSlot(method); break; case QSIGNAL_CODE: // 得到信号的索引 method_index = rmeta->indexOfSignal(method); break; } if (method_index < 0) { // check for normalized methods tmp_method_name = QMetaObject::normalizedSignature(method); method = tmp_method_name.constData(); switch (membcode) { case QSLOT_CODE: method_index = rmeta->indexOfSlot(method); break; case QSIGNAL_CODE: method_index = rmeta->indexOfSignal(method); break; } } if (method_index < 0) { #ifndef QT_NO_DEBUG err_method_notfound(membcode, receiver, method, \"connect\"); err_info_about_objects(\"connect\#endif return false; } #ifndef QT_NO_DEBUG // 检查参数,信号和槽的参数必须一致,槽的参数也可以小于信号的参数 if (!QMetaObject::checkConnectArgs(signal, method)) { qWarning(\"Object::connect: Incompatible sender/receiver arguments\" \"\\n\%s::%s --> %s::%s\ sender->metaObject()->className(), signal, receiver->metaObject()->className(), method); return false; } #endif int *types = 0; if (type == Qt::QueuedConnection && !(types = ::queuedConnectionTypes(smeta->method(signal_index).parameterTypes()))) return false; #ifndef QT_NO_DEBUG { // 得到方法的元数据 QMetaMethod smethod = smeta->method(signal_index); QMetaMethod rmethod = rmeta->method(method_index); if (warnCompat) { if(smethod.attributes() & QMetaMethod::Compatibility) { if (!(rmethod.attributes() & QMetaMethod::Compatibility)) qWarning(\"Object::connect: Connecting from COMPAT signal (%s::%s).\ } else if(rmethod.attributes() & QMetaMethod::Compatibility && membcode != QSIGNAL_CODE) { qWarning(\"Object::connect: Connecting from %s::%s to COMPAT slot (%s::%s).\ smeta->className(), signal, rmeta->className(), method); } } } #endif // 调用元数据类的连接 QMetaObject::connect(sender, signal_index, receiver, method_index, type, types); // 发送连接的通知,现在的实现是空的 const_cast return true; } 检查信号标记其实比较简单,就是用signal的第一个字符和用QSIGNAL_CODE=2的标记比较而已 static bool check_signal_macro(const QObject *sender, const char *signal, const char *func, const char *op) { int sigcode = (int)(*signal) - '0'; if (sigcode != QSIGNAL_CODE) { if (sigcode == QSLOT_CODE) qWarning(\"Object::%s: Attempt to %s non-signal %s::%s\ func, op, sender->metaObject()->className(), signal+1); else qWarning(\"Object::%s: Use the SIGNAL macro to %s %s::%s\ func, op, sender->metaObject()->className(), signal); return false; } return true; } 得到信号的索引实际上要依次找每个基类的元数据,得到的偏移也是所有元数据表加在一起后的一个索引 int QMetaObject::indexOfSignal(const char *signal) const { int i = -1; const QMetaObject *m = this; while (m && i < 0) { // 根据方法的数目倒序的查找 for (i = priv(m->d.data)->methodCount-1; i >= 0; --i) // 得到该方法的类型 if ((m->d.data[priv(m->d.data)->methodData + 5*i + 4] & MethodTypeMask) == MethodSignal && strcmp(signal, m->d.stringdata // 得到方法名称的偏移 + m->d.data[priv(m->d.data)->methodData + 5*i]) == 0) { //如果找到了正确的方法,再增加所有基类的方法偏移量 i += m->methodOffset(); break; } // 没找到,在父类中继续找 m = m->d.superdata; } #ifndef QT_NO_DEBUG // 判断是否于基类中的冲突 if (i >= 0 && m && m->d.superdata) { int conflict = m->d.superdata->indexOfMethod(signal); if (conflict >= 0) qWarning(\"QMetaObject::indexOfSignal:%s: Conflict with %s::%s\ m->d.stringdata, m->d.superdata->d.stringdata, signal); } #endif return i; } // 这里是所有基类的方法偏移量算法,就是累加基类所有的方法数目 int QMetaObject::methodOffset() const { int offset = 0; const QMetaObject *m = d.superdata; while (m) { offset += priv(m->d.data)->methodCount; m = m->d.superdata; } return offset; } // 得到方法的元数据 QMetaMethod QMetaObject::method(int index) const { int i = index; // 要减去基类的偏移 i -= methodOffset(); // 如果本类找不到,就到基类中去找 if (i < 0 && d.superdata) return d.superdata->method(index); // 如果找到了,就填充QMetaMethod结构 QMetaMethod result; if (i >= 0 && i < priv(d.data)->methodCount) { // 这里是类的元数据 result.mobj = this; // 这里是方法相关数据在data数组中的偏移量 result.handle = priv(d.data)->methodData + 5*i; } return result; } bool QMetaObject::connect(const QObject *sender, int signal_index, const QObject *receiver, int method_index, int type, int *types) { // 得到全局的连接列表 QConnectionList *list = ::connectionList(); if (!list) return false; QWriteLocker locker(&list->lock); // 增加一个连接 list->addConnection(const_cast const_cast void QConnectionList::addConnection(QObject *sender, int signal, QObject *receiver, int method, int type, int *types) { // 构造一个连接 QConnection c = { sender, signal, receiver, method, 0, 0, types }; c.type = type; // don't warn on VC++6 int at = -1; // 如果有中间被删除的连接,可以重用这个空间 for (int i = 0; i < unusedConnections.size(); ++i) { if (!connections.at(unusedConnections.at(i)).inUse) { // reuse an unused connection at = unusedConnections.takeAt(i); connections[at] = c; break; } } if (at == -1) { // append new connection at = connections.size(); // 加入一个连接 connections << c; } // 构造sender,receiver连接的哈希表,加速搜索速度 sendersHash.insert(sender, at); receiversHash.insert(receiver, at); } 通过connect函数,我们建立了信号和槽的连接,并且把信号类+信号索引+槽类,槽索引作为记录写到了全局的connect列表中 4.emit: 一旦我们发送了信号,就应该调用相关槽中的方法了,这个过程其实就是查找全局的connect列表的过程,当然还要注意其中要对相关的参数打包和解包 // emit是发送信号的代码 void Foo::setValue(int v) { if (v != val) { val = v; emit valueChanged(v); } } // 发送信号的真正实现在moc里面 // SIGNAL 0 void Foo::valueChanged(int _t1) { // 首先把参数打包 void *_a[] = { 0, const_cast QMetaObject::activate(this, &staticMetaObject, 0, _a); } void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index, void **argv) { // 增加一个基类偏移量 int offset = m->methodOffset(); activate(sender, offset + local_signal_index, offset + local_signal_index, argv); } void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv) { // 这里得到的是QObject的数据,首先判断是否为阻塞设置 if (sender->d_func()->blockSig) return; // 得到全局链表 QConnectionList * const list = ::connectionList(); if (!list) return; QReadLocker locker(&list->lock); void *empty_argv[] = { 0 }; if (qt_signal_spy_callback_set.signal_begin_callback != 0) { locker.unlock(); qt_signal_spy_callback_set.signal_begin_callback(sender, from_signal_index, argv ? argv : empty_argv); locker.relock(); } // 在sender的哈希表中得到sender的连接 QConnectionList::Hash::const_iterator it = list->sendersHash.find(sender); const QConnectionList::Hash::const_iterator end = list->sendersHash.constEnd(); if (it == end) { if (qt_signal_spy_callback_set.signal_end_callback != 0) { locker.unlock(); qt_signal_spy_callback_set.signal_end_callback(sender, from_signal_index); locker.relock(); } return; } QThread * const currentThread = QThread::currentThread(); const int currentQThreadId = currentThread ? QThreadData::get(currentThread)->id : -1; // 记录sender连接的索引 QVarLengthArray for (; it != end && it.key() == sender; ++it) { connections.append(it.value()); // 打上使用标记,因为可能是放在队列中 list->connections[it.value()].inUse = 1; } for (int i = 0; i < connections.size(); ++i) { const int at = connections.constData()[connections.size() - (i + 1)]; QConnectionList * const list = ::connectionList(); // 得到连接 QConnection &c = list->connections[at]; c.inUse = 0; if (!c.receiver || (c.signal < from_signal_index || c.signal > to_signal_index)) continue; // 判断是否放到队列中 // determine if this connection should be sent immediately or // put into the event queue if ((c.type == Qt::AutoConnection && (currentQThreadId != sender->d_func()->thread || c.receiver->d_func()->thread != sender->d_func()->thread)) || (c.type == Qt::QueuedConnection)) { ::queued_activate(sender, c, argv); continue; } // 为receiver设置当前发送者 const int method = c.method; QObject * const previousSender = c.receiver->d_func()->currentSender; c.receiver->d_func()->currentSender = sender; list->lock.unlock(); if (qt_signal_spy_callback_set.slot_begin_callback != 0) qt_signal_spy_callback_set.slot_begin_callback(c.receiver, method, argv ? argv : empty_argv); #if defined(QT_NO_EXCEPTIONS) c.receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv); #else try { // 调用receiver的方法 c.receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv); } catch (...) { list->lock.lockForRead(); if (c.receiver) c.receiver->d_func()->currentSender = previousSender; throw; } #endif if (qt_signal_spy_callback_set.slot_end_callback != 0) qt_signal_spy_callback_set.slot_end_callback(c.receiver, method); list->lock.lockForRead(); if (c.receiver) c.receiver->d_func()->currentSender = previousSender; } if (qt_signal_spy_callback_set.signal_end_callback != 0) { locker.unlock(); qt_signal_spy_callback_set.signal_end_callback(sender, from_signal_index); locker.relock(); } } // 响应信号也是在moc里实现的 int Foo::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { // 首先在基类中调用方法,返回的id已经变成当前类的方法id了 _id = QObject::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { switch (_id) { // 这里就是真正的调用方法了,实际上是一个大的switch语句,注意参数的解包用法,本质为通过moc生成强制的参数类型转换 case 0: valueChanged(*reinterpret_cast< int(*)>(_a[1])); break; case 1: setValue(*reinterpret_cast< int(*)>(_a[1])); break; } _id -= 2; //通过掉正ID,使调用基类后,变位当前类的ID } return _id; }
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- aiwanbo.com 版权所有 赣ICP备2024042808号-3
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务