您的位置:首页 > 编程语言 > Qt开发

解析Qt的信号-槽机制是如何工作的 - How Qt Signals and Slots Work

2014-04-02 12:28 741 查看
How Qt Signals and Slots Work - Qt的信号-槽机制是如何工作的original:http://woboq.com/blog/how-qt-signals-slots-work.html
译:NewThinker_wei NewThinker_Jiwey @CSDN

(译注:这是原文和译文的对照版,不方便阅读,仅供对比参考。想看译文请点这里:/article/2700782.html )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.很多人都知道Qt的信号和槽机制,但它究竟是如何工作的呢?在本篇博文中,我们会探索QObject 和 QMetaObject 内部的一些具体实现,揭开信号-槽的神秘面纱。在本篇博文中,我会贴出部分Qt5的源码,有时候还会根据需要改变一下格式并做一些简化。First, let us recall how signals and slots look like by showing the
official example.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()
在源文件的某个地方,可以找到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的对象:
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.这是从1992年Qt最初阶段开始就沿用下来而几乎没有变过的原始语法。虽然基本的API并没有怎么变过,但它的实现方式却变了几次。很多新特性被添加了进来,底层的实现也发生了很多变化。不过这里面并没有什么神奇的难以理解的东西,本篇博文会向你展示这究竟是如何工作的,MOC, the Meta Object Compiler - MOC,元对象编译器
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.
Qt的信号/槽和属性系统都基于其能在运行时刻对对象进行实时考察的功能。实时考察意味着即使在运行过程中也可以列出一个对象有哪些方法(成员函数)和属性,以及关于它们的各种信息(比如参数类型)。
如果没有实时考察这个功能,QtScript 和
 QML 就基本不可能实现了。

C++ does not offer introspection support natively, so Qt comes with a tool to provide it. That tool is MOC. It is acode 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 theQt documentation respond to this
criticism. There is nothing wrong with code generators and the MOC is of a great help.C++本身不提供对实时考察的支持,所以Qt就推出了一个工具来提供这个支持。这个工具就是MOC。注意,它是一个代码生成器,而不是很多人说的“预编译器”。MOC会解析头文件并为每一个含有Q_OBJECT宏的头文件生成一个对应的C++文件(这个文件会跟工程中的其他代码一块参与编译)。这个生成的C++文件包含了实时考察功能所需的全部信息(文件一般被命名为moc_HeaderName。cpp)。因为这个额外的代码生成器,Qt有时对语言会有很严格的要求。 这里我就让这篇Qt文档来解释这个严格的要求。代码生成器没有什么错误,MOC起到了很大的作用。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 inqobjectdefs.h你能看出这几个关键字并不是标准C++的关键字吗?
signals, slots, Q_OBJECT, emit, SIGNAL, SLOT
. 这些都是Qt对C++的扩展。这几个关键字其实都是很简单的宏定义而已,在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 enablethe new syntax.没错,信号和槽都是很简单的功能:编译器会将他们与其他任何宏一样处理。不过这些宏还有一个特殊的作用:MOC会发现他们。Signals在Qt4之前都是protected类型的,他们在Qt5中变为了public,这样就可以使用一些新的语法了。
#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, int, void **); \
    QT_TR_FUNCTIONS /* translations helper */ \
private: \
    Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);

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).emit是个空的宏定义,而且MOC也不会对它进行解析。也就是,emit完全是可有可无的,他没有任何意义(除了对开发者有提示作用)。
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
(译注:对于#define的一些高级用法,参见我整理的一片文章:/article/2700755.html)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.上面这些宏,会利用预编译器将一些参数转化成字符串,并且在前面添加上编码。在调试模式中,如果singnal的连接出现问题,我们提示警告信息的时候还会注明对应的文件位置。这是在Qt4.5之后以兼容方式添加进来的功能。为了知道代码对应的行信息,我们可以用
qFlagLocation
,它会将对应代码的地址信息注册到一个有两个入口的表里。MOC Generated Code - MOC生成的代码We will now go over portion of the code generated by
moc
in Qt5.我们现在就来看看Qt5的moc生成的部分代码。The QMetaObject
const QMetaObject Counter::staticMetaObject = {
    { &QObject::staticMetaObject, qt_meta_stringdata_Counter.data,
      qt_meta_data_Counter,  qt_static_metacall, 0, 0}
};

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.我们在这里可以看到
Counter::metaObject()和
Counter::staticMetaObject 的实现。他们都是在Q_OBJECT宏中被声明的。QObject::d_ptr->metaObject 只被用于动态元对象(QML对象),随意通常虚函数metaObject()
只是返回类的staticMetaObject 。The
staticMetaObject
is constructed in the read-only data. QMetaObject as defined inqobjectdefs.h
looks like this:staticMetaObject被创建为只读数据。qobjectdefs.h
文件中QMetaObject的定义如下:
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
superdata
.
stringdata

and
data
are initialized with some data explained later in this article.
static_metacall
is a function pointer initialized
to
Counter::qt_static_metacall
.代码中用的d是为了表明那些数据都本应为私有的。然而他们并没有成为私有的是为了保持它为POD和允许静态初始化。(译注:在C++中,我们把传统的C风格的struct叫做POD(Plain Old Data),字面意思古老的普通的结构体)。这里会用父类对象的元对象(此处就是指QObject::staticMetaObject
)来初始化QMetaObject的superdata,而它的stringdata 和
data
这两个成员则会用之后要讲到的数据初始化。static_metacall是一个被初始化为 Counter::qt_static_metacall的函数指针。Introspection Tables - 实时考察功能用到的数据表First, let us analyze the integer data of QMetaObject.首先,我们来分析 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, 0x05,

 // slots: name, argc, parameters, tag, flags
       4,    1,   27,    2, 0x0a,

 // 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.开头的13个整型数组成了结构体的头信息。对于有两列的那些数据,第一列表示某一类项目的个数,第二列表示这一类项目的描述信息开始于这个数组中的哪个位置(索引值)。这里,我们的Counter类有两个方法,并且关于方法的描述信息开始于第14个int数据。每个方法的描述信息由5个int型数据组成。第一个整型数代表方法名,它的值是该方法名(译注:方法名就是个字符串)在字符串表中的索引位置(之后会介绍字符串表)。第二个整数表示该方法所需参数的个数,后面紧跟的第三个数就是关于参数的描述(译注:它表示与参数相关的描述信息开始于本数组中的哪个位置,也是个索引)。我们现在先忽略掉tag和flags。对于每个函数,Moc还会保存它的返回类型、每个参数的类型、以及参数的名称。String Table - 字符串表
struct qt_meta_stringdata_Counter_t {
    QByteArrayData data[6];
    char stringdata[47];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    offsetof(qt_meta_stringdata_Counter_t, stringdata) + ofs \
        - idx * sizeof(QByteArrayData) \
    )
static const qt_meta_stringdata_Counter_t qt_meta_stringdata_Counter = {
    {
QT_MOC_LITERAL(0, 0, 7),
QT_MOC_LITERAL(1, 8, 12),
QT_MOC_LITERAL(2, 21, 0),
QT_MOC_LITERAL(3, 22, 8),
QT_MOC_LITERAL(4, 31, 8),
QT_MOC_LITERAL(5, 40, 5)
    },
    "Counter\0valueChanged\0\0newValue\0setValue\0"
    "value\0"
};
#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.这主要就是一个QByteArray的静态数组。QT_MOC_LITERAL 这个宏可以创建一个静态的
QByteArray

,其数据就是参考的在它下面的对应索引处的字符串。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).
MOC还实现了信号signals(译注:根据前面的介绍我们已经知道signals其实就是publib,因此所有被定义的信号其实都必须有具体的函数定义,这样才能C++编译器编译,而我们在开发中并不曾写过信号的定义,这是因为这些都由MOC来完成)。所有的信号都是很简单的函数而已,他们只是为参数创建一个指针数组并传递给QMetaObject::activate函数。指针数组的第一个元素是属于返回值的。在我们的例子中将它设置为了0,这是因为我们的返回类型是void。
传递给activate 函数的第3个参数是信号的索引(在这里,该索引为0)。
// SIGNAL 0
void Counter::valueChanged(int _t1)
{
    void *_a[] = { 0, 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:
通过一个槽的索引来调用一个槽函数也是行得通的,这要借助qt_static_metacall这个函数:
void Counter::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        Counter *_t = static_cast<Counter *>(_o);
        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.
函数中的指针数组与在上面介绍signal时的那个函数中的指针数组格式相同。只不过这里没有用到_a[0],因为这里所有的函数都是返回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 theabsolute 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 internalsignal 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.在每个 QMetaObject中,对象的槽、信号和其他一些被涉及到的方法都被分配了一个索引,这些索引值从0开始。他们是按照“先信号、再槽、最后其他方法”的顺序排列。这样得到的索引被称为相对索引(relative index),相对索引与该类的父类(就是基类)和祖宗类中的方法数量无关。
但是通常,我们不是想知道相对索引,而是想知道在包含了从父类和祖宗类中继承来的所有方法后的绝对索引。为了得到这个索引,我们只需要在相关索引(relative index)上加上一个偏移量就可以得到绝对索引absolute index了。这个绝对索引就是在Qt的API中使用的, 像
QMetaObject::indexOf{Signal,Slot,Method}这样的函数返回的就是绝对索引。

另外,在信号槽的连接机制中还要用到一个关于信号的向量索引。这样的索引表中如果把槽也包含进来的话槽会造成向量的浪费,而一般槽的数量又要比信号多。所以从Qt4.6开始,Qt内部又多出了一个专门的信号索引signal index ,它是一个只包含了信号的索引表。

在用Qt开发的时候,我们只需要关心绝对索引就行。不过在浏览Qt源码的时候,要留意这三种索引的不同。

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 :在进行信号和槽的连接时Qt做的一件事就是找出要连接的信号和槽的索引。Qt会在meta object的字符串表中查找对应的索引。然后会创建一个
QObjectPrivate::Connection对象并添加到内部的链表中。
一个
connection 中需要存储哪些数据呢?我们需要一种能根据信号索引signal index快速访问到对应的connection的方法。因为可能会同时有不止一个槽连接到同一个信号上,所以每一个信号都要有一个槽列表;每个connection必须包含接收对象的指针以及被连接的槽的索引;我们还想让一个connection能在与之对应的接收者被销毁时自动取消连接,所以每个接收者对象也需要知道谁与他建立了连接这样才能在析构时将connection清理掉。下面是定义在
qobject_p.h 中的QObjectPrivate::Connection :(译注:只认识QObject不认识QObjectPrivate?看这里:/article/2700781.html)
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.每一个对象有一个connection vector:每一个信号有一个 QObjectPrivate::Connection的链表,这个vector就是与这些链表相关联的。每一个对象还有一个反向链表,它包含了这个对象被连接到的所有的 connection,这样可以实现连接的自动清除。而且这个反向链表是一个双重链表。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.使用链表是因为它们可以快速地添加和移除对象,它们靠保存在QObjectPrivate::Connection中的next/previous前后节点的指针来实现这些操作。
注意senderList的prev指针是一个“指针的指针”。这是因为我们不是真的要指向前一个节点,而是要指向一个指向前节点的指针。这个“指针的指针”只有在销毁连接时才用到,而且不要用它重复往回迭代。这样设计可以不用对链表的首结点做特殊处理。(译注:对连接的建立如果还有疑惑,请参考:/article/2700784.htmlSignal 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 我们已经知道当调用一个信号的时候,最终调用的是MOC生成的代码中的QMetaObject::activate函数。
这里是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;
//译注:注意下面这一段,从这里可以看出对于跨线程的连接,信号发出
//后槽函数不会立即在当前线程中执行。其执行要等到槽函数所在的线程被
//激活后。有时间了再研究一下queued_activate这个函数。
        // 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);
}






内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: