信号槽是Qt提供的一种比较灵活的对象之间进行通信的机制. 一个信号发出后会把与这个信号关联的槽函数都会执行一遍.
本文接下来会从代码入手, 看看信号槽背后究竟是什么东西. 一个信号发出后是怎么走到对应的槽函数的.
从信号函数说起
以QWidget的第0个信号windowTitleChanged为例, 在窗口标题发生改变的时候会发这个信号:
void QWidget::setWindowTitle(const QString &title)
{
if (QWidget::windowTitle() == title && !title.isEmpty() && !title.isNull())
return;
// ...
emit windowTitleChanged(title);
}
首先留意一下前面的emit关键字, 刚开始接触QT的时候还以为这个emit关键字干了什么神奇的事情, 但其实就是一个宏, 一个空的宏啥都没干, 只是为了方便人眼阅读.
既然这样, 那触发一个信号就和普通的函数调用没什么区别了. 还是有一点不同, 这个函数不需要我们手动实现, 编译的时候MOC会自动生成, 我们定义一个信号的时候只需要写函数的声明.
MOC生成的代码如下:
// SIGNAL 0
void QWidget::windowTitleChanged(const QString & _t1)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
其实就是做了个转调, 转调到QMetaObject::activate, 所有信号函数都转调到这里.
QMetaObject::activate原函数比较复杂, 这里做了些简化:
void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
{
int signal_index = signalOffset + local_signal_index;
ConnectionListsRef connectionLists = sender->d_func()->connectionLists;
const QObjectPrivate::ConnectionList *list = &connectionLists->at(signal_index);
do {
QObjectPrivate::Connection *c = list->first;
if (!c) continue;
QObjectPrivate::Connection *last = list->last;
do {
if (c->isSlotObject) {
// ...
}
else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
const int method_relative = c->method_relative;
const auto callFunction = c->callFunction;
callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);
}
else {
// ...
}
} while (c != last && (c = c->nextConnectionList) != 0);
} while (list != &connectionLists->allsignals &&
((list = &connectionLists->allsignals), true));
}
从上面简化后的代码可以清晰的看到内部数据的组织关系:
用Connection对象记录信号函数和槽函数的连接关系. 同一个信号的所有连接用nextConnectionList指针连成一个单向链表, 链表的头尾记录用ConnectionList对象来记录. 所有信号的链表都放在connectionLists里面.
看connectionLists的定义, 其实就是一个数组, 数组的下标就是信号的id:
class QObjectConnectionListVector : public QVector<QObjectPrivate::ConnectionList>
{
// ...
};
再附上ConnectionList和Connection的代码片段:
struct ConnectionList {
ConnectionList() : first(nullptr), last(nullptr) {}
Connection *first;
Connection *last;
};
struct Connection
{
QObject *sender;
QObject *receiver;
union {
StaticMetaCallFunction callFunction;
QtPrivate::QSlotObjectBase *slotObj;
};
Connection *nextConnectionList;
Connection *next;
Connection **prev;
};
关于callFunction
callFunction的函数原型如下:
typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call _c, int _id, void **_a);
这玩意儿指向的是Q_OBJECT宏里面的qt_metacall函数, 函数体由MOC生成, 是暴露给QMetaObject的统一接口. 元对象对类的所有操作都要走到这里. 其实就是一个操作的派发中心, _c参数指定操作类型, _id参数指定具体要执行哪个函数, _a是传给函数的参数. 代码如下:
int QWidget::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
if (_id < 27)
qt_static_metacall(this, _c, _id, _a);
_id -= 27;
else {
// ...
}
return _id;
}
void QWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
auto *_t = static_cast<QWidget *>(_o);
switch (_id) {
case 0: _t->windowTitleChanged((*reinterpret_cast< const QString(*)>(_a[1]))); break;
case 1: _t->windowIconChanged((*reinterpret_cast< const QIcon(*)>(_a[1]))); break;
// ...
default: ;
}
}
else {
// ...
}
}
这里再稍微留意一下qt_static_metacall里面id的分配规则. 每个能由原对象调用到的方法都需要一个id.
而信号的id还要用作connectionLists数组的下标, 下标要从0开始, 所以, 信号函数的id会优先分配保证id是从0开始的. 信号函数之间则是取决于信号的声明顺序.
关于信号和槽的连接
现在再回过头看看 信号函数和槽函数是怎么关联起来的. 通过前面的代码已经可以知道信号和槽函数的连接关系记录在Connection对象中.
信号和槽关联的过程,其实就是创建Connection对象的过程.
这个逻辑接QObject::connect函数中实现, 这个函数有几个重载, 这里我们以Qt4的字符串参数的版本为例:
QMetaObject::Connection 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 QMetaObject::Connection(0);
}
QByteArray tmp_signal_name;
if (!check_signal_macro(sender, signal, "connect", "bind"))
return QMetaObject::Connection(0);
const QMetaObject *smeta = sender->metaObject();
++signal; //skip code
QArgumentTypeArray signalTypes;
QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
int signal_index = QMetaObjectPrivate::indexOfSignalRelative(
&smeta, signalName, signalTypes.size(), signalTypes.constData());
if (signal_index < 0) {
// check for normalized signatures
// ......
}
signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);
signal_index += QMetaObjectPrivate::signalOffset(smeta);
QByteArray tmp_method_name;
int membcode = extract_code(method);
if (!check_method_code(membcode, receiver, method, "connect"))
return QMetaObject::Connection(0);
const char *method_arg = method;
++method; // skip code
QArgumentTypeArray methodTypes;
QByteArray methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
const QMetaObject *rmeta = receiver->metaObject();
int method_index_relative = -1;
switch (membcode) {
case QSLOT_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
case QSIGNAL_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
}
if (method_index_relative < 0) {
// check for normalized methods
tmp_method_name = QMetaObject::normalizedSignature(method);
method = tmp_method_name.constData();
methodTypes.clear();
methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
// rmeta may have been modified above
rmeta = receiver->metaObject();
switch (membcode) {
case QSLOT_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
case QSIGNAL_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
}
}
if (method_index_relative < 0) {
return QMetaObject::Connection(0);
}
if (!QMetaObjectPrivate::checkConnectArgs(signalTypes.size(), signalTypes.constData(),
methodTypes.size(), methodTypes.constData())) {
return QMetaObject::Connection(0);
}
int *types = 0;
if ((type == Qt::QueuedConnection)
&& !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) {
return QMetaObject::Connection(0);
}
QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));
return handle;
}
函数很长, 都是在解析传进来的函数签名, 根据传进来的字符串找到信号函数和槽函数的id, 然后传给QMetaObjectPrivate::connect函数用这两个id构造出Connection对象:
QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,
int signal_index, const QMetaObject *smeta,
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;
QObjectPrivate::StaticMetaCallFunction callFunction =
rmeta ? rmeta->d.static_metacall : 0;
QOrderedMutexLocker locker(signalSlotLock(sender),
signalSlotLock(receiver));
if (type & Qt::UniqueConnection) {
// ...
type &= Qt::UniqueConnection - 1;
}
QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
c->sender = s;
c->signal_index = signal_index;
c->receiver = r;
c->method_relative = method_index;
c->method_offset = method_offset;
c->connectionType = type;
c->isSlotObject = false;
c->argumentTypes.store(types);
c->nextConnectionList = 0;
c->callFunction = callFunction;
QObjectPrivate::get(s)->addConnection(signal_index, c.data());
locker.unlock();
QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
if (smethod.isValid())
s->connectNotify(smethod);
return c.take();
}
QObjectPrivate::addConnection函数把构造出来的Connection对象添加到链表里.
void QObjectPrivate::addConnection(int signal, Connection *c)
{
Q_ASSERT(c->sender == q_ptr);
if (!connectionLists)
connectionLists = new QObjectConnectionListVector();
if (signal >= connectionLists->count())
connectionLists->resize(signal + 1);
ConnectionList &connectionList = (*connectionLists)[signal];
if (connectionList.last) {
connectionList.last->nextConnectionList = c;
} else {
connectionList.first = c;
}
connectionList.last = c;
cleanConnectionLists();
c->prev = &(QObjectPrivate::get(c->receiver)->senders);
c->next = *c->prev;
*c->prev = c;
if (c->next)
c->next->prev = &c->next;
if (signal < 0) {
connectedSignals[0].store(~0);
connectedSignals[1].store(~0);
} else if (signal < (int)sizeof(connectedSignals) * 8) {
connectedSignals[signal >> 5].store(connectedSignals[signal >> 5].load() | (1 << (signal & 0x1f)));
}
}
最后再看, 信号槽背后其实就是观察者模式, 一个注册机制, 一个通知机制. QObject::connect创建连接就是一个注册的过程, 把槽函数注册到信号函数的通知列表中. 当信号函数触发的时候把通知列表中的槽函数依次执行一遍. 信号槽解决的是任意两个对象之间的通信问题, 所以它的设计比普通的观察者模式来得灵活. 一个信号函数和一个槽函数只要他们的参数一致就可以关联起来, 两个函数可以分别在两个毫不相干的类里面, 也不关心对方是谁长什么样.
浙公网安备 33010602011771号