现在的位置: 首页 > 综合 > 正文

信号槽细节

2018年02月16日 ⁄ 综合 ⁄ 共 6680字 ⁄ 字号 评论关闭

(1)Reciver对象可能有多个方法,qt如何知道要调用哪个函数详解

我们从QMetaObjectPrivate::connect(sender, signal_index, receiver, method_index, type, types)

函数入手。sender, receiver是外部传入的参数,signal_index, method_index是两个intsignal,method是字符串。QObject::connect(s,SIGNAL(send(int)),r1,SLOT(count(int)))中,SIGNAL(send(int))产生signal,内容为 "send(int)", SLOT(count(int))产生method,内容为 "1count(int)"Connect函数中,先得到sender的元对象smeta = sender->metaObject(),然后通过这个函数 int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal),得到send(int)方法在元对象smeta中的索引值。通过rmeta = receiver->metaObject()得到元对象,method_index = rmeta->indexOfSlot(method);在receiver的元对象中找到"coun(int)"对应的函数的索引。知道了sender对象且知道要调用其函数的索引,知道了receiver对象且知道要调用其函数的索引,将四者连在一起。当触发sendersignal_index对应的函数时,调用已

连在一起的receivermethod_index对应的函数。

下面是具体的代码分析

QObject::connect(s,SIGNAL(send(int)),r1,SLOT(count(int)));  
bool QObject::connect(const QObject *sender, const char *signal,
                      const QObject *receiver, const char *method,
                      Qt::ConnectionType type)
{
//................................................
//<section1>
    const QMetaObject *smeta = sender->metaObject();
    const char *signal_arg = signal;
    ++signal; //skip code
int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal);
//</section1>
//................................................
//<section2>
    const QMetaObject *rmeta = receiver->metaObject();
    int method_index = -1;
    switch (membcode) {
    case QSLOT_CODE:
        method_index = rmeta->indexOfSlot(method);
        break;
    case QSIGNAL_CODE:
        method_index = rmeta->indexOfSignal(method);
        break;
    }
//</section2>
//.............................................
//<section3>
 if (!QMetaObject::checkConnectArgs(signal, method)) {
        qWarning("QObject::connect: Incompatible sender/receiver arguments"
                 "\n        %s::%s --> %s::%s",
                 sender->metaObject()->className(), signal,
                 receiver->metaObject()->className(), method);
        return false;
    }
//</section3>
//..............................................
//<section4>
    if (!QMetaObjectPrivate::connect(sender, signal_index, receiver, method_index, type, types))
        return false;
//</section4>
    const_cast<QObject*>(sender)->connectNotify(signal - 1);
    return true;
}

在此文中,sender对象是Informer的对象.receiver对象是Receiver的对象。

section1,const QMetaObject *smeta = sender->metaObject();得到了sender的元对象。这个元对象定义在.moc文件中。在Informer类中,有一个Q_OBJECT

class Informer:public QObject  

{  

Q_OBJECT;

//.....................

};

其中有个数据成员

 static const QMetaObject staticMetaObject;

还有获得这个元对象的函数

virtual const QMetaObject *metaObject() const; \ 

.moc文件中实现了它们

//<code_1>

const QMetaObject Informer::staticMetaObject = {

    { &QObject::staticMetaObject, qt_meta_stringdata_Informer,

      qt_meta_data_Informer, 0 }

};

//</code_1>

const QMetaObject *Informer::metaObject() const

{

    return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;

}

sender->metaObject()便得到了QMetaObject对象。

重要代码

int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal);

函数indexOfSignalRelative从sender对象的元对象中查找signal,即字符串"send(int)"对应的索引值。先看一下QMetaObjectPrivate的数据结构

//<code_2>
struct QMetaObjectPrivate
{
    int revision;
    int className;
    int classInfoCount, classInfoData;
    int methodCount, methodData;
    int propertyCount, propertyData;
    int enumeratorCount, enumeratorData;
    int constructorCount, constructorData; //since revision 2
    int flags; //since revision 3
int signalCount; //since revision 4
}
//</code_2>

对比一下.moc文件中的qt_meta_data_Informer数组

//<code_3>
static const uint qt_meta_data_Informer[] = {
 // content:
       4,       // revision  //0
       0,       // classname
       0,    0, // classinfo //2
       1,   14, // methods//4
       0,    0, // properties//6
       0,    0, // enums/sets//8
       0,    0, // constructors//10
       0,       // flags//12
       1,       // signalCount//13
 // signals: signature, parameters, type, tag, flags
      17,   10,    9,    9, 0x05, //14
       0        // eod
};
//</code_3>

发现qt_meta_data_Informer前14个字段对应QMetaObjectPrivate数据结构中的字段。能不能由qt_meta_data_Informer得到QMetaObjectPrivate对象呢。是可以的。见Qt的函数priv,代码如下

static inline const QMetaObjectPrivate *priv(const uint* data)

{ return reinterpret_cast<const QMetaObjectPrivate*>(data); }

这样便得到了QMetaObjectPrivate对象。

我们回过头来看indexOfSignalRelative函数,代码如下

int QMetaObjectPrivate::indexOfSignalRelative(const QMetaObject **baseObject, const char *signal)
{
    int i = -1;
    while (*baseObject) {
        const QMetaObject *const m = *baseObject;
        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) {
                break;
            }
        if (i >= 0)
            break;
        *baseObject = m->d.superdata;
}
//......................................
}

QMetaObject的数据结构如下

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

m->d.data是const uint *data。m->d.data保存了qt_meta_data_Informer静态数组的指针,见code_1priv(m->d.data)将qt_meta_data_Informer转化成QMetaObjectPrivate数据类型。很明显,priv(m->d.data)->methodCount为14, priv(m->d.data)->methodData + 5*i + 4 = 18, m->d.data[priv(m->d.data)->methodData + 5*i + 4] = m->d.data[18], m->d.tata[18]0x05, MethodTypeMask = 0x0c, 0x05 & 0x0c = 0x04 == MethodSignal。

这段代码(m->d.data[priv(m->d.data)->methodData + 5*i + 4] & MethodTypeMask) == MethodSignal,判断从元对象而来的函数是不是Signal,因为元对象的方法也可能是Slot

strcmp(signal, m->d.stringdata

                + m->d.data[priv(m->d.data)->methodData + 5*i])

priv(m->d.data)->methodData + 5*i = 14

d.data[14]17

m->d.stringdata+ m->d.data[priv(m->d.data)->methodData + 5*i] = m->d.stringdata+17

strcmp(signal, m->d.stringdata+17)

Signal"send(int)", m->d.stringdata为 "Informer",看一下.moc文件中有如下代码

static const char qt_meta_stringdata_Informer[] = {

    "Informer\0\0status\0send(int)\0"

};

而 m->d.stringdata在IDE中显示为 "Informer",这是因为\0的原因。

我们看下m->d.stringdata内存,如下

查一下"Informer..status."刚好17m->d.stringdata+17正好移动到"send(int)"起始处。这回就明白了qt_meta_data_Informer中17的原因了。

(2)如何将参数传给reciver对应的函数

这个问题就简单了。看<section3>函数checkConnectArgs。这个函数很简单,就是检查const char *signal, const char *method参数是否一样。就是简单的字符串比较

代码如下

/*!
    Returns true if the \a signal and \a method arguments are
    compatible; otherwise returns false.
    Both \a signal and \a method are expected to be normalized.
    \sa normalizedSignature()
*/
bool QMetaObject::checkConnectArgs(const char *signal, const char *method)
{
    const char *s1 = signal;
    const char *s2 = method;
    while (*s1++ != '(') { }                        // scan to first '('
    while (*s2++ != '(') { }
    if (*s2 == ')' || qstrcmp(s1,s2) == 0)        // method has no args or
        return true;                                //   exact match
    int s1len = qstrlen(s1);
    int s2len = qstrlen(s2);
    if (s2len < s1len && strncmp(s1,s2,s2len-1)==0 && s1[s2len-1]==',')
        return true;                                // method has less args
    return false;
}

 参数类型一样后,在信号发送端,所有参数强转成void * [], 储存在void **argv中,在接受端强转成对应参数既可。

代码如下

// SIGNAL 0
void Informer::send(int _t1)
{
    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
int Receiver::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) {
        switch (_id) {
        case 0: count((*reinterpret_cast< int(*)>(_a[1]))); break;
        default: ;
        }
        _id -= 1;
    }
    return _id;
}

     本文分析了Qt信号槽机制脉络,更具体的细节就不分析了。至此完成了此机制的分析。

【上篇】
【下篇】

抱歉!评论已关闭.