查表得索引,通过switch把所有的信号或者槽自动预编译成代码即qt_static_metacall函数,通过传入索引得以运行对应的槽函数。
connect通过查表把接收者的对象指针和槽函数索引发在发送者的对应信号的索引指明的连接链表中。
信号槽简介
信号槽是观察者模式的一种实现,特性如下:
- 一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知;
- 一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候——也可以说是信号发出的时候——被调用的函数;
- 信号与槽的连接,形成一种观察者-被观察者的关系;
- 当事件或者状态发生改变的时候,信号就会被发出;同时,信号发出者有义务调用所有注册的对这个事件(信号)感兴趣的函数(槽)。
信号和槽是多对多的关系。一个信号可以连接多个槽,而一个槽也可以监听多个信号。
信号槽与语言无关,有多种方法可以实现信号槽,不同的实现机制会导致信号槽的差别很大。信号槽术语最初来自 Trolltech 公司的 Qt 库,由于其设计理念的先进性,立刻引起计算机科学界的注意,提出了多种不同的实现。
目前,信号槽依然是 Qt 库的核心之一,其他许多库也提供了类似的实现,甚至出现了一些专门提供这一机制的工具库。
信号槽是Qt对象以及其派生类对象之间的一种高效通信接口,是Qt的核心特性,也是Qt区别与其他工具包的重要地方。
信号槽完全独立于标准的C/C++语言,因此要正确的处理好信号和槽,必须借助于一个成为MOC(Meta Object Compiler)的Qt工具,MOC工具是一个C++预处理程序,能为高层次的事件处理自动生成所需要的附加代码。
Meta Object Compiler(MOC),为每个QObject派生类生成代码,以支持meta-object功能。
如果一个类从QObject派生,确没有声明Q_OBJECT宏,那么这个类的metaobject对象不会被生成,这样这个类所声明的signal slot都不能使用,而这个类实例调用metaObject()返回的就是其父类的metaobject对象,这样导致的后果就是你从这个类实例获得的元数据其实都是父类的数据,这显然给你的代码埋下隐患。因此如果一个类从QOBject派生,它都应该声明Q_OBJECT宏,不管这个类有没有定义signal&slot和Property。
定义一个类
class Object : public QObject
{
Q_OBJECT
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
Q_PROPERTY(int score READ score WRITE setScore NOTIFY scoreChanged)
Q_PROPERTY(Level level READ level WRITE setLevel)
Q_CLASSINFO("Author", "Scorpio")
Q_CLASSINFO("Version", "1.0")
public:
enum Level
{
Basic = 1,
Middle,
Advanced,
Master
};
Q_ENUMS(Level)
protected:
QString m_name;
Level m_level;
int m_age;
int m_score;
void setLevel(const int& score)
{
if(score <= 60)
{
m_level = Basic;
}
else if(score < 100)
{
m_level = Middle;
}
else if(score < 150)
{
m_level = Advanced;
}
else
{
m_level = Master;
}
}
public:
explicit Object(QString name, QObject *parent = 0):QObject(parent)
{
m_name = name;
setObjectName(m_name);
connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
}
int age()const
{
return m_age;
}
void setAge(const int& age)
{
m_age = age;
emit ageChanged(m_age);
}
int score()const
{
return m_score;
}
void setScore(const int& score)
{
m_score = score;
setLevel(m_score);
emit scoreChanged(m_score);
}
Level level()const
{
return m_level;
}
void setLevel(const Level& level)
{
m_level = level;
}
signals:
void ageChanged(int age);
void scoreChanged(int score);
public slots:
void onAgeChanged(int age)
{
qDebug() << "age changed:" << age;
}
void onScoreChanged(int score)
{
qDebug() << "score changed:" << score;
}
};
MOC预编译后生成的代码
调用MOC工具对源文件进行分析,如果某个类包含了Q_OBJECT宏,MOC会生成对应的moc_xxx.cpp文件。
qt_meta_stringdata_Object数组
这些字符串虽然对于已经预编译编译生成好的代码没有用了,但是对于应用程序运行时获取一些字符串信息,以及用这些字符串获取一些函数,有点用,比如说QMetaObject::invokeMethod
static const char qt_meta_stringdata_Object[] = {
"Object\0Scorpio\0Author\0""1.0\0Version\0\0" //0~42
"age\0ageChanged(int)\0score\0scoreChanged(int)\0" // 43 ~ 90
"onAgeChanged(int)\0onScoreChanged(int)\0" // 91 ~ 130
"int\0Level\0level\0Basic\0Middle\0Advanced\0" //131 ~ 174
"Master\0" //175 ~ 182
};
内省表qt_meta_data_Object
内省表是一个 uint 数组,分为五个部分:
第一部分content,即内容,分为9行。第一行revision,指MOC生成代码的版本号(Qt4 是6,Qt5则是7)。第二个classname,即类名,该值是一个索引,指向字符串表的某一个位置(本例中就是第0位)。
static const uint qt_meta_data_Object[] = {
// content:内容信息
6, // revision MOC生成代码的版本号
0, // classname 类名,在qt_meta_stringdata_Object数组中索引为0
2, 14, // classinfo 类信息,有2个cassinfo定义,
4, 18, // methods 类有4个自定义方法,即信号与槽个数,
3, 38, // properties 属性的位置信息,有3个自定义属性,
1, 50, // enums/sets 枚举的位置信息,有一个自定义枚举,在qt_meta_stringdata_Object数组中索引为50
0, 0, // constructors 构造函数的位置信息
0, // flags
2, // signalCount
// classinfo: key, value //类信息的存储在qt_meta_stringdata_Object数组中,
15, 7, //第一个类信息,key的数组索引为15,即Author,value的数组索引为7,即Scorpio
26, 22, //第二个类信息,key的数组索引为26,即Version,value的数组索引为22,即1.0
// signals: signature, parameters, type, tag, flags
39, 35, 34, 34, 0x05, //第一个自定义信号的签名存储在qt_meta_stringdata_Object数组中,
//索引是39,即ageChanged(int)
61, 55, 34, 34, 0x05, //第二个自定义信号的签名存储在qt_meta_stringdata_Object数组中,
//索引是61,即scoreChanged(int)
// slots: signature, parameters, type, tag, flags
79, 35, 34, 34, 0x0a, //第一个自定义槽函数的签名存储在qt_meta_stringdata_Object数组中,
//索引是79,即onAgeChanged(int)
97, 55, 34, 34, 0x0a, //第二个自定义槽函数的签名存储在qt_meta_stringdata_Object数组中,
//索引是79,即onScoreChanged(int)
// properties: name, type, flags
35, 117, 0x02495103, // 第一个自定义属性的签名存储在qt_meta_stringdata_Object中,索引是35,即age
55, 117, 0x02495103, // 第二个自定义属性的签名存储在qt_meta_stringdata_Object中,索引是55,即score
127, 121, 0x0009510b, // 第三个自定义属性的签名存储在qt_meta_stringdata_Object中,索引是127,即level
// properties: notify_signal_id //属性关联的信号编号
0,
1,
0,
// enums: name, flags, count, data
121, 0x0, 4, 54, //枚举的定义,存储在qt_meta_stringdata_Object中,索引是121,即Level,内含4个枚举常量
// enum data: key, value //枚举数据的键值对
133, uint(Object::Basic), //数组索引是133,即Basic
139, uint(Object::Middle), //数组索引是139,即Middle
146, uint(Object::Advanced), //数组索引是146,即Advanced
155, uint(Object::Master), //数组索引是155,即Master
0 // eod 元数据结束标记
};
先从槽函数的调用开始,慢慢溯源,找到和信号,元系统之间的关系。
槽函数的调用 qt_static_metacall
槽函数最终通过qt_static_metacall函数根据参数调用相应的槽函数。
void Object::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod)
{
Q_ASSERT(staticMetaObject.cast(_o));
Object *_t = static_cast<Object *>(_o);
switch (_id) {
case 0: _t->ageChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: _t->scoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 2: _t->onAgeChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 3: _t->onScoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
default: ;
}
}
}
QObject::connect
bool QObject::connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type)
{
{
const void *cbdata[] = { sender, signal, receiver, method, &type };
QByteArray tmp_signal_name;
const QMetaObject *smeta = sender->metaObject();
//在发送者对象的元对象中将信号的相对索引找到
int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, false);
const QMetaObject *rmeta = receiver->metaObject();
//在接受者对象的元对象中将槽函数的相对索引找到
int method_index_relative = -1;
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, false);
//调用QMetaObjectPrivate::connect将信号与槽进行连接
if (!QMetaObjectPrivate::connect(sender, signal_index, receiver, method_index_relative, rmeta ,type, types))
return false;
const_cast<QObject*>(sender)->connectNotify(signal - 1);
return true;
}
QMetaObjectPrivate::connect
bool QMetaObjectPrivate::connect(const QObject *sender, int signal_index,
const QObject *receiver, int method_index,
const QMetaObject *rmeta, int type, int *types)
{
QObject *s = const_cast<QObject *>(sender);
QObject *r = const_cast<QObject *>(receiver);
int method_offset = rmeta ? rmeta->methodOffset() : 0;
//在元对象的元数据字符串中找到回调的函数指针qt_static_metacall
QObjectPrivate::StaticMetaCallFunction callFunction =
(rmeta && QMetaObjectPrivate::get(rmeta)->revision >= 6 && rmeta->d.extradata)
? reinterpret_cast<const QMetaObjectExtraData *>(rmeta->d.extradata)->static_metacall : 0;
//如果连接类型为Qt::UniqueConnection
if (type & Qt::UniqueConnection)
{
QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;
//创建一个新的连接
QObjectPrivate::Connection *c = new QObjectPrivate::Connection;
//设置连接的属性
c->sender = s;
c->receiver = r;
c->method_relative = method_index;
c->method_offset = method_offset;
c->connectionType = type;
c->argumentTypes = types;
c->nextConnectionList = 0;
c->callFunction = callFunction;//设置回调的函数指针为qt_static_metacall
//将连接添加到发送者的连接链表容器中相应的信号对应的连接链表中
QObjectPrivate::get(s)->addConnection(signal_index, c);
return true;
}
const QMetaObjectExtraData Object::staticMetaObjectExtraData = {
0, qt_static_metacall
};
利用槽函数在qt_static_metacall 函数的索引位置来调用槽函数
信号的编号通过一系列转换 变成 对应对象的槽函数的编号进行调用,即通过一系列connect从对象中找到的槽函数的索引。信号连接时只有信号和槽在相应对象中的索引编号。
到这一步之前,如果时非直连方式,会通过QEvent事件系统,将接受者和槽索引作为事件中的值,发送到对应接受者对应的线程中,通过processevent,调用相应的槽函数。
void Object::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod)
{
Q_ASSERT(staticMetaObject.cast(_o));
Object *_t = static_cast<Object *>(_o);
switch (_id) {
case 0: _t->ageChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: _t->scoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 2: _t->onAgeChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 3: _t->onScoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
default: ;
}
}
}
信号的实现
MOC在生成的moc_xxx.cpp文件中实现了信号,创建了一个指向参数的指针的数组,并将指针数组传给QMetaObject::activate函数。数组的第一个元素是返回值。本例中值是0,因为返回值是void。传给activate函数的第三个参数是信号的索引(本例中是0)。
// SIGNAL 0,ageChanged信号的实现
void Object::ageChanged(int _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
// SIGNAL 1 scoreChanged信号的实现
void Object::scoreChanged(int _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
对象的连接链表
QObject::connect函数的主要功能是在接受者对象的元对象中将槽函数的相对索引找到,在接受者对象的元对象中将槽函数的相对索引找到,最后调用QMetaObjectPrivate::connect将信号与槽进行连接。
QObject及其派生类对象的元对象在创建时就有一个QObjectConnectionListVector连接链表容器,QObject::connect的作用就是将新的连接加入到信号发送者附属的元对象的连接链表容器的相应信号的连接链表中(一个信号可能连接多个槽函数)。
链表的容器即每个信号都有一个连接链表,同时信号的连接链表们形成了一个容器
每个QObject及其派生类对象都有一个QObjectConnectionListVector *connectionLists连接链表容器,将信号的索引作为容器的索引,将每一个信号与一个 QObjectPrivate::ConnectionList链表关联起来。
同时,QObjectPrivate::ConnectionList链表中连接的某个槽函数可能是接收者对象的槽函数链表中的一个。
每个接收者对象的链表如下:
senderList 的 prev 指针是一个指针的指针。这是因为并不是真的指向上一个节点,而是指向上一个节点中的 next 指针。这个指针仅在连接销毁时使用,并且不能向后遍历。它允许不为第一个元素添加特殊处理。
容器中存储的ConnectionList如下:
struct ConnectionList {
ConnectionList() : first(0), last(0) {}
Connection *first;//第一个结点
Connection *last;//最后一个结点
};
每个ConnectionList类型元素是一个双向链表,保存了信号的所有连接。连接的类型Connection结构如下:
struct Connection
{
QObject *sender;//发送者
QObject *receiver;//接受者
StaticMetaCallFunction callFunction;//调用的槽函数
// The next pointer for the singly-linked ConnectionList
Connection *nextConnectionList;
//senders linked list
Connection *next;
Connection **prev;
QBasicAtomicPointer<int> argumentTypes;
ushort method_offset;
ushort method_relative;
ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
~Connection();
int method() const { return method_offset + method_relative; }
};