詳解 QT 源碼之QT元對象系統(tǒng)和信號槽機(jī)制
QT 源碼之QT元對象系統(tǒng)和信號槽機(jī)制是本文要介紹的內(nèi)容。QT的信號和槽機(jī)制是用來在對象間通訊的方法,當(dāng)一個特定事件發(fā)生的時候,signal會被 emit 出來,slot 調(diào)用是用來響應(yīng)相應(yīng)的 signal 的。簡單點說就是如何在一個類的一個函數(shù)中觸發(fā)另一個類的另一個函數(shù)調(diào)用,而且還要把相關(guān)的參數(shù)傳遞過去.好像這和回調(diào)函數(shù)也有點關(guān)系,但是消息機(jī)制可比回調(diào)函數(shù)有用多了,也復(fù)雜多了。
下面的代碼是我寫的一個繼承QLabel的類,是QLabel可以響應(yīng)鼠標(biāo)單擊的消息。
- view plaincopy to clipboardprint?
- #include <QLabel>
- #include <QWidget>
- #include <QMessageBox>
- #include <QApplication>
- class ClickedLabel : public QLabel
- {
- Q_OBJECT
- signals:
- void Clicked(ClickedLabel* clicked);
- public:
- ClickedLabel(const QString &text,QWidget *parent=0): QLabel(text,parent){ };
- ~ClickedLabel() {};
- protected:
- void mouseReleaseEvent( QMouseEvent* ){emit Clicked(this);};
- public slots:
- void OnCLicked( ClickedLabel* ) {QMessageBox::information(topLevelWidget(), "Message from Qt", "Label Clicked!"); };
- };
- #include "main.moc"
- int main(int argc,char* argv[])
- {
- QApplication app(argc,argv);
- ClickedLabel label("<h2>test</h2>");
- QObject::connect( &label, SIGNAL( Clicked(ClickedLabel*) ),&label, SLOT( OnCLicked(ClickedLabel*) ) ) ;
- label.show();
- return app.exec();
- }
- #include <QLabel>
- #include <QWidget>
- #include <QMessageBox>
- #include <QApplication>
- class ClickedLabel : public QLabel
- {
- Q_OBJECT
- signals:
- void Clicked(ClickedLabel* clicked);
- public:
- ClickedLabel(const QString &text,QWidget *parent=0): QLabel(text,parent){ };
- ~ClickedLabel() {};
- protected:
- void mouseReleaseEvent( QMouseEvent* ){emit Clicked(this);};
- public slots:
- void OnCLicked( ClickedLabel* ) {QMessageBox::information(topLevelWidget(), "Message from Qt", "Label Clicked!"); };
- };
- #include "main.moc"
- int main(int argc,char* argv[])
- {
- QApplication app(argc,argv);
- ClickedLabel label("<h2>test</h2>");
- QObject::connect( &label, SIGNAL( Clicked(ClickedLabel*) ),&label, SLOT( OnCLicked(ClickedLabel*) ) ) ;
- label.show();
- return app.exec();
- }
這段代碼很簡單,講述了QT的singal和slot的使用。下面我們就深入QT的源碼內(nèi)部,來看一看QT是如何實現(xiàn)singal和slots的。
#include “main.moc” 的意思就是使編譯器找到moc對Q_OBJECT處理后的標(biāo)準(zhǔn)C++文件。編譯的時候我們需要首先在該目錄中使用 qmake -project 生成一個 .pro 文件,該文件含有工程細(xì)節(jié),然后使用 qmake 產(chǎn)生 Makefile,最后 nmake 就可以產(chǎn)生可執(zhí)行文件了。我們看看在nmake之后除了生成目標(biāo)代碼和可執(zhí)行文件之外,還有一個main.moc文件,這個文件是moc產(chǎn)生的一個中間文件。
現(xiàn)在我們要看一下Q_OBJECT宏到底是什么?他與main.moc有什么關(guān)聯(lián)呢?相信我介紹完了Q_OBJECT宏之后,再看main.moc就能明白其所有函數(shù)的含義了。我們先到objectdefs.h 文件中看一下Q_OBJECT宏的定義:
- #define Q_OBJECT \
- public: \
- Q_OBJECT_CHECK \
- 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:
1首先調(diào)用了 Q_OBJECT_CHECK (插入了一個 qt_check_for_QOBJECT_macro 的 template function)
2 然后是全局常量 QMetaObject 對象,因此可以用 QClassname::staticMetaObject 直接訪問,另外提供了兩個接口函數(shù) metaObject() 用于不同的 class 返回自己的 staticMetaObject、qt_metacast() 用于轉(zhuǎn)換,我們在 moc 產(chǎn)生的文件里面可以找到這兩個接口的實現(xiàn):
- const QMetaObject *ClickedLabel::metaObject() const
- {
- return &staticMetaObject;
- }
- void *ClickedLabel::qt_metacast(const char *_clname)
- {
- if (!_clname) return 0;
- if (!strcmp(_clname, qt_meta_stringdata_ClickedLabel))
- return static_cast<void*>(const_cast< ClickedLabel*>(this));
- return QLabel::qt_metacast(_clname);
- }
- 3 宏QT_TR_FUNCTIONS是和i18n相關(guān)的,我們暫時不用去管它。
- # define QT_TR_FUNCTIONS \
- static inline QString tr(const char *s, const char *c = 0) \
- { return staticMetaObject.tr(s, c); }
- 4 最后是接口函數(shù)qt_metacall,他的作用是查表,調(diào)用函數(shù)
- int ClickedLabel::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
- {
- _id = QLabel::qt_metacall(_c, _id, _a);
- if (_id < 0)
- return _id;
- if (_c == QMetaObject::InvokeMetaMethod) {
- switch (_id) {
- case 0: Clicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
- case 1: OnCLicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
- }
- _id -= 2;
- }
- return _id;
- }
- 我們來仔細(xì)看看 QMetaObject,這就是meta-object的數(shù)據(jù)結(jié)構(gòu)定義
- struct Q_CORE_EXPORT QMetaObject
- {
- const char *className() const;
- const QMetaObject *superClass() const;
- QObject *cast(QObject *obj) const;
- // ...
- struct { // private data
- const QMetaObject *superdata;
- const char *stringdata;
- const uint *data;
- const void *extradata;
- } d;
- } ;
#p#
下面看看我們生成的具體的代碼:
- static const uint qt_meta_data_ClickedLabel[] = {
- // content:
- 1, // revision
- 0, // classname
- 0, 0, // classinfo
- 2, 10, // methods
- 0, 0, // properties
- 0, 0, // enums/sets
- // signals: signature, parameters, type, tag, flags
- 22, 14, 13, 13, 0x05,
- // slots: signature, parameters, type, tag, flags
- 45, 13, 13, 13, 0x0a,
- 0 // eod
- };
- static const char qt_meta_stringdata_ClickedLabel[] = {
- "ClickedLabel\0\0clicked\0Clicked(ClickedLabel*)\0"
- "OnCLicked(ClickedLabel*)\0"
- };
- const QMetaObject ClickedLabel::staticMetaObject = {
- { &QLabel::staticMetaObject, qt_meta_stringdata_ClickedLabel,
- qt_meta_data_ClickedLabel, 0 }
- };
這就是meta-object的初始化代碼,meta-object包含所有繼承QObject類的元對象信息。包括class name, superclass name, properties, signals and slots等等。
ClickedLabel的staticMetaObject初始化用到了QLabel::staticMetaObject,
qt_meta_stringdata_ClickedLabel是元數(shù)據(jù)的簽名
qt_meta_data_ClickedLabel,是元數(shù)據(jù)的索引數(shù)組指針。
qt_meta_data_ClickedLabel中這些莫名其妙的數(shù)字是如何變成QMetaObject的呢?
在qmetaobject.cpp中我們找到了QMetaObjectPrivate的定義:
- struct QMetaObjectPrivate
- {
- int revision;
- int className;
- int classInfoCount, classInfoData;
- int methodCount, methodData;
- int propertyCount, propertyData;
- int enumeratorCount, enumeratorData;
- };
很明顯,利用qt_meta_data_ClickedLabel中存儲的索引和qt_meta_stringdata_ClickedLabel中存儲的值,我們很容易將QMetaObject構(gòu)建起來。這中間的轉(zhuǎn)換是通過
- static inline const QMetaObjectPrivate *priv(const uint* data)
- { return reinterpret_cast<const QMetaObjectPrivate*>(data); }
這個函數(shù)來完成的。
#p#
下面我們著重看看幾個與 signal/slot 相關(guān)的代碼
qobject.cpp 文件中關(guān)于 QObject::connect() 函數(shù)的代碼,
- 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;
- }
- #ifndef QT_NO_DEBUG
- bool warnCompat = true;
- #endif
- if (type == Qt::AutoCompatConnection) {
- type = Qt::AutoConnection;
- #ifndef QT_NO_DEBUG
- warnCompat = false;
- #endif
- }
- //判斷是否是NULL
- 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();
- ++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) {
- err_method_notfound(QSIGNAL_CODE, sender, signal, "connect");
- err_info_about_objects("connect", sender, receiver);
- return false;
- }
- }
- QByteArray tmp_method_name;
- int membcode = method[0] - '0';
- if (!check_method_code(membcode, receiver, method, "connect"))
- return false;
- ++method; // skip code
- 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;
- }
- 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) {
- err_method_notfound(membcode, receiver, method, "connect");
- err_info_about_objects("connect", sender, receiver);
- return false;
- }
- if (!QMetaObject::checkConnectArgs(signal, method)) {
- qWarning("QObject::connect: Incompatible sender/receiver arguments"
- "\n\t%s::%s --> %s::%s",
- sender->metaObject()->className(), signal,
- receiver->metaObject()->className(), method);
- return false;
- }
- int *types = 0;
- if ((type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
- && !(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("QObject::connect: Connecting from COMPAT signal (%s::%s)", smeta->className(), signal);
- } else if(rmethod.attributes() & QMetaMethod::Compatibility && membcode != QSIGNAL_CODE) {
- qWarning("QObject::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;
- }
上面這段代碼首先調(diào)用了 QInternal 這個 namespace 里面 activateCallbacks 這個函數(shù),然后根據(jù) QMetaObject 信息檢查了 sender、receiver 以及對應(yīng) signal/slots 的匹配性,得到元數(shù)據(jù)類。把 signal/slot 字符串轉(zhuǎn)換成為了對應(yīng)的 index,然后檢查信號和槽的參數(shù)是否一致,槽函數(shù)的參數(shù)可以小于信號函數(shù)的參數(shù)。
最后得到method的元數(shù)據(jù)QMetaMethod,然后調(diào)用QMetaObject::connect的方法。
- 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;
- }
QMetaObject::connect代碼中QWriteLocker是為了防止多線程操作引起問題。
一旦我們發(fā)送了信號,就應(yīng)該調(diào)用相關(guān)槽中的方法了,這個過程其實就是查找全局的connect列表的過程。真正發(fā)出信號是在main.moc中。
- void ClickedLabel::Clicked(ClickedLabel * _t1)
- {
- void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
- QMetaObject::activate(this, &staticMetaObject, 0, _a);
- }
- void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv)
- {
- // 這里得到的是QObject的數(shù)據(jù),首先判斷是否為阻塞設(shè)置
- 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());
- // 打上使用標(biāo)記,因為可能是放在隊列中
- 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設(shè)置當(dāng)前發(fā)送者
- 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 {
- // 調(diào)用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();
- }
- }
響應(yīng)信號也是在main.moc中實現(xiàn)的。
- int ClickedLabel::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
- {
- _id = QLabel::qt_metacall(_c, _id, _a);
- if (_id < 0)
- return _id;
- if (_c == QMetaObject::InvokeMetaMethod) {
- switch (_id) {
- case 0: Clicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
- case 1: OnCLicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
- }
- _id -= 2;
- }
- return _id;
- }
小結(jié): QT 源碼之QT元對象系統(tǒng)和信號槽機(jī)制的內(nèi)容介紹完了,希望本文對你有所幫助!