Qt is well known for its signals and slots mechanism. But how does it work? In this blog post, we will explore the internals of QObject and QMetaObject and discover how signals and slot work under the hood.

In this blog article, I show portions of Qt5 code, sometimes edited for formatting and brevity.

Signals and Slots

First, let us recall how signals and slots look like by showing the official example.

Hover over the code to see fancy tool tips powered by the Woboq Code Browser!

The header looks like this:

class Counter : public QObject
{
Q_OBJECT
int m_value;
public:
int value() const { return m_value; }
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
};

Somewhere in the .cpp file, we implement setValue()

void Counter::setValue(int value)
{
if (value != m_value) {
m_value = value;
emit valueChanged(value);
}
}

Then one can use this Counter object like this:

  Counter a, b;
QObject::connect(&a, SIGNAL(valueChanged(int)),
&b, SLOT(setValue(int))); a.setValue(12); // a.value() == 12, b.value() == 12

This is the original syntax that has almost not changed since the beginning of Qt in 1992.

But even if the basic API has not changed since the beginning, its implementation has been changed several times. New features have been added and a lot happened under the hood. There is no magic involved and this blog post will show you how it works.

MOC, the Meta Object Compiler

The Qt signals/slots and property system are based on the ability to introspect the objects at runtime. Introspection means being able to list the methods and properties of an object and have all kinds of information about them such as the type of their arguments.
QtScript and QML would have hardly been possible without that ability.

C++ does not offer introspection support natively, so Qt comes with a tool to provide it. That tool is MOC. It is a code generator (and NOT a preprocessor like some people call it).

It parses the header files and generates an additional C++ file that is compiled with the rest of the program. That generated C++ file contains all the information required for the introspection.

Qt has sometimes been criticized by language purists because of this extra code generator. I will let the Qt documentation respond to this criticism. There is nothing wrong with code generators and the MOC is of a great help.

Magic Macros

Can you spot the keywords that are not pure C++ keywords? signals, slots, Q_OBJECT, emit, SIGNAL, SLOT. Those are known as the Qt extension to C++. They are in fact simple macros, defined in qobjectdefs.h

#define signals public
#define slots /* nothing */

That is right, signals and slots are simple functions: the compiler will handle them them like any other functions. The macros still serve a purpose though: the MOC will see them.

Signals were protected in Qt4 and before. They are becoming public in Qt5 in order to enable the new syntax.

#define Q_OBJECT \
public: \
    static const QMetaObject staticMetaObject; \
    virtual const QMetaObject *metaObject() const; \
    virtual void *qt_metacast(const char *); \
    virtual int qt_metacall(QMetaObject::Call, intvoid **); \
    QT_TR_FUNCTIONS /* translations helper */ \
private: \
    Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, intvoid **);

Q_OBJECT defines a bunch of functions and a static QMetaObject Those functions are implemented in the file generated by MOC.

#define emit /* nothing */

emit is an empty macro. It is not even parsed by MOC. In other words, emit is just optional and means nothing (except being a hint to the developer).

Q_CORE_EXPORT const char *qFlagLocation(const char *method);
#ifndef QT_NO_DEBUG
# define QLOCATION "\0" __FILE__ ":" QTOSTRING(__LINE__)
# define SLOT(a)     qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a)   qFlagLocation("2"#a QLOCATION)
#else
# define SLOT(a)     "1"#a
# define SIGNAL(a)   "2"#a
#endif

Those macros just use the preprocessor to convert the parameter into a string, and add a code in front.

In debug mode we also annotate the string with the file location for a warning message if the signal connection did not work. This was added in Qt 4.5 in a compatible way. In order to know which strings have the line information, we use qFlagLocation which will register the string address in a table with two entries.

MOC Generated Code

We will now go over portion of the code generated by moc in Qt5.

The QMetaObject

const QMetaObject Counter::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_Counter.data,
qt_meta_data_Counter, qt_static_metacall, Q_NULLPTR, Q_NULLPTR}
}; const QMetaObject *Counter::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}

We see here the implementation of Counter::metaObject() and Counter::staticMetaObject. They are declared in the Q_OBJECT macro. QObject::d_ptr->metaObject is only used for dynamic meta objects (QML Objects), so in general, the virtual function metaObject() just returns the staticMetaObject of the class.

The staticMetaObject is constructed in the read-only data. QMetaObject as defined in qobjectdefs.h looks like this:

struct QMetaObject
{
/* ... Skiped all the public functions ... */ enum Call { InvokeMetaMethod, ReadProperty, WriteProperty, /*...*/ }; struct { // private data
const QMetaObject *superdata;
const QByteArrayData *stringdata;
const uint *data;
typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
StaticMetacallFunction static_metacall;
const QMetaObject **relatedMetaObjects;
void *extradata; //reserved for future use
} d;
};

The d indirection is there to symbolize that all the member should be private. They are not private in order to keep it a POD and allow static initialization.

The QMetaObject is initialized with the meta object of the parent object (QObject::staticMetaObject in this case) as superdatastringdata and data are initialized with some data explained later in this article. static_metacall is a function pointer initialized to Counter::qt_static_metacall.

Introspection Tables

First, let us analyze the integer data of QMetaObject.

static const uint qt_meta_data_Counter[] = {

 // content:
7, // revision
0, // classname
0, 0, // classinfo
2, 14, // methods
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
1, // signalCount // signals: name, argc, parameters, tag, flags
1, 1, 24, 2, 0x06 /* Public */, // slots: name, argc, parameters, tag, flags
4, 1, 27, 2, 0x0a /* Public */, // signals: parameters
QMetaType::Void, QMetaType::Int, 3, // slots: parameters
QMetaType::Void, QMetaType::Int, 5, 0 // eod
};

The first 13 int consists of the header. When there are two columns, the first column is the count and the second column is the index in this array where the description starts.
In this case we have 2 methods, and the methods description starts at index 14.

The method descriptions are composed of 5 int. The first one is the name, it is an index in the string table (we will look into the details later). The second integer is the number of parameters, followed by the index at which one can find the parameter description. We will ignore the tag and flags for now. For each function, moc also saves the return type of each parameter, their type and index to the name.

String Table

struct qt_meta_stringdata_Counter_t {
QByteArrayData data[6];
char stringdata0[46];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_Counter_t, stringdata0) + ofs \
- idx * sizeof(QByteArrayData)) \
)
static const qt_meta_stringdata_Counter_t qt_meta_stringdata_Counter = {
{
QT_MOC_LITERAL(0, 0, 7), // "Counter"
QT_MOC_LITERAL(1, 8, 12), // "valueChanged"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 8), // "newValue"
QT_MOC_LITERAL(4, 31, 8), // "setValue"
QT_MOC_LITERAL(5, 40, 5) // "value" },
"Counter\0valueChanged\0\0newValue\0setValue\0"
"value"
};
#undef QT_MOC_LITERAL

This is basically a static array of QByteArray. the QT_MOC_LITERAL macro creates a static QByteArray that references a particular index in the string below.

Signals

The MOC also implements the signals. They are simple functions that just create an array of pointers to the arguments and pass that to QMetaObject::activate. The first element of the array is the return value. In our example it is 0 because the return value is void.
The 3rd parameter passed to activate is the signal index (0 in that case).

// SIGNAL 0
void Counter::valueChanged(int _t1)
{
void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

Calling a Slot

It is also possible to call a slot by its index in the qt_static_metacall function:

void Counter::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
Q_ASSERT(staticMetaObject.cast(_o));
Counter *_t = static_cast<Counter *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: _t->setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
default: ;
}

The array pointers to the argument is the same format as the one used for the signal. _a[0] is not touched because everything here returns void.

A Note About Indexes.

In each QMetaObject, the slots, signals and other invokable methods of that object are given an index, starting from 0. They are ordered so that the signals come first, then the slots and then the other methods. This index is called internally the relative index. They do not include the indexes of the parents.

But in general, we do not want to know a more global index that is not relative to a particular class, but include all the other methods in the inheritance chain. To that, we just add an offset to that relative index and get the absolute index. It is the index used in the public API, returned by functions like QMetaObject::indexOf{Signal,Slot,Method}

The connection mechanism uses a vector indexed by signals. But all the slots waste space in the vector and there are usually more slots than signals in an object. So from Qt 4.6, a new internal signal index which only includes the signal index is used.

While developing with Qt, you only need to know about the absolute method index. But while browsing the Qt's QObject source code, you must be aware of the difference between those three.

How Connecting Works.

The first thing Qt does when doing a connection is to find out the index of the signal and the slot. Qt will look up in the string tables of the meta object to find the corresponding indexes.

Then a QObjectPrivate::Connection object is created and added in the internal linked lists.

What information needs to be stored for each connection? We need a way to quickly access the connections for a given signal index. Since there can be several slots connected to the same signal, we need for each signal to have a list of the connected slots. Each connection must contain the receiver object, and the index of the slot. We also want the connections to be automatically destroyed when the receiver is destroyed, so each receiver object needs to know who is connected to him so he can clear the connection.

Here is the QObjectPrivate::Connection as defined in qobject_p.h :

struct QObjectPrivate::Connection
{
QObject *sender;
QObject *receiver;
union {
StaticMetaCallFunction callFunction;
QtPrivate::QSlotObjectBase *slotObj;
};
// The next pointer for the singly-linked ConnectionList
Connection *nextConnectionList;
//senders linked list
Connection *next;
Connection **prev;
QAtomicPointer<const int> argumentTypes;
QAtomicInt ref_;
ushort method_offset;
ushort method_relative;
uint signal_index : 27; // In signal range (see QObjectPrivate::signalIndex())
ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
ushort isSlotObject : 1;
ushort ownArgumentTypes : 1;
Connection() : nextConnectionList(0), ref_(2), ownArgumentTypes(true) {
//ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection
}
~Connection();
int method() const { return method_offset + method_relative; }
void ref() { ref_.ref(); }
void deref() {
if (!ref_.deref()) {
Q_ASSERT(!receiver);
delete this;
}
}
};

Each object has then a connection vector: It is a vector which associates for each of the signals a linked lists of QObjectPrivate::Connection.
Each object also has a reversed lists of connections the object is connected to for automatic deletion. It is a doubly linked list.

Linked lists are used because they allow to quickly add and remove objects. They are implemented by having the pointers to the next/previous nodes within QObjectPrivate::Connection
Note that the prev pointer of the senderList is a pointer to a pointer. That is because we don't really point to the previous node, but rather to the pointer to the next in the previous node. This pointer is only used when the connection is destroyed, and not to iterate backwards. It allows not to have a special case for the first item.

Signal Emission

When we call a signal, we have seen that it calls the MOC generated code which calls QMetaObject::activate.

Here is an annotated version of its implementation from qobject.cpp

void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
void **argv)
{
activate(sender, QMetaObjectPrivate::signalOffset(m), local_signal_index, argv);
/* We just forward to the next function here. We pass the signal offset of
* the meta object rather than the QMetaObject itself
* It is split into two functions because QML internals will call the later. */
} void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
{
int signal_index = signalOffset + local_signal_index; /* The first thing we do is quickly check a bit-mask of 64 bits. If it is 0,
* we are sure there is nothing connected to this signal, and we can return
* quickly, which means emitting a signal connected to no slot is extremely
* fast. */
if (!sender->d_func()->isSignalConnected(signal_index))
return; // nothing connected to these signals, and no spy /* ... Skipped some debugging and QML hooks, and some sanity check ... */ /* We lock a mutex because all operations in the connectionLists are thread safe */
QMutexLocker locker(signalSlotLock(sender)); /* Get the ConnectionList for this signal. I simplified a bit here. The real code
* also refcount the list and do sanity checks */
QObjectConnectionListVector *connectionLists = sender->d_func()->connectionLists;
const QObjectPrivate::ConnectionList *list =
&connectionLists->at(signal_index); QObjectPrivate::Connection *c = list->first;
if (!c) continue;
// We need to check against last here to ensure that signals added
// during the signal emission are not emitted in this emission.
QObjectPrivate::Connection *last = list->last; /* Now iterates, for each slot */
do {
if (!c->receiver)
continue; QObject * const receiver = c->receiver;
const bool receiverInSameThread = QThread::currentThreadId() == receiver->d_func()->threadData->threadId; // determine if this connection should be sent immediately or
// put into the event queue
if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
|| (c->connectionType == Qt::QueuedConnection)) {
/* Will basically copy the argument and post an event */
queued_activate(sender, signal_index, c, argv);
continue;
} else if (c->connectionType == Qt::BlockingQueuedConnection) {
/* ... Skipped ... */
continue;
} /* Helper struct that sets the sender() (and reset it backs when it
* goes out of scope */
QConnectionSenderSwitcher sw;
if (receiverInSameThread)
sw.switchSender(receiver, sender, signal_index); const QObjectPrivate::StaticMetaCallFunction callFunction = c->callFunction;
const int method_relative = c->method_relative;
if (c->isSlotObject) {
/* ... Skipped.... Qt5-style connection to function pointer */
} else if (callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
/* If we have a callFunction (a pointer to the qt_static_metacall
* generated by moc) we will call it. We also need to check the
* saved metodOffset is still valid (we could be called from the
* destructor) */
locker.unlock(); // We must not keep the lock while calling use code
callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv);
locker.relock();
} else {
/* Fallback for dynamic objects */
const int method = method_relative + c->method_offset;
locker.unlock();
metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv);
locker.relock();
} // Check if the object was not deleted by the slot
if (connectionLists->orphaned) break;
} while (c != last && (c = c->nextConnectionList) != 0);
}

Conclusion

We saw how connections are made and how signals slots are emitted. What we have not seen is the implementation of the new Qt5 syntax, but that will be for another post.

Update: The part 2 is available.

https://woboq.com/blog/how-qt-signals-slots-work.html

How Qt Signals and Slots Work(感觉是通过Meta根据名字来调用)的更多相关文章

  1. Why Does Qt Use Moc for Signals and Slots(QT官方的解释:GUI可以是动态的)

    GUIs are Dynamic C++ is a standarized, powerful and elaborate general-purpose language. It's the onl ...

  2. Dynamic Signals and Slots

    Ref https://doc.qt.io/archives/qq/qq16-dynamicqobject.html Trolltech | Documentation | Qt Quarterly ...

  3. ZetCode PyQt4 tutorial signals and slots

    #!/usr/bin/python # -*- coding: utf-8 -*- """ ZetCode PyQt4 tutorial In this example, ...

  4. 【Qt编程】基于Qt的词典开发系列&lt;八&gt;--用户登录及API调用的实现

    在上一篇文章<调用网络API>中,我仅仅讲述了怎样直观的使用API接口以及调用API后返回的结果,本文则从程序实现的角度来实现API的调用.当然本程序的实现也是借助于扇贝网的API接口文档 ...

  5. Create a Qt Widget Based Application—Windows

    This turtorial describes how to use Qt Creator to create a small Qt application, Text Finder. It is ...

  6. QT Slot/Signal

    QT的Slot/Singal-槽/信号 1.Usage/使用方法 所有从 QObject 或其子类 ( 例如 Qwidget) 派生的类都能够包含 信号/signal和 槽/slot.当对象改变其状态 ...

  7. Qt信号和槽的个人总结

    1.connect connect(sender,SIGNAL(signal()),receiver,SLOT(slot())); 这里用到了两个宏:SIGNAL() 和SLOT():通过connec ...

  8. Qt的槽可以使用默认参数

    引用自:http://www.ibm.com/developerworks/cn/linux/guitoolkit/qt/signal-slot/ 的一篇经典文章,是关于Qt的信号和槽的分析的.看年份 ...

  9. Qt 和 Boost关于信号和槽的对比说明

    对比 无论是 Qt 的实现方式还是 Boost 的实现方式,除了必须的定义信号和槽的类之外,都不需要额外的类. 两种实现都解决了类爆炸的问题.下面让我们对照着来看一下我们前面的分析. 两个不同的术语以 ...

随机推荐

  1. Android菜鸟的成长笔记(21)——跨进程调用Service

    我们都知道在Android中的每一个应用是一个进程,其实每一个应用就相当于Linux系统里面的一个用户,进程和进程之间的通信其实就相当于用户和用户之间的通信,为了实现这种跨进程通信,Android提供 ...

  2. C# 反射调用私有事件

    原文:C# 反射调用私有事件 在 C# 反射调用私有事件经常会不知道如何写,本文告诉大家如何调用 假设有 A 类的代码定义了一个私有的事件 class A { private event EventH ...

  3. 机器学习: Viola-Jones 人脸检测算法解析(二)

    上一篇博客里,我们介绍了VJ人脸检测算法的特征,就是基于积分图像的矩形特征,这些矩形特征也被称为Haar like features, 通常来说,一张图像会生成一个远远高于图像维度的特征集,比如一个 ...

  4. OpenCV 图像清晰度评价(相机自动对焦)

    相机的自动对焦要求相机根据拍摄环境和场景的变化,通过相机内部的微型驱动马达,自动调节相机镜头和CCD之间的距离,保证像平面正好投影到CCD的成像表面上.这时候物体的成像比较清晰,图像细节信息丰富. 相 ...

  5. nyoj116士兵杀死(两)段树单点更新

    士兵杀敌(二) 时间限制:1000 ms  |  内存限制:65535 KB 难度:5 描写叙述 南将军手下有N个士兵,分别编号1到N,这些士兵的杀敌数都是已知的. 小工是南将军手下的军师,南将军常常 ...

  6. MCB2300的CTM1050(CAN) - 系列示意图

    这一系列示意图由Portel DXP 2004绘. 截图: 文件下载: CTM1050.7z 版权声明:本文博客原创文章,博客,未经同意,不得转载.

  7. C#使用Selenium实现QQ空间数据抓取 登录QQ空间

    经@吃西瓜的星星提醒 首先我们介绍下Selenium Selenium也是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE.Mo ...

  8. Angular 请求另一服务的api(请求代理)

    1.edit "start" of your package.json to look below 定义一个叫做start的新命令 "start": " ...

  9. Wireshark基本介绍和学习TCP三次握手 专题

    wireshark有两种过滤器: 捕捉过滤器(CaptureFilters):用于决定将什么样的信息记录在捕捉结果中.显示过滤器(DisplayFilters):用于在捕捉结果中进行详细查找. 捕捉过 ...

  10. WPF 4 日历控件(Calendar)

    原文:WPF 4 日历控件(Calendar)      在之前我已经写过两篇关于WPF 4 任务栏(Taskbar)相关的特性.相信自从VS2010 Beta 版放出后,WPF 的粉丝们肯定在第一时 ...