Qt信號槽機制源碼解析


Qt信號槽機制源碼解析

來源 https://blog.51cto.com/9291927/2070398

 

一、信號槽機制的原理

1、信號槽簡介

信號槽是觀察者模式的一種實現,特性如下:
A、一個信號就是一個能夠被觀察的事件,或者至少是事件已經發生的一種通知;
B、一個槽就是一個觀察者,通常就是在被觀察的對象發生改變的時候——也可以說是信號發出的時候——被調用的函數;
C、信號與槽的連接,形成一種觀察者-被觀察者的關系;
D、當事件或者狀態發生改變的時候,信號就會被發出;同時,信號發出者有義務調用所有注冊的對這個事件(信號)感興趣的函數(槽)。
信號和槽是多對多的關系。一個信號可以連接多個槽,而一個槽也可以監聽多個信號。
信號槽與語言無關,有多種方法可以實現信號槽,不同的實現機制會導致信號槽的差別很大。信號槽術語最初來自 Trolltech 公司的 Qt 庫,由於其設計理念的先進性,立刻引起計算機科學界的注意,提出了多種不同的實現。目前,信號槽依然是 Qt 庫的核心之一,其他許多庫也提供了類似的實現,甚至出現了一些專門提供這一機制的工具庫。
  信號槽是Qt對象以及其派生類對象之間的一種高效通信接口,是Qt的核心特性,也是Qt區別與其他工具包的重要地方。信號槽完全獨立於標准的C/C++語言,因此要正確的處理好信號和槽,必須借助於一個成為MOC(Meta Object Compiler)的Qt工具,MOC工具是一個C++預處理程序,能為高層次的事件處理自動生成所需要的附加代碼。

2、不同平台的實現

MFC中的消息機制沒有采用C++中的虛函數機制,原因是消息太多,虛函數開銷太大。在Qt中也沒有采用C++中的虛函數機制,而是采用了信號槽機制,原因與此相同。更深層次的原因上,多態的底層實現機制只有兩種,一種是按照名稱查表,一種是按照位置查表。兩種方式各有利弊,而C++的虛函數機制無條件的采用了后者,導致的問題就是在子類很少重載基類實現的時候開銷太大,再加上界面編程中子類眾多的情況,基本上C++的虛函數機制效率太低,於是各家庫的編寫者就只好自謀生路,當然,這其實是C++語言本身的缺陷。

二、Qt信號槽實例解析

1、信號槽使用示例

使用簡單的實例:

#ifndef OBJECT_H #define OBJECT_H #include <QObject> #include <QString> #include <QDebug> class Object : public QObject { Q_OBJECT Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged) Q_PROPERTY(int score READ score WRITE setScore NOTIFY scoreChanged) Q_PROPERTY(Level level READ level WRITE setLevel) Q_CLASSINFO("Author", "Scorpio") Q_CLASSINFO("Version", "1.0") public: enum Level { Basic = 1, Middle, Advanced, Master }; Q_ENUMS(Level) protected: QString m_name; Level m_level; int m_age; int m_score; void setLevel(const int& score) { if(score <= 60) { m_level = Basic; } else if(score < 100) { m_level = Middle; } else if(score < 150) { m_level = Advanced; } else { m_level = Master; } } public: explicit Object(QString name, QObject *parent = 0):QObject(parent) { m_name = name; setObjectName(m_name); connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int))); connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int))); } int age()const { return m_age; } void setAge(const int& age) { m_age = age; emit ageChanged(m_age); } int score()const { return m_score; } void setScore(const int& score) { m_score = score; setLevel(m_score); emit scoreChanged(m_score); } Level level()const { return m_level; } void setLevel(const Level& level) { m_level = level; } signals: void ageChanged(int age); void scoreChanged(int score); public slots: void onAgeChanged(int age) { qDebug() << "age changed:" << age; } void onScoreChanged(int score) { qDebug() << "score changed:" << score; } }; #endif // OBJECT_H

Main函數:

#include <QCoreApplication> #include "Object.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Object ob("object"); //設置屬性age ob.setProperty("age", QVariant(30)); qDebug() << "age: " << ob.age(); qDebug() << "property age: " << ob.property("age").toInt(); //設置屬性score ob.setProperty("score", QVariant(90)); qDebug() << "score: " << ob.score(); qDebug() << "property score: " << ob.property("score").toInt(); qDebug() << "Level: " << ob.level(); ob.setProperty("level", 4); qDebug() << "level: " << ob.level(); qDebug() << "Property level: " << ob.property("level").toInt(); //內省intropection,運行時查詢對象信息 qDebug() << "object name: " << ob.objectName(); qDebug() << "class name: " << ob.metaObject()->className(); qDebug() << "isWidgetType: " << ob.isWidgetType(); qDebug() << "inherit: " << ob.inherits("QObject"); return a.exec(); }

2、SIGNAL與SLOT宏

SIGNAL與SLOT宏定義在/src/corelib/kernel/Qobjectdefs.h文件中。

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

SIGNAL與SLOT宏會利用預編譯器將一些參數轉化成字符串,並且在前面添加上編碼。
在調試模式中,如果signal的連接出現問題,提示警告信息的時候還會注明對應的文件位置。qFlagLocation 用於定位代碼對應的行信息,會將對應代碼的地址信息注冊到一個有兩個入口的表里。
Object.h文件中有關SIGNAL與SLOT宏部分代碼如下:

 connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int))); connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));

通過對Object.h文件進行預編譯,得到Object.i文件。
使用G++進行預編譯:
g++ -E Object.h -o Object.i -I/usr/local/Trolltech/Qt-4.8.6/include/QtCore -I/usr/local/Trolltech/Qt-4.8.6/include -I.
Object.i文件中結果如下:

connect(this, qFlagLocation("2""ageChanged(int)" "\0" "Object.h" ":" "54"), this, qFlagLocation("1""onAgeChanged(int)" "\0" "Object.h" ":" "54")); connect(this, qFlagLocation("2""scoreChanged(int)" "\0" "Object.h" ":" "55"), this, qFlagLocation("1""onScoreChanged(int)" "\0" "Object.h" ":" "55"));

3、類的元對象

程序編譯時make調用MOC對工程源碼進行解析,生成相應類的moc_xxx.cpp文件,

const QMetaObject Object::staticMetaObject = { { &QObject::staticMetaObject, qt_meta_stringdata_Object, qt_meta_data_Object, &staticMetaObjectExtraData } };

靜態成員staticMetaObject被填充的值如下:
      const QMetaObject superdata;//元數據代表的類的基類的元數據,被填充為基類的元數據指針&QWidget::staticMetaObject
    const char 
stringdata;//元數據的簽名標記,被填充為qt_meta_stringdata_Widget.data
    const uint *data;//元數據的索引數組的指針,被填充為qt_meta_data_Widget
        const QMetaObject **extradata;//擴展元數據表的指針,內部被填充為函數指針qt_static_metacall。
staticMetaObjectExtraData初始化如下:

const QMetaObjectExtraData Object::staticMetaObjectExtraData = { 0, qt_static_metacall };

QMetaObjectExtraData類型的內部成員static_metacall是一個指向Object::qt_static_metacall 的函數指針。
Object的內存布局如下:

Object內存布局已經包含了靜態成員staticMetaObject和
staticMetaObjectExtraData成員。

const QMetaObject *Object::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; }

QObject::d_ptr->metaObject僅供動態元對象(QML對象)使用,所以一般而言,虛函數 metaObject() 僅返回類的 staticMetaObject。

4、元數據表

Qt程序編譯時make會調用MOC工具對源文件進行分析,如果某個類包含了Q_OBJECT宏,MOC會生成對應的moc_xxx.cpp文件。
moc_Object.cpp文件內容中:
Object的元數據如下:

static const uint qt_meta_data_Object[] = { // content:內容信息 6, // revision MOC生成代碼的版本號 0, // classname 類名,在qt_meta_stringdata_Object數組中索引為0 2, 14, // classinfo 類信息,有2個cassinfo定義, 4, 18, // methods 類有4個自定義方法,即信號與槽個數, 3, 38, // properties 屬性的位置信息,有3個自定義屬性, 1, 50, // enums/sets 枚舉的位置信息,有一個自定義枚舉,在qt_meta_stringdata_Object數組中索引為50 0, 0, // constructors 構造函數的位置信息 0, // flags 2, // signalCount // classinfo: key, value //類信息的存儲在qt_meta_stringdata_Object數組中, 15, 7, //第一個類信息,key的數組索引為15,即Author,value的數組索引為7,即Scorpio 26, 22, //第二個類信息,key的數組索引為26,即Version,value的數組索引為22,即1.0 // signals: signature, parameters, type, tag, flags 39, 35, 34, 34, 0x05, //第一個自定義信號的簽名存儲在qt_meta_stringdata_Object數組中, //索引是39,即ageChanged(int) 61, 55, 34, 34, 0x05, //第二個自定義信號的簽名存儲在qt_meta_stringdata_Object數組中, //索引是61,即scoreChanged(int) // slots: signature, parameters, type, tag, flags 79, 35, 34, 34, 0x0a, //第一個自定義槽函數的簽名存儲在qt_meta_stringdata_Object數組中, //索引是79,即onAgeChanged(int) 97, 55, 34, 34, 0x0a, //第二個自定義槽函數的簽名存儲在qt_meta_stringdata_Object數組中, //索引是79,即onScoreChanged(int) // properties: name, type, flags 35, 117, 0x02495103, // 第一個自定義屬性的簽名存儲在qt_meta_stringdata_Object中,索引是35,即age 55, 117, 0x02495103, // 第二個自定義屬性的簽名存儲在qt_meta_stringdata_Object中,索引是55,即score 127, 121, 0x0009510b, // 第三個自定義屬性的簽名存儲在qt_meta_stringdata_Object中,索引是127,即level // properties: notify_signal_id //屬性關聯的信號編號 0, 1, 0, // enums: name, flags, count, data 121, 0x0, 4, 54, //枚舉的定義,存儲在qt_meta_stringdata_Object中,索引是121,即Level,內含4個枚舉常量 // enum data: key, value //枚舉數據的鍵值對 133, uint(Object::Basic), //數組索引是133,即Basic 139, uint(Object::Middle), //數組索引是139,即Middle 146, uint(Object::Advanced), //數組索引是146,即Advanced 155, uint(Object::Master), //數組索引是155,即Master 0 // eod 元數據結束標記 };

內省表是一個 uint 數組,分為五個部分:第一部分content,即內容,分為9行。第一行revision,指MOC生成代碼的版本號(Qt4 是6,Qt5則是7)。第二個classname,即類名,該值是一個索引,指向字符串表的某一個位置(本例中就是第0位)。

static const char qt_meta_stringdata_Object[] = { "Object\0Scorpio\0Author\0""1.0\0Version\0\0" "age\0ageChanged(int)\0score\0scoreChanged(int)\0" "onAgeChanged(int)\0onScoreChanged(int)\0" "int\0Level\0level\0Basic\0Middle\0Advanced\0" "Master\0" };

5、信號的實現

MOC在生成的moc_xxx.cpp文件中實現了信號,創建了一個指向參數的指針的數組,並將指針數組傳給QMetaObject::activate函數。數組的第一個元素是返回值。本例中值是0,因為返回值是void。傳給activate函數的第三個參數是信號的索引(本例中是0)。

// SIGNAL 0,ageChanged信號的實現 void Object::ageChanged(int _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 0, _a); } // SIGNAL 1 scoreChanged信號的實現 void Object::scoreChanged(int _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 1, _a); }

6、槽函數的調用

利用槽函數在qt_static_metacall 函數的索引位置來調用槽函數:

void Object::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { Q_ASSERT(staticMetaObject.cast(_o)); Object *_t = static_cast<Object *>(_o); switch (_id) { case 0: _t->ageChanged((*reinterpret_cast< int(*)>(_a[1]))); break; case 1: _t->scoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break; case 2: _t->onAgeChanged((*reinterpret_cast< int(*)>(_a[1]))); break; case 3: _t->onScoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break; default: ; } } }

7、元對象中的索引

在每一個QMetaObject對象中,槽、信號以及其它的對象可調用函數都會分配一個從0開始的索引。索引是有順序的,信號在第一位,槽在第二位,最后是其它函數。這個索引在內部被稱為相對索引,不包含父對象的索引位。
為了實現包含在繼承鏈中其它函數的索引,在相對索引的基礎上添加一個偏移量,得到絕對索引。絕對索引是在公開API中使用的索引,由QMetaObject::indexOf(Signal, Slot, Method) 類似的函數返回。
連接機制使用以信號為索引的向量。但是在向量中,所有的槽也會占有一定空間,通常在一個對象中,槽的數量要比信號多。所以從 Qt 4.6開始,使用的是一種僅包含信號索引的新的內部實現。

8、信號與槽的連接

開始連接時,Qt所要做的第一件事是找出所需要的信號和槽的索引。Qt會去查找元對象的字符串表來找出相應的索引。
然后,創建一個 QObjectPrivate::Connection 對象,將其添加到內部的鏈表中。
由於允許多個槽連接到同一個信號,需要為每一個信號添加一個已連接的槽的列表。每一個連接都必須包含接收對象和槽的索引。在接收對象銷毀的時候,相應的連接也能夠被自動銷毀。所以每一個接收對象都需要知道誰連接到它自己,以便能夠清理連接。
QObject對象的私有數據QObjectPrivate如下:

class Q_CORE_EXPORT QObjectPrivate : public QObjectData { Q_DECLARE_PUBLIC(QObject) public: struct ExtraData { ExtraData() {} QList<QByteArray> propertyNames; QList<QVariant> propertyValues; }; typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **); struct Connection { QObject *sender; QObject *receiver; StaticMetaCallFunction callFunction; // The next pointer for the singly-linked ConnectionList Connection *nextConnectionList; //senders linked list Connection *next; Connection **prev; QBasicAtomicPointer<int> argumentTypes; ushort method_offset; ushort method_relative; ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking ~Connection(); int method() const { return method_offset + method_relative; } }; // ConnectionList is a singly-linked list struct ConnectionList { ConnectionList() : first(0), last(0) {} Connection *first; Connection *last; }; struct Sender { QObject *sender; int signal; int ref; }; QObjectPrivate(int version = QObjectPrivateVersion); virtual ~QObjectPrivate(); void deleteChildren(); void setParent_helper(QObject *); void moveToThread_helper(); void setThreadData_helper(QThreadData *currentData, QThreadData *targetData); void _q_reregisterTimers(void *pointer); bool isSender(const QObject *receiver, const char *signal) const; QObjectList receiverList(const char *signal) const; QObjectList senderList() const; void addConnection(int signal, Connection *c); void cleanConnectionLists(); static inline Sender *setCurrentSender(QObject *receiver, Sender *sender); static inline void resetCurrentSender(QObject *receiver, Sender *currentSender, Sender *previousSender); static void clearGuards(QObject *); static QObjectPrivate *get(QObject *o) { return o->d_func(); } int signalIndex(const char *signalName) const; inline bool isSignalConnected(uint signalIdx) const; // To allow arbitrary objects to call connectNotify()/disconnectNotify() without making // the API public in QObject. This is used by QDeclarativeNotifierEndpoint. inline void connectNotify(const char *signal); inline void disconnectNotify(const char *signal); static inline void signalSignature(const QMetaMethod &signal, QVarLengthArray<char> *result); public: QString objectName; ExtraData *extraData; // extra data set by the user QThreadData *threadData; // id of the thread that owns the object QObjectConnectionListVector *connectionLists;//連接鏈表向量容器 Connection *senders; // linked list of connections connected to this object Sender *currentSender; // object currently activating the object mutable quint32 connectedSignals[2]; // preserve binary compatibility with code compiled without Qt 3 support // keeping the binary layout stable helps the Qt Creator debugger void *unused; QList<QPointer<QObject> > eventFilters; union { QObject *currentChildBeingDeleted; QAbstractDeclarativeData *declarativeData; //extra data used by the declarative module }; // these objects are all used to indicate that a QObject was deleted // plus QPointer, which keeps a separate list QAtomicPointer<QtSharedPointer::ExternalRefCountData> sharedRefcount; };

每一個QObject對象都有一個連接鏈表容器QObjectConnectionListVector *connectionLists:將每一個信號與一個 QObjectPrivate::Connection 的鏈表關聯起來。
QObject::connect函數的實現如下:

bool QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type) { { const void *cbdata[] = { sender, signal, receiver, method, &type }; if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata)) return true; } if (type == Qt::AutoCompatConnection) { type = Qt::AutoConnection; } if (sender == 0 || receiver == 0 || signal == 0 || method == 0) { qWarning("QObject::connect: Cannot connect %s::%s to %s::%s", sender ? sender->metaObject()->className() : "(null)", (signal && *signal) ? signal+1 : "(null)", receiver ? receiver->metaObject()->className() : "(null)", (method && *method) ? method+1 : "(null)"); return false; } QByteArray tmp_signal_name; if (!check_signal_macro(sender, signal, "connect", "bind")) return false; const QMetaObject *smeta = sender->metaObject(); const char *signal_arg = signal; ++signal; //skip code //在發送者對象的元對象中將信號的相對索引找到 int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, false); if (signal_index < 0) { // check for normalized signatures tmp_signal_name = QMetaObject::normalizedSignature(signal - 1); signal = tmp_signal_name.constData() + 1; smeta = sender->metaObject(); signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, false); } if (signal_index < 0) { // re-use tmp_signal_name and signal from above smeta = sender->metaObject(); signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, true); } if (signal_index < 0) { err_method_notfound(sender, signal_arg, "connect"); err_info_about_objects("connect", sender, receiver); return false; } signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index); int signalOffset, methodOffset; computeOffsets(smeta, &signalOffset, &methodOffset); int signal_absolute_index = signal_index + methodOffset; signal_index += signalOffset; QByteArray tmp_method_name; int membcode = extract_code(method); if (!check_method_code(membcode, receiver, method, "connect")) return false; const char *method_arg = method; ++method; // skip code const QMetaObject *rmeta = receiver->metaObject(); //在接受者對象的元對象中將槽函數的相對索引找到 int method_index_relative = -1; switch (membcode) { case QSLOT_CODE: method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, false); break; case QSIGNAL_CODE: method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, false); break; } if (method_index_relative < 0) { // check for normalized methods tmp_method_name = QMetaObject::normalizedSignature(method); method = tmp_method_name.constData(); // rmeta may have been modified above rmeta = receiver->metaObject(); switch (membcode) { case QSLOT_CODE: method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, false); if (method_index_relative < 0) method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, true); break; case QSIGNAL_CODE: method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, false); if (method_index_relative < 0) method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, true); break; } } if (method_index_relative < 0) { err_method_notfound(receiver, method_arg, "connect"); err_info_about_objects("connect", sender, receiver); return false; } //檢查連接參數是否匹配 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; } int *types = 0; if ((type == Qt::QueuedConnection) && !(types = queuedConnectionTypes(smeta->method(signal_absolute_index).parameterTypes()))) return false; //調用QMetaObjectPrivate::connect將信號與槽進行連接 if (!QMetaObjectPrivate::connect(sender, signal_index, receiver, method_index_relative, rmeta ,type, types)) return false; const_cast<QObject*>(sender)->connectNotify(signal - 1); return true; }

QObject::connect函數的主要功能是在接受者對象的元對象中將槽函數的相對索引找到,在接受者對象的元對象中將槽函數的相對索引找到,最后調用QMetaObjectPrivate::connect將信號與槽進行連接。QObject及其派生類對象的元對象在創建時就有一個QObjectConnectionListVector連接鏈表容器,QObject::connect的作用就是將新的連接加入到信號發送者附屬的元對象的連接鏈表容器的相應信號的連接鏈表中(一個信號可能連接多個槽函數)。

每個QObject及其派生類對象都有一個QObjectConnectionListVector *connectionLists連接鏈表容器,將信號的索引作為容器的索引,將每一個信號與一個 QObjectPrivate::ConnectionList鏈表關聯起來。同時,QObjectPrivate::ConnectionList鏈表中連接的某個槽函數可能是接收者對象的槽函數鏈表中的一個。每個接收者對象的鏈表如下:

senderList 的 prev 指針是一個指針的指針。這是因為並不是真的指向上一個節點,而是指向上一個節點中的 next 指針。這個指針僅在連接銷毀時使用,並且不能向后遍歷。它允許不為第一個元素添加特殊處理。
容器中存儲的ConnectionList如下:

struct ConnectionList { ConnectionList() : first(0), last(0) {} Connection *first;//第一個結點 Connection *last;//最后一個結點 };

每個ConnectionList類型元素是一個雙向鏈表,保存了信號的所有連接。連接的類型Connection結構如下:

struct Connection { QObject *sender;//發送者 QObject *receiver;//接受者 StaticMetaCallFunction callFunction;//調用的槽函數 // The next pointer for the singly-linked ConnectionList Connection *nextConnectionList; //senders linked list Connection *next; Connection **prev; QBasicAtomicPointer<int> argumentTypes; ushort method_offset; ushort method_relative; ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking ~Connection(); int method() const { return method_offset + method_relative; } };

QMetaObjectPrivate::connect函數源碼如下:
//將一個新的連接加入到信號發送者的連接鏈表容器中相應信號的連接鏈表中,其中連接加入的連接鏈表的索引為信號的索引

bool QMetaObjectPrivate::connect(const QObject *sender, int signal_index, const QObject *receiver, int method_index, const QMetaObject *rmeta, int type, int *types) { QObject *s = const_cast<QObject *>(sender); QObject *r = const_cast<QObject *>(receiver); int method_offset = rmeta ? rmeta->methodOffset() : 0; //在元對象的元數據字符串中找到回調的函數指針qt_static_metacall QObjectPrivate::StaticMetaCallFunction callFunction = (rmeta && QMetaObjectPrivate::get(rmeta)->revision >= 6 && rmeta->d.extradata) ? reinterpret_cast<const QMetaObjectExtraData *>(rmeta->d.extradata)->static_metacall : 0; QOrderedMutexLocker locker(signalSlotLock(sender), signalSlotLock(receiver)); //如果連接類型為Qt::UniqueConnection if (type & Qt::UniqueConnection) { QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists; if (connectionLists && connectionLists->count() > signal_index) { //根據信號索引獲取信號的連接 const QObjectPrivate::Connection *c2 = (*connectionLists)[signal_index].first; int method_index_absolute = method_index + method_offset; while (c2) { //如果信號的接收者相同並且槽函數相同,即相同的連接已經存在 if (c2->receiver == receiver && c2->method() == method_index_absolute) return false;//直接返回, c2 = c2->nextConnectionList;//下一個信號連接 } } type &= Qt::UniqueConnection - 1; } //創建一個新的連接 QObjectPrivate::Connection *c = new QObjectPrivate::Connection; //設置連接的屬性 c->sender = s; c->receiver = r; c->method_relative = method_index; c->method_offset = method_offset; c->connectionType = type; c->argumentTypes = types; c->nextConnectionList = 0; c->callFunction = callFunction;//設置回調的函數指針為qt_static_metacall QT_TRY { //將連接添加到發送者的連接鏈表容器中相應的信號對應的連接鏈表中 QObjectPrivate::get(s)->addConnection(signal_index, c); } QT_CATCH(...) { delete c; QT_RETHROW; } c->prev = &(QObjectPrivate::get(r)->senders); c->next = *c->prev; *c->prev = c; if (c->next) c->next->prev = &c->next; QObjectPrivate *const sender_d = QObjectPrivate::get(s); if (signal_index < 0) { sender_d->connectedSignals[0] = sender_d->connectedSignals[1] = ~0; } else if (signal_index < (int)sizeof(sender_d->connectedSignals) * 8) { sender_d->connectedSignals[signal_index >> 5] |= (1 << (signal_index & 0x1f)); } return true; }

9、信號的發射

使用emit發射信號時,實際調用MOC實現的信號函數,信號函數內部調用了QMetaObject::activate()函數。

// SIGNAL 0,ageChanged信號的實現 void Object::ageChanged(int _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 0, _a); } // SIGNAL 1 scoreChanged信號的實現 void Object::scoreChanged(int _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 1, _a); } void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,void **argv) { int signalOffset; int methodOffset; computeOffsets(m, &signalOffset, &methodOffset); int signal_index = signalOffset + local_signal_index; if (!sender->d_func()->isSignalConnected(signal_index)) return; // 如果發送的信號沒有槽連接,直接返回 if (sender->d_func()->blockSig) return;//如果阻塞,直接返回 int signal_absolute_index = methodOffset + local_signal_index; void *empty_argv[] = { 0 }; if (qt_signal_spy_callback_set.signal_begin_callback != 0) { qt_signal_spy_callback_set.signal_begin_callback(sender, signal_absolute_index, argv ? argv : empty_argv); } Qt::HANDLE currentThreadId = QThread::currentThreadId(); QMutexLocker locker(signalSlotLock(sender)); //獲取發送者的連接鏈表容器 QObjectConnectionListVector *connectionLists = sender->d_func()->connectionLists; if (!connectionLists) { locker.unlock(); if (qt_signal_spy_callback_set.signal_end_callback != 0) qt_signal_spy_callback_set.signal_end_callback(sender, signal_absolute_index); return; } ++connectionLists->inUse; //從發送者的連接鏈表容器中使用信號索引作為索引,獲取相應的連接鏈表 const QObjectPrivate::ConnectionList *list; if (signal_index < connectionLists->count()) list = &connectionLists->at(signal_index); else list = &connectionLists->allsignals; do { //索取發送的信號的連接鏈表的第一個連接 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; do { if (!c->receiver) continue;//如果連接的接收者為空,繼續 QObject * const receiver = c->receiver; const bool receiverInSameThread = 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)) { queued_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv); continue; #ifndef QT_NO_THREAD } //阻塞隊列連接類型 else if (c->connectionType == Qt::BlockingQueuedConnection) { locker.unlock(); if (receiverInSameThread) { qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: " "Sender is %s(%p), receiver is %s(%p)", sender->metaObject()->className(), sender, receiver->metaObject()->className(), receiver); } QSemaphore semaphore; QCoreApplication::postEvent(receiver, new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal_absolute_index, 0, 0, argv ? argv : empty_argv, &semaphore)); semaphore.acquire(); locker.relock(); continue; #endif } QObjectPrivate::Sender currentSender; QObjectPrivate::Sender *previousSender = 0; if (receiverInSameThread) { currentSender.sender = sender; currentSender.signal = signal_absolute_index; currentSender.ref = 1; previousSender = QObjectPrivate::setCurrentSender(receiver, ¤tSender); } //獲取連接的回調函數指針 const QObjectPrivate::StaticMetaCallFunction callFunction = c->callFunction; const int method_relative = c->method_relative; //如果連接的方法的偏移小於接收者的元對象的方法的偏移 if (callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) { //we compare the vtable to make sure we are not in the destructor of the object. locker.unlock(); if (qt_signal_spy_callback_set.slot_begin_callback != 0) qt_signal_spy_callback_set.slot_begin_callback(receiver, c->method(), argv ? argv : empty_argv); //根據接收者的方法偏移,接收者等參數調用qt_static_metacall回調函數 callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv); if (qt_signal_spy_callback_set.slot_end_callback != 0) qt_signal_spy_callback_set.slot_end_callback(receiver, c->method()); locker.relock(); } else { const int method = method_relative + c->method_offset; locker.unlock(); if (qt_signal_spy_callback_set.slot_begin_callback != 0) { qt_signal_spy_callback_set.slot_begin_callback(receiver, method, argv ? argv : empty_argv); } //根據接收者、接收者的方法索引等參數調用發送元對象的metacall metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv); if (qt_signal_spy_callback_set.slot_end_callback != 0) qt_signal_spy_callback_set.slot_end_callback(receiver, method); locker.relock(); } if (receiverInSameThread) QObjectPrivate::resetCurrentSender(receiver, ¤tSender, previousSender); if (connectionLists->orphaned) break; } while (c != last && (c = c->nextConnectionList) != 0); if (connectionLists->orphaned) break; } while (list != &connectionLists->allsignals && //start over for all signals; ((list = &connectionLists->allsignals), true)); --connectionLists->inUse; Q_ASSERT(connectionLists->inUse >= 0); if (connectionLists->orphaned) { if (!connectionLists->inUse) delete connectionLists; } else if (connectionLists->dirty) { sender->d_func()->cleanConnectionLists(); } locker.unlock(); if (qt_signal_spy_callback_set.signal_end_callback != 0) qt_signal_spy_callback_set.signal_end_callback(sender, signal_absolute_index); } metacall函數內部調用了qt_metacall函數。 int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv) { if (QMetaObject *mo = object->d_ptr->metaObject) return static_cast<QAbstractDynamicMetaObject*>(mo)->metaCall(cl, idx, argv); else return object->qt_metacall(cl, idx, argv); } int Object::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) { if (_id < 4) qt_static_metacall(this, _c, _id, _a); _id -= 4; } #ifndef QT_NO_PROPERTIES else if (_c == QMetaObject::ReadProperty) { void *_v = _a[0]; switch (_id) { case 0: *reinterpret_cast< int*>(_v) = age(); break; case 1: *reinterpret_cast< int*>(_v) = score(); break; case 2: *reinterpret_cast< Level*>(_v) = level(); break; } _id -= 3; } else if (_c == QMetaObject::WriteProperty) { void *_v = _a[0]; switch (_id) { case 0: setAge(*reinterpret_cast< int*>(_v)); break; case 1: setScore(*reinterpret_cast< int*>(_v)); break; case 2: setLevel(*reinterpret_cast< Level*>(_v)); break; } _id -= 3; } else if (_c == QMetaObject::ResetProperty) { _id -= 3; } else if (_c == QMetaObject::QueryPropertyDesignable) { _id -= 3; } else if (_c == QMetaObject::QueryPropertyScriptable) { _id -= 3; } else if (_c == QMetaObject::QueryPropertyStored) { _id -= 3; } else if (_c == QMetaObject::QueryPropertyEditable) { _id -= 3; } else if (_c == QMetaObject::QueryPropertyUser) { _id -= 3; } #endif // QT_NO_PROPERTIES return _id; }

qt_metacall函數內部調用了qt_static_metacall函數。

10、槽函數的調用

槽函數最終通過qt_static_metacall函數根據參數調用相應的槽函數。

void Object::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { Q_ASSERT(staticMetaObject.cast(_o)); Object *_t = static_cast<Object *>(_o); switch (_id) { case 0: _t->ageChanged((*reinterpret_cast< int(*)>(_a[1]))); break; case 1: _t->scoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break; case 2: _t->onAgeChanged((*reinterpret_cast< int(*)>(_a[1]))); break; case 3: _t->onScoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break; default: ; } } }

11、函數調用流程分析

在onAgeChanged(int age)槽函數內部斷點調試。
Qt高級——Qt信號槽機制源碼解析
得到的函數調用棧如下:
函數調用棧分析:
Qt高級——Qt信號槽機制源碼解析
Object::qt_metacall函數內部調用了Object::setAge函數,setAge內部調用Object::ageChanged信號函數,ageChanged信號函數內部調用了QMetaObject::activate函數,activate函數內部調用Object::qt_static_metacall函數,最終qt_static_metacall函數內部調用了槽函數onAgeChanged。
因此在本例中,當調用ob.setProperty("age", QVariant(30));設置屬性時,觸發了QMetaProperty::Write函數的調用,進而調用MOC實現的moc_Object.cpp文件中的Object::qt_metacall,qt_metacall內部調用setAge函數,setAge函數內部發射信號ageChanged,即調用Object::ageChanged信號函數,Object::ageChanged函數內部調用了Object對象的元對象的QMetaObject::activate函數,activate函數內部調用了Object::qt_static_metacall函數,最終qt_static_metacall內部實現對槽函數onAgeChanged的調用。
本例中,信號和槽處於同一線程,連接類型為直接連接,因此屬於同步調用,是最簡單的調用類型。QMetaObject::activate函數內部實際上根據Object對象的元對象中的信號連接鏈表容器查找得到信號對應的:qt_static_metacall回調函數,進而回調的。

三、信號槽的標准C++實現

1、Qt元對象的模擬實現

Object類的實現:

#ifndef OBJECT_H #define OBJECT_H #include<map> #include <iostream> #include <cstring> using namespace std; //宏定義 #define SLOT(a) #a #define SIGNAL(a) #a #define cpp_slots #define cpp_signals protected #define cpp_emit class Object; //元對象系統,負責搜集信號與槽的名稱 struct MetaObject { //信號組 const char * signal; //槽組 const char * slot; //激活某個信號,idx為信號索引 static void active(Object * sender, int idx); }; //被連接對象信息 struct Connection { Object * receiver;//信號的接收者 int method;//槽函數索引 }; //保存信號索引與連接對象映射 typedef std::multimap<int, Connection> ConnectionMap; typedef std::multimap<int, Connection>::iterator ConnectionMapIt; //信號和槽的索引查找函數,返回信號或槽的索引 static int find_string(const char * str, const char * substr) { if (strlen(str) < strlen(substr)) return -1; int idx = 0; int len = strlen(substr); bool start = true; const char * pos = str; while (*pos) { if (start && !strncmp(pos, substr, len) && pos[len] == '\n') return idx; start = false; if (*pos == '/n') { idx++; start = true; } pos++; } return -1; } class Object { static MetaObject meta;//靜態元對象聲明 void metacall(int idx);//聲明元方法調用函數 public: Object() { } //建立連接 static void cpp_connect(Object* sender, const char* sig, Object* receiver, const char* slt) { cout << "connecting a signal to slot..." << endl; //從元對象數據表中查看信號和槽是否存在 int sig_idx = find_string(sender->meta.signal, sig); int slt_idx = find_string(receiver->meta.slot, slt); //如果沒有找到信號或者槽 if (sig_idx == -1 || slt_idx == -1) { perror("signal or slot not found!"); } else { //創建一個連接,連接內存儲接收者和槽函數的索引 Connection c = { receiver, slt_idx }; cout << "add a signal index and an Connection of receiver to sender's Connection map..." << endl; //將信號的索引和接收者的信息存儲到信號發射者的map容器中 sender->connections.insert(std::pair<int, Connection>(sig_idx, c)); cout << "connected success." << endl; } } void emitSignal()//公有測試函數,發送一個信號 { cout << "emiting a signal..." << endl; cpp_emit valueChanged(); } cpp_signals: void valueChanged();//信號聲明 public cpp_slots: void onValueChanged()//槽函數 { cout << "Value Changed."<< endl; } friend class MetaObject; private: ConnectionMap connections;//連接鍵值對 }; #endif // OBJECT_H

moc_Object.cpp實現:

#include "Object.h" //信號的名稱 static const char signalNames[] = "valueChanged\n"; //槽的名稱 static const char slotNames[] = "onValueChanged\n"; //靜態元對象的填充 MetaObject Object::meta = { signalNames, slotNames }; //元方法調用函數的實現,根據連接的索引回調槽函數 void Object::metacall(int idx) { switch (idx) { case 0: onValueChanged(); break; default: break; }; } //信號的實現 void Object::valueChanged() { MetaObject::active(this, 0); } //激活信號 void MetaObject::active(Object* sender, int idx) { ConnectionMapIt it; std::pair<ConnectionMapIt, ConnectionMapIt> ret; ret = sender->connections.equal_range(idx); for (it = ret.first; it != ret.second; ++it) { Connection c = (*it).second; c.receiver->metacall(c.method);//根據索引調用元方法 } }

2、信號槽模擬使用

Main.cpp文件:

#include <iostream> #include "Object.h" using namespace std; int main(int argc, char *argv[]) { char p[32] = SLOT(Object); cout << "cur_value: " << p << endl; Object obj1, obj2; //連接信號和槽 Object::cpp_connect(&obj1, SLOT(valueChanged), &obj2, SIGNAL(onValueChanged)); //發射一個信號進行測試 obj1.emitSignal(); getchar(); return 0; }

四、信號槽的開源實現

1、sigslot

sigslot是信號槽的一個非常精煉的C++實現,作者是Sarah Thompson,sigslot實現只有一個頭文件sigslot.h,跨平台且線程安全。在WebRTC中,sigslot .h是其基礎的事件處理框架, 在多個模塊的消息通知,響應處理中被使用。
sigslot庫官網:
http://sigslot.sourceforge.net/

Sigslot使用示例如下:

#include "sigslot.h" #include <string> #include <stdio.h> #include <iostream> #include <windows.h> using namespace sigslot; using namespace std; class CSender { public: sigslot::signal2<string, int> m_pfnsigDanger; void Panic() { static int nVal = 0; char szVal[20] = { 0 }; sprintf_s(szVal,20, "help--%d", nVal); m_pfnsigDanger(szVal, nVal++); } }; class CReceiver :public sigslot::has_slots<> { public: void OnDanger(string strMsg, int nVal) { //printf("%s ==> %d", strMsg.c_str(), nVal); cout << strMsg.c_str() << " ==> " << nVal << endl; } }; int main() { CSender sender; CReceiver recever; cout << "create object ok..." << endl; sender.m_pfnsigDanger.connect(&recever, &CReceiver::OnDanger); cout << "connect succ!" << endl; while (1) { cout << "in while..." << endl; sender.Panic(); Sleep(2000); cout << "end of sleep" << endl; } return 0; }

如果在Qt工程中使用sigslot.h,sigslot.h中的emit函數名會和Qt中的emit宏沖突,修改方法有兩個,一是將sigslot.h的emit改成其他名字,二是在.pro文件中添加DEFINES+=QT_NO_EMIT,禁用Qt的emit宏。

2、Boost.Signals

Boost.Signals實現了signals/slots模式,信號(signals)被發射,而插槽(slots)接收該信號。

#include <iostream> #include "boost/signals.hpp" void firstSlot() { std::cout << "void firstSlot()"; } class secondSlot { public: void operator()() const { std::cout << "void secondSlot::operator()() const "; } }; int main() { boost::signal<void ()> sig; sig.connect(&firstSlot); sig.connect(secondSlot()); std::cout << "Emitting a signal... "; sig(); }

插槽函數的執行順序是隨機的,可以使用分組參數來控制調用順序。
sig.connect(1,&firstSlot);
sig.connect(2,secondSlot());

3、Qt信號槽實現與Boost信號槽實現的區別

 

Boost.Signals Qt Signals 和 Slots
一個信號就是一個對象 信號只能是成員函數
發出信號類似於函數調用 發出信號類似於函數調用,Qt 提供了一個 emit 關鍵字來完成這個操作
信號可以是全局的、局部的或者是成員對象 信號只能是成員函數
任何能夠訪問到信號對象的代碼都可以發出信號 只有信號的擁有者才能發出信號
槽是任何可被調用的函數或者函數對象 槽是經過特別設計的成員函數
可以有返回值,返回值可以在多個槽中使用 沒有返回值
同步的 同步的或者隊列的
非線程安全 線程安全,可以跨線程使用
當且僅當槽是可追蹤的時候,槽被銷毀時,連接自動斷開 槽被銷毀時,連接都會自動斷開(因為所有槽都是可追蹤的)
類型安全(編譯器檢查) 類型安全(運行期檢查)
參數列表必須完全一致 槽可以忽略信號中多余的參數
信號、槽可以是模板 信號、槽不能是模板
C++ 直接實現   通過由 moc 生成的元對象實現(moc 以及元對象系統都是 C++ 直接實現的)
沒有內省機制 可以通過內省發現,可以通過元對象調用,

 

============= End

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM