這一篇 涉及到Qt 元對象概念,從根本上解析Qt 信號槽是如何對應的.如果你的問題還停留在跨線程當中,那我也沒辦法!
QT 源碼之QT元對象系統和信號槽機制是本文要介紹的內容。QT的信號和槽機制是用來在對象間通訊的方法,當一個特定事件發生的時候,signal會被 emit 出來,slot 調用是用來響應相應的 signal 的。簡單點說就是如何在一個類的一個函數中觸發另一個類的另一個函數調用,而且還要把相關的參數傳遞過去.好像這和回調函數也有點關係,但是消息機制可比回調函數有用多了,也複雜多了。
下面的代碼是我寫的一個繼承QLabel的類,是QLabel可以響應鼠標單擊的消息。
- #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的源碼內部,來看一看QT是如何實現singal和slots的。
#include “main.moc” 的意思就是使編譯器找到moc對Q_OBJECT處理後的標準C++文件。編譯的時候我們需要首先在該目錄中使用 qmake -project 生成一個 .pro 文件,該文件含有工程細節,然後使用 qmake 產生 Makefile,最後 nmake 就可以產生可執行文件了。我們看看在nmake之後除了生成目標代碼和可執行文件之外,還有一個main.moc文件,這個文件是moc產生的一箇中間文件。
現在我們要看一下Q_OBJECT宏到底是什麼?他與main.moc有什麼關聯呢?相信我介紹完了Q_OBJECT宏之後,再看main.moc就能明白其所有函數的含義了。我們先到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.首先調用了 Q_OBJECT_CHECK (插入了一個 qt_check_for_QOBJECT_macro 的 template function)
2.然後是全局常量 QMetaObject 對象,因此可以用 QClassname::staticMetaObject 直接訪問,另外提供了兩個接口函數 metaObject() 用於不同的 class 返回自己的 staticMetaObject、qt_metacast() 用於轉換,我們在 moc 產生的文件裏面可以找到這兩個接口的實現:
- 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相關的,我們暫時不用去管它。
- # define QT_TR_FUNCTIONS \
- static inline QString tr(const char *s, const char *c = 0) \
- { return staticMetaObject.tr(s, c); }
- 4.最後是接口函數qt_metacall,他的作用是查表,調用函數
- int ClickedLabel::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
- { //Note:QMetaObject::Call 是ClickedLabel的元對象私有枚舉,表示對目標屬性的操作.
- //qt_metacall is virtual function,child must be rewirte it.(當然,這是moc的工作)
- _id = QLabel::qt_metacall(_c, _id, _a);
- if (_id < 0)
- return _id;
- if (_c == QMetaObject::InvokeMetaMethod) { //表示invoke slot function
- switch (_id) {
- case 0: Clicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
- case 1: OnCLicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
- }
- _id -= 2;
- }
- return _id;
- }
- 我們來仔細看看 QMetaObject,這就是meta-object的數據結構定義
- struct Q_CORE_EXPORT QMetaObject
- {
-
class Connection;
- 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;
- } ;
下面看看我們生成的具體的代碼:
- 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是元數據的簽名
qt_meta_data_ClickedLabel,是元數據的索引數組指針。
qt_meta_data_ClickedLabel中這些莫名其妙的數字是如何變成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構建起來。這中間的轉換是通過
- static inline const QMetaObjectPrivate *priv(const uint* data)
- { return reinterpret_cast<const QMetaObjectPrivate*>(data); }
這個函數來完成的。
下面我們着重看看幾個與 signal/slot 相關的代碼
qobject.cpp 文件中關於 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;
- }
- #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;
- }
上面這段代碼首先調用了 QInternal 這個 namespace 裏面 activateCallbacks 這個函數,然後根據 QMetaObject 信息檢查了 sender、receiver 以及對應 signal/slots 的匹配性,得到元數據類。把 signal/slot 字符串轉換成爲了對應的 index,然後檢查信號和槽的參數是否一致,槽函數的參數可以小於信號函數的參數。
最後得到method的元數據QMetaMethod,然後調用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是爲了防止多線程操作引起問題。
一旦我們發送了信號,就應該調用相關槽中的方法了,這個過程其實就是查找全局的connect列表的過程。真正發出信號是在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的數據,首先判斷是否爲阻塞設置
- 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();
- }
- }
響應信號也是在main.moc中實現的。
- 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;
- }