Signals and slots

因为简历里面写了Qt,所以..特别的复习了一下Signals and slots的实现。

首先,Signals and slots是观察者模式的一种实现。整个机制包括四个关键字,Signals,Slots,Connect和Emit。以下是一个官方的Demo。

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);
};

void Counter::setValue(int value)
{
    if (value != m_value) {
        m_value = value;
        emit valueChanged(value);
    }
}
int main()
{
  Counter a, b;
  QObject::connect(&a, SIGNAL(valueChanged(int)),
                   &b, SLOT(setValue(int)));

  a.setValue(12);  // a.value() == 12, b.value() == 12
}

Signals && Slots

SIGNAL与SLOT宏会利用预编译器将一些参数转化成字符串,并且在前面添加上编码。
# define SLOT(a) "1"#a
# define SIGNAL(a) "2"#a
 。其实在类内定义的slots: signals: 都没有任何的意义。

MOC( Meta-Object Compiler)

Qt的精髓之一,所谓的元对象编译器,用于解析Qt的宏和生成代码。包括实现反射和Signals和Slots。

因此,当执行MOC时,会分析源程序中的,生成Introspection Tables,以下就是Introspection Tables的表现,重点看methods,2代表有两个methods,14代表开始的index是14。

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
};

同时还会生成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
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
valueChanged
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
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
newValue
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
setValue
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
" "value" }; #undef QT_MOC_LITERAL

之后就是真实的调用机制的。

Signals

实现的方式很简单,就是将参数存到一个数组中然后使用QMetaObject::activate即可。其中第三个参数0,表示的是Signal的Index。

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

这里的switch case 是MOC自动生成的,更具每个人的Id。

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: ;
        }
}
Connection

首先Qt会根据String Table 找到Signals的Index和Slots的Index。首先有几个需求,为了能够快速的找到Connection根据给定的Signal Index,同时一个Signal会有好几个Slots同时连着,因为我们需要给每一个Signal一个List,其中需要保存Objects和Slots的Index。同时,在Slots被清除,也需要能够自动在Connection中删掉相关的信息。

具体的实现就是对于每个Signal都有一个双向链表的ConnectionVector。因为链表能够快速的插入和删除。

同时Slots也有一个单向链表,其中prev并不是一个双向链表,而是在销毁时方便时而已,源码

Emit

Emit在MOC中被转换成了QMetaObject::activate

总的来说,即使在程序编译阶段,由Qt的MOC将Signals和Slots使用Metadata连接起来了,emit时调用了activate 程序,之后就调用了qt_static_metacall,之后就根据swtich case语句调用Slots即可。

Qt5的实现

以上的实现均为Qt4的基本实现,而Qt5中使用模板元对Connect有了新的实现方式。能够在编译时检查Signals和Slots的类型,同时还支持Lambda。

线程安全的问题

信号槽的执行方式有几种,参考connect的最后一个参数,这个参数经常被省略掉。其中一个是DirectConnection,就是直接连接,跟踪代码会发现,emit之后仅仅经过了signal/slot匹配就直接调用了slot函数,这种不是线程安全的,所以只能连接处于同一线程中的Qt对象,因为界面控件一般都是在UI线程创建的,所以平常我们在控件之间传输数据就不用考虑线程安全了。但是如果两个对象处于不同线程,Direct就有线程安全问题了,Qt也考虑到了这个问题,此时用Direct会被忽略,而需要用另一种连接方式,QueuedConnection,顾名思义,信号emit之后Qt会先把emit放到目标线程的事件队列中,这时emit会立即返回,等目标线程的主循环执行到了处理事件的时候才会执行slot,而Qt的这个事件队列经过了处理是线程安全的。

 

Reference

https://woboq.com/blog/how-qt-signals-slots-work.html
https://woboq.com/blog/how-qt-signals-slots-work-part2-qt5.html
https://woboq.com/blog/qmetatype-knows-your-types.html
https://woboq.com/blog/how-qt-signals-slots-work-part3-queuedconnection.html

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注