Qt源码分析之信号和槽机制

2023-11-15

原文在这里:http://blog.csdn.net/oowgsoo/article/details/1529411

Qt的信号和槽机制是Qt的一大特点,实际上这是和MFC中的消息映射机制相似的东西,要完成的事情也差不多,就是发送一个消息然后让其它窗口响应,当然,这里的消息是广义的
说法,简单点说就是如何在一个类的一个函数中触发另一个类的另一个函数调用,而且还要把相关的参数传递过去.好像这和回调函数也有点关系,但是消息机制可比回调函数有用
多了,也复杂多了

MFC中的消息机制没有采用C++中的虚函数机制,原因是消息太多,虚函数开销太大.在Qt中也没有采用C++中的虚函数机制,原因与此相同.其实这里还有更深层次上的原因,大体说来,
多态的底层实现机制只有两种,一种是按照名称查表,一种是按照位置查表,两种方式各有利弊,而C++的虚函数机制无条件的采用了后者,导致的问题就是在子类很少重载基类实现
的时候开销太大,再加上象界面编程这样子类众多的情况,基本上C++的虚函数机制就废掉了,于是各家库的编写者就只好自谋生路了,说到底,这确实是C++语言本身的缺陷

示例代码:

#include <QApplication>
#include <QPushButton>
#include <QPointer>

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

这里主要是看QPushButton的clicked()信号和app的quit()槽如何连接?又是如何响应?
前面已经说过了,Qt的信号槽机制其实就是按照名称查表,因此这里的首要问题是如何构造这个表?
和C++虚函数表机制类似的,在Qt中,这个表就是元数据表,Qt中的元数据表最大的作用就是支持信号槽机制,当然,也可以在此基础上扩展出更多的功能,因为
元数据是我们可以直接访问到的,不再是象虚函数表那样被编译器遮遮掩掩的藏了起来,不过Qt似乎还没有完全发挥元数据的能力,动态属性,反射之类的机制好像还没有

任何从QObject派生的类都包含了自己的元数据模型,一般是通过宏Q_OBJECT定义的

#define Q_OBJECT /
public: /
    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:

首先声明了一个QMetaObject类型的静态成员变量,这就是元数据的数据结构

struct Q_CORE_EXPORT QMetaObject
{
...
    struct { // private data
        const QMetaObject *superdata;
        const char *stringdata;
        const uint *data;
        const QMetaObject **extradata;
    } d;
}


QMetaObject中有一个嵌套类封装了所有的数据
        const QMetaObject *superdata;//这是元数据代表的类的基类的元数据
        const char *stringdata;//这是元数据的签名标记
        const uint *data;//这是元数据的索引数组的指针
        const QMetaObject **extradata;//这是扩展元数据表的指针,一般是不用的       

这里的三个虚函数metaObject,qt_metacast,qt_metacall是在moc文件中定义的
metaObject的作用是得到元数据表指针
qt_metacast的作用是根据签名得到相关结构的指针,注意它返回的可是void*指针
qt_metacall的作用是查表然后调用调用相关的函数

宏QT_TR_FUNCTIONS是和翻译相关的

#  define QT_TR_FUNCTIONS /
    static inline QString tr(const char *s, const char *c = 0) /
        { return staticMetaObject.tr(s, c); }

好了,看看实际的例子吧:

QPushButton的元数据表如下:

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,
      64,   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<const QMetaObjectPrivate*>(data); }

这种转换怎么看都有些黑客的味道,这确实是十足的C风格

再结合实际的数据看一看

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_data_QPushButton中,索引是13,就是showMenu()了
      24,   12,   12,   12, 0x08,
      第二个自定义方法的签名存储在qt_meta_data_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
      64,   39, 0x01095103,   
      第三个自定义属性的签名存储在qt_meta_data_QPushButton中,索引是64,就是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"
};

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_data_QPushButton中,索引是13,就是showMenu()了
      24,   12,   12,   12, 0x08,
      第二个自定义方法的签名存储在qt_meta_data_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
      64,   39, 0x01095103,   
      第三个自定义属性的签名存储在qt_meta_data_QPushButton中,索引是64,就是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
      89,   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

基本上都是大同小异的

  QObject::connect(&quit, SIGNAL(clicked()), &app, SLOT(quit()));
下面开始看信号和槽连接的源码了

// connect的源码
connect函数是连接信号和槽的桥梁,非常关键

bool QObject::connect(const QObject *sender, const char *signal,
                      const QObject *receiver, const char *method,
                      Qt::ConnectionType type)
{
#ifndef QT_NO_DEBUG
    bool warnCompat = true;
#endif
    if (type == Qt::AutoCompatConnection) {
        type = Qt::AutoConnection;
#ifndef QT_NO_DEBUG
        warnCompat = false;
#endif
    }

 // 不允许空输入
    if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
#ifndef QT_NO_DEBUG
        qWarning("Object::connect: Cannot connect %s::%s to %s::%s",
                 sender ? sender->metaObject()->className() : "(null)",
                 signal ? signal+1 : "(null)",
                 receiver ? receiver->metaObject()->className() : "(null)",
                 method ? method+1 : "(null)");
#endif
        return false;
    }
    QByteArray tmp_signal_name;

#ifndef QT_NO_DEBUG
 // 检查是否是信号标记
    if (!check_signal_macro(sender, signal, "connect", "bind"))
        return false;
#endif
 // 得到元数据类
    const QMetaObject *smeta = sender->metaObject();
    ++signal; //skip code跳过信号标记,直接得到信号标识
 // 得到信号的索引
    int signal_index = smeta->indexOfSignal(signal);
    if (signal_index < 0) {
        // check for normalized signatures
        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", sender, receiver);
#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", sender, receiver);
#endif
        return false;
    }
#ifndef QT_NO_DEBUG
 // 检查参数,信号和槽的参数必须一致,槽的参数也可以小于信号的参数
    if (!QMetaObject::checkConnectArgs(signal, method)) {
        qWarning("Object::connect: Incompatible sender/receiver arguments"
                 "/n/t%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).", smeta->className(), signal);
            } 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<QObject*>(sender)->connectNotify(signal - 1);
    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<QObject *>(sender), signal_index,
                        const_cast<QObject *>(receiver), method_index, type, types);
    return true;
}

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列表中

一旦我们发送了信号,就应该调用相关槽中的方法了,这个过程其实就是查找全局的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<void*>(reinterpret_cast<const void*>(&_t1)) };
 // 调用元数据类的激活
    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<int> connections;
    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) {
  // 这里就是真正的调用方法了,注意参数的解包用法
        case 0: valueChanged(*reinterpret_cast< int(*)>(_a[1])); break;
        case 1: setValue(*reinterpret_cast< int(*)>(_a[1])); break;
        }
        _id -= 2;
    }
    return _id;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt源码分析之信号和槽机制 的相关文章

  • qt:同一份代码在vs2022 QT VS TOOL扩展和 QtCreator下运行结果不同

    公司要求用的是QtCreator 但是谁能离得开安装了Resharper的VS呢 我就在VS下装了QT的环境 开始编写调试代码 其实是两个软件都在用的 可能是没找到方法 VS下的资源文件显示不是很方便 我就用QtCreator加资源 到后面
  • QT 新手小白USBCAN 学习

    一 介绍CAN总线 CAN总线介绍 二 USBCAN总线 2 1 产品介绍 USBCAN 转换器模块实现了将 CAN 总线接口与 USB 接口进行相互转换 可以 简单方便的通过电脑监视 CAN 总线网络 同时可以实现工业现场数据稳定的双 向
  • QXlsx读写数据库

    最近写读写xlsx文件的工具 用了Qt自带的比较卡 操作也不舒服 最后选择用了QXlsx QXlsx源码地址 github https github com dbzhang800 QtXlsxWriter QXlsx官网连接 Documen
  • Qt 为.h和.cpp文件添加ui文件

    假设在工程中已经有了一个纯类A的头文件a h和源文件a cpp 现在想给这个纯类文件添加UI 可以通过以下操作来实现 给工程添加一个和类同名的UI文件a ui 在a cpp中添加UI的头文件 头文件的名字应该形如ui xxx h 但在添加时
  • qt控件学习(4)

    文章目录 QTabWidget 控件 QMenu QToolBar 控件 QSystemTrayIcon 任务栏控件 QTabWidget 控件 mainwindow h ifndef MAINWINDOW H define MAINWIN
  • 模拟点击事件

    一 通过代码模拟用户对按钮的点击 模拟按钮的点击 方法一 使用btn click模拟用户的点击 btn click 方法二 两秒之后自动松开按钮 btn animateClick 2000 区别是方法一没有什么动画 界面展示 方法二有时间效
  • qt 中 file generation failure: unable to create the directory

    原因 不能将qt的项目工程安装到qt软件的安装目录中 如 opt 的目录中 解决 应该使用当前系统的普通用户的目录中建立项目目录 如 home chenfan QT myprojects
  • QT---窗口、按钮的基本设置

    目录 一 窗口相关的设置及中文编译错误设置 1 在源文件widget cpp中进行修改数据 并创建有关界面 2 如遇中文编译错误 即标题中文显示乱码 可如下设置 3 窗口界面及标题设置 窗口是否拉伸 二 创建按钮的相关设置 1 添加头文件
  • Qt编译没使用Q_OBJECT导致编译出错,然后加入后编译仍出错的解决方法。

    这个问题 困扰我一下午 之前没加Q OBJECT导致不能使用信号和槽功能 导致我的程序已知编译出错 后来发现加上后 还是不能编译成功 继续出错 最后在overfolow stack上面找到了答案 原因首先是编译时没加Q OBJECT导致编译
  • qt学习笔记5:资源文件的添加、模态和非模态对话框创建

    这次创建的时候勾选ui创建界面 打开 ui文件 在这里就可以通过拖拽的方式对内容进行实现 可以在 ui中进行简单界面设计一些拖拽生成需要的控件 然后在 cpp中进行代码实现 添加资源 比如要添加图片 首先将资源文件放到文件项目中 可以通过右
  • QT报错:multiple definition of 'qMain(int ,char**)'

    QT导入项目时 出现重定义错误 今天在导入人家完整的QT项目时 在导入之后 点击构建 出现很多重定义的问题 具体如下图所示 出现重定义错误 经过网上查找解决办法 得知是因为重复的导入项目 导致项目中项目文件重复引用了一些文件 如下图所示 解
  • QT发布软件

    Qt Creator 完成对release版本编译完成之后 就需要将exe文件发布出来 单纯的只拷贝exe文件是不能运行的 exe的运行需要依赖很多的Qt库 1 生成可以执行的exe文件 这里需要将exe文档放在一个单独创建的test文件夹
  • 数据隐藏之Qt中d指针详解

    最近看到代码有用到了Qt中的Q D指针 就去学习了下 发现真的很好用 因此写一篇文章总结下 student h class CStudent public CStudent CStudent private string m name in
  • Qt源码分析之信号和槽机制

    原文在这里 http blog csdn net oowgsoo article details 1529411 Qt的信号和槽机制是Qt的一大特点 实际上这是和MFC中的消息映射机制相似的东西 要完成的事情也差不多 就是发送一个消息然后让
  • Qt学习11:Dialog对话框操作总结

    文章首发于我的个人博客 欢迎大佬们来逛逛 完整Qt学习项目地址 源码地址 文章目录 QDialog QDialogButtonBox QMessageBox QFileDialog QFontDialog QColorDialog QInp
  • QT基础学习(12)---事件过滤

    文章目录 事件过滤 一 事件过滤 实现该功能的方法就是在目标部件 自定义的图片显示部件 上注册事件过滤器 此时的事件过滤器就是我们所说的监视对象 完成这些步骤之后 当目标部件有事件产生后 首先会传递给监视对象 事件过滤器 进行处理而不是该事
  • QT QTabWidget

    QTabWidget 使用 1 1 ui界面添加tabWidget 想展示的Widget 1 2 TabWidget 动态添加tab 1 3 tabWidget 动态删除tab 1 4 TabWidget 获取所有tab的界面值 2 1 T
  • QT使用emit时发生内存泄露

    1 场景 在QT里面使用多线程进行编程时 子线程执行的函数里面使用了emit发生了内存泄露 2 主要原因 在使用子线程时 线程使用了join 来等待子线程完成 这样使用emit也不会发送信号 因为join 是阻塞的 必须等待当前线程完成 3
  • QT编译环境配置,以及开发板移植的问题

    一 QT编译环境的设置 编译环境的配置 这个是真个系统构建的时候配置的问题 比较麻烦 后面在补这部分的知识 韦东山的开发板和乌班图的编译工具链里面是具有qt的编译工具链的 自己看的是正点原子的视频 所以按照正点正点原子的编译工具进行配置的
  • Qt学习_17_一些关于QTableWidget的记录

    1 QTableWidget clear 程序异常退出 近日 项目中使用到QTableWidget 遇到一个问题 项目需要清空这个表格 但是无论调用clear clearContents 程序都报 程序异常退出 而且项目程序还比较多 最开始

随机推荐

  • IDEA导入eclipse项目并部署tomcat运行Web项目同时配置打WAR包

    工作中点滴记录 本人比较熟悉常用eclipse开发工具 最近在项目中使用eclipse导入同事提供的项目出现文件上传OSS服务失败报找不到桶地址 但使用IDEA打包运行正常 原因未知 迫不得已只能改用IDEA开发 在使用IDEA导入ecli
  • python-Excel多个表格合并

    时间 2019 02 19 工具 python3 7 1 用到的模块 xlrd 负责读取数据 xlsxwriter 负责写入数据 glob 查找符合自己目的的文件 一 模块安装 pip 注意 这里我用的是pip模块来安装 部分python版
  • 算法设计技巧与分析 答案整理

    算法设计技巧与分析 沙特版 这书答案真难找啊 东拼西凑薅出这么些 https wenku baidu com view 279b9245561252d380eb6ea4 html https wenku baidu com view af5
  • 线性分类模型--感知机(perceptron)

    线性分类模型 感知机 perceptron 1 引言 分类问题 分类是监督学习的一个核心问题 在监督学习中 当输出变量Y取有限个离散值时 预测问题便成为分类问题 基本概念可以参考 机器学习方法概论1 监督学习从数据中学习一个分类模型或分类决
  • 蓝桥杯如何混分

    文章目录 蓝桥杯 一 如何暴力求解 二 举例说明 1 蓝桥杯真题 2 暴力代码 3 正解思路 代码 总结 蓝桥杯 蓝桥杯是OI赛制 也就是说即使不会算法 也可以暴力求解 拿到分数 一 如何暴力求解 可以在电脑的编译器上使用超过时间的循环暴力
  • 高等数值计算方法学习笔记第6章【解线性代数方程组的迭代方法(高维稀疏矩阵)】

    高等数值计算方法学习笔记第6章 解线性代数方程组的迭代方法 高维稀疏矩阵 一 引言 1 例题 说明迭代法的收敛性研究的重要性 2 定义 迭代法 迭代法收敛 解误差 二 基本迭代法 1 雅可比迭代法 2 高斯 塞德尔迭代法 Gauss Sei
  • 探究ConcurrentHashMap中键值对在Segment[]的下标如何确定

    内容 本文对JDK1 7下使用segmentShift和segmentMask求解ConcurrentHashMap键值对在Segment 中的下标值进行了探究和论证 适合人群 Java进阶 说明 转载请注明出处 尊重笔者的劳动成果 推荐阅
  • 新媒体运营数据分析怎么做?

    一 分析数据的意义 了解运营质量 预测运营方向 控制运营成本 评估营销方案 二 新媒体数据分析基本步骤 1 设定目的 提炼出最准确清晰的目的 eg 看看为什么最近公众号涨粉情况不好 近期粉丝增长缓慢 推广没做好 寻找推广错误环节 真正目的
  • 【C语言】变量/常量/宏定义,数据类型以及取值范围

    变量和常量是程序处理的两种基本数据对象 变量 变量的意义就是确定目标并提供存放的空间 变量的命名规则 1 变量名只能是英文字母 A Z a z 和数字 0 9 或者下划线 组成 2 第一个字母必须是字母或者下划线开头 3 变量名区分大小写
  • CompletableFuture使用(四)

    CompletableFuture中需要多个异步任务执行 再执行异步操作 allOf和anyOf 1 allOf CompletableFuture是多个任务执行完成后才会执行 有一个任务执行异常CompletableFuture执行get
  • 自学编程,10个程序员学习必收藏的编程网站,你知道几个?

    很多小伙伴在刚开始学习编程的时候 都去找一些网站来提高自己的水平 但现在网络越来越发达 学习类的网站真的是多如牛毛 有的网站真的是非常的优秀 可以让你学到不少的技术 但是同样也有的网站真的是非常的垃圾 不仅会浪费你的时间 而且还可能会误导你
  • SSH远程终端神器,你在用哪一款

    唠嗑部分 在我们日常开发中啊 不可避免的要与Linux打交道 虽然我们作为开发 不要求我们对Linux有多么的专业 但是基本的操作还是要会的 举几个常用的例子 1 查看nginx配置 配置转发 2 清理maven仓库依赖 3 搭建环境 如r
  • 修改服务器磁盘槽位编号,服务器磁盘槽位管理

    服务器磁盘槽位管理 内容精选 换一换 本文以裸金属服务器的操作系统为 Windows Server 2008 R2 Enterprise 为例 初始化数据盘 不同裸金属服务器的操作系统的格式化操作可能不同 本文仅供参考 具体操作步骤和差异请
  • 2023软件测试面试大全(超详细~)

    Part1 1 你的测试职业发展是什么 测试经验越多 测试能力越高 所以我的职业发展是需要时间积累的 一步步向着高级测试工程师奔去 而且我也有初步的职业规划 前3年积累测试经验 按如何做好测试工程师的要点去要求自己 不断更新自己改正自己 做
  • Python课程设计题目

    文章目录 1 基于Python的XX管理系统 2 基于Python的XX工具开发 1 基于Python的XX管理系统 实例要求 采用面向对象或是字典保存实例的属性信息 功能要求 提供增加 删除 修改 单个查询 查询所有 保存信息到本地文件
  • 制作Win7多合一原版系统光盘镜像

    先看看效果 提前准备工具 1 UltraISO 链接 https pan baidu com s 1cXff0 PjKPPmRr8 zJNJHA 密码 nnj1 2 GimageX 链接 https pan baidu com s 1fqG
  • element ui select multiple 多选数据修改回显失灵问题的解决

    使用change方法 this forceUpdate 重新渲染
  • 管理“项目”之人际关系篇

    项目管理里的人际关系 也是令人头疼且最重要的 管的好 众人拾柴 火焰高 管的不好 眼看他起高楼 眼看他楼塌了 文章目录 前言 一 项目关系人管理 1 项目创立一定要获得关系人尤其主要关系人 或市场 的认可 2 识别相关人员 识别关系人 3
  • Cesium:入门教程(一)之 Hello World

    简介 Cesium是国外一个基于JavaScript编写的使用WebGL的地图引擎 Cesium支持3D 2D 2 5D形式的地图展示 它提供了基于JavaScript语言的开发包 方便用户快速搭建一款零插件的虚拟地球Web应用 并在性能
  • Qt源码分析之信号和槽机制

    原文在这里 http blog csdn net oowgsoo article details 1529411 Qt的信号和槽机制是Qt的一大特点 实际上这是和MFC中的消息映射机制相似的东西 要完成的事情也差不多 就是发送一个消息然后让