Qt訊號和槽的實現揭秘

來源:互聯網
上載者:User
Qt訊號和槽的實現揭秘對於剛開始學習Qt的同學,對訊號和槽的運行機制是非常難於理解的,這篇Blog 的目的就是解析掀開訊號和槽的神秘面紗。支援訊號與槽機制的類必須派生於QObject,並且在類的聲明中必須包涵Q_OBJECT宏。這裡用到的就如下的幾行代碼,非常簡單,一個按鈕單擊退出。#include <QtGui/QApplication>#include <QtGui/QPushButton>int main(int argc, char *argv[]){QApplication a(argc, argv);QPushButton button;QObject::connect(&button,SIGNAL(clicked()),qApp,SLOT(quit()));button.setText("hello world!");button.show();return a.exec();}//這裡main是qMain的宏替換,有興趣的可以到qtmain_win.cpp中看看在開始之前先解釋幾個宏:#define slots  #define signals protected#define emit  # define SLOT(a) qFlagLocation("1"#a QLOCATION)# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)# define QLOCATION "\0"__FILE__":"QTOSTRING(__LINE__)__FILE__ __LINE__ //調試宏不用理會const char *qFlagLocation(const char *method){static int idx = 0;flagged_locations[idx] = method;idx = (idx+1) % flagged_locations_count;return method;}//qobject.cpp : 2229這幾個宏是非常簡單的,signals和slots並不是C++的關鍵字,是Qt定義的宏為moc的關鍵字,signals,slots和emit的宏定義只是為了在編譯時間不會報錯,emit表示發射訊號的意義,其實不寫也沒關係,因為它的定義就是空。SLOT(a)與SIGNAL(a)只是將a替換成加”1”或者”2”的字串,在加入connectionLists鏈表時表示這是訊號與槽串連還是訊號與訊號串連。一. 開始調試:將斷點打到第八行:QObject::connect...與第十行:return a.exec();。先從connect開始,connect函數的功能是以訊號為索引記錄對應的槽函數:1. 將訊號與槽函數轉換成對應的字串,傳入connect函數,在connect函數中用一個指標數組cbdata儲存傳入的資料。//qobject.cpp2. 跳過一些安全檢查到第2510行const QMetaObject *smeta = sender->metaObject();const char *signal_arg = signal;++signal; //skip code得到QPushButton的元對象smeta,signal_arg儲存”2clicked()”,++signal後,signal為”clicked()”;既然出現了QMetaObject類,就在這裡介紹一下吧,訊號與槽的支援完全是QMetaObject在後台工作的結果,在我們使用Qt時完全用不到這個類,所以看不懂也沒關係:struct Q_CORE_EXPORT QMetaObject{ ...//函數的定義,可以到qobjectdefs.h查看struct { // private dataconst QMetaObject *superdata;const char *stringdata;const uint *data;const void *extradata;} d;//唯一一個資料的定義};每個支援訊號與槽機制的類必須派生於QObject,並且在類的聲明中必須包涵Q_OBJECT宏。在blog的最後有Q_OBJECT宏的展開,其中有一項是:static const QMetaObject staticMetaObject;詳細的解釋可以參考第三部分:moc元對象編譯器3. 計算訊號索引int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal);... ... //一系列計算訊號索引的方法,利用staticMetaObject 因為在繼承鏈中支援訊號和槽的類 都可以用staticMetaObject 對訊號和槽的個數進行計數signal_index += signalOffset;4. 對槽函數進行操作int membcode = extract_code(method);//membcode得到添加到訊號或者槽前面的字元’1’或者’2’減去’0’的值表示這是一個槽還是訊號。這裡是1const char *method_arg = method;++method; //skip codeconst QMetaObject *rmeta = receiver->metaObject();int method_index = -1;switch (membcode) {//1case QSLOT_CODE:method_index = rmeta->indexOfSlot(method);//獲得在整個繼承鏈中的索引break;case QSIGNAL_CODE:method_index = rmeta->indexOfSignal(method);//獲得在整個繼承鏈中的索引break;}IndexOfSlot(method)//這裡method是”quit()”字串在函數內部會從子類就是QApplication的staticMetaObject開始對slots函數名的字串判斷,如果找到返回對應的索引如果沒找到就到父類的staticMetaObject繼續尋找。5. 通過一系列的判斷,並對signal_index,和method_index的賦值後終於到了串連的語句,為了看到串連過程這裡摘錄了比較多的語句QMetaObjectPrivate::connect(sender, signal_index, receiver, method_index, type, types){//connect內部QObject *s = const_cast<QObject *>(sender);QObject *r = const_cast<QObject *>(receiver);QObjectPrivate::Connection *c = new QObjectPrivate::Connection;//對Connection賦值c->sender = s;c->receiver = r;c->method = method_index;c->connectionType = type;c->argumentTypes = types;c->nextConnectionList = 0;//將Connection加入到鏈表,函數很簡單就不做解釋了QObjectPrivate::get(s)->addConnection(signal_index, c);{//addConnection內部if (!connectionLists)connectionLists = new QObjectConnectionListVector();if (signal >= connectionLists->count())connectionLists->resize(signal + 1);ConnectionList &connectionList =(*connectionLists)[signal];if (connectionList.last) {connectionList.last->nextConnectionList = c;} else {connectionList.first = c;}connectionList.last = c;cleanConnectionLists();}//addConnection...}//connect二. 至此訊號和槽函數的串連完成,下面是訊號調用槽函數的過程:當button視窗正常顯示時,跟蹤clicked()訊號產生,及如何調用槽函數。1.window 系統下,每個視窗都有自己的視窗處理函數WndProc,Qt所有的視窗組件共用一個視窗處理函數 QtWndProc(qapplication_win.cpp:1465),這個視窗處理函數在視窗建立的過程中註冊到系統,關於視窗的所有訊息均由 QtWndProc處理。左鍵單擊是由WM_LBUTTONDOWN和WM_LBUTTONUP兩個訊息構成。2.以下是WM_LBUTTONUP處理過程的函數調用關係,因為WM_LBUTTONDOWN與WM_LBUTTONUP的處理過程類似,就以WM_LBUTTONUP為例:QtWndProc()QETWidget::translateMouseEvent()QApplicationPrivate::sendMouseEvent()QCoreApplication::sendSpontaneousEvent()QCoreApplication::notifyInternal()QApplication::notify()QApplicationPrivate::notify_helper()QPushButton::event()QAbstractButton::event()QWidget::event()QAbstractButton::mouseReleaseEvent()QAbstractButtonPrivate::click()QAbstractButtonPrivate::emitClicked()QAbstractButton::clicked()QMetaObject::activate()視窗獲得訊息,將其封裝成對應的事件,這裡是滑鼠事件,一層一層的傳遞到發生事件的對象,對事件傳遞的過程上面的函數調用已經列舉的很清楚了,就從QAbstractButton::mouseReleaseEvent()開始,會對滑鼠狀態進行判斷:if (e->button() != Qt::LeftButton)...//是否是滑鼠左鍵if (!d->down)...//按鍵是否被按下過if (hitButton(e->pos()))...//按鍵是否在視窗範圍內如果都成立則調用click()函數,click()調用emitClicked()函數,在emitClicked()中:emit q->clicked(checked);繞了一大圈終於看到發射訊號了,我們將其過程簡單的總結一下,首先將訊息WM_LBUTTONDOWN和WM_LBUTTONUP封裝成滑鼠click事件,再將事件轉換成訊號就是在click()函數中調用emitClicked()函數發射訊號,這隻是訊號到槽的路由的開始。但在開始之前還要解釋一下 emit q->clicked(checked);中的文法問題,這裡q是QAbstractButton對象的指標,是通過 Q_Q(QAbstractButton)得到的,因為訊號是signals:聲明的,而signals就是protected,在類外是無法訪問 protected成員的,但是在QAbstractButtonPrivate類中有這樣的聲明:Q_DECLARE_PUBLIC(QAbstractButton)宏的定義如下:#define Q_DECLARE_PUBLIC(Class)\inline Class* q_func() { return static_cast<Class *>(q_ptr); } \inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \friend class Class;這樣作為友元類就可以調用clicked()函數了。emit可以不寫,但是寫上做為訊號調用的標識是個不錯的習慣。3.下面的調用進入到訊號函數體,在我們寫代碼的時候,訊號唯寫了聲明,訊號的實現是由moc來實現的。moc會在檔案最後稍作解釋。void QAbstractButton::clicked(bool _t1){void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };QMetaObject::activate(this, &staticMetaObject, 2, _a);}_a是儲存所有參數地址的數組。QMetaObject::activate(this, &staticMetaObject, 2, _a);this是寄件者QPushButton,staticMetaObject是QAbstractButton類的元對象,因為有QAbstractButton::範圍,2是clicked()在這個moc檔案的索引,_a是參數數組地址。{//activate//首先計算通過staticMetaObject得到訊號和槽的位移量int signalOffset;int methodOffset;computeOffsets(metaObject, &signalOffset, &methodOffset);//從QAbstractButton的父類一直到QObject的所有訊號和槽的總數就是位移量//加上在QAbstractButton類內的位移量,就是clicked()訊號的索引int signal_index = signalOffset + local_signal_index;int signal_absolute_index = methodOffset + local_signal_index;//一些安全執行緒的操作,不做解釋略過QObjectConnectionListVector *connectionLists =sender->d_func()->connectionLists;// connectionLists在connect時見過的++connectionLists->inUse;QObjectPrivate::Connection *c =connectionLists->at(signal_index).first;QObjectPrivate::Connection *last =connectionLists->at(signal_index).last;QObject * const receiver = c->receiver;//Connection儲存起來的receiver,就是qApp;const int method = c->method;QObjectPrivate::Sender currentSender;const bool receiverInSameThread = currentThreadData == receiver->d_func()->threadData;//判斷sender和receiver是否在相同的線程QObjectPrivate::Sender *previousSender = 0;if (receiverInSameThread) {//如果在同一個線程currentSender.sender = sender;currentSender.signal = signal_absolute_index;currentSender.ref = 1;previousSender =QObjectPrivate::setCurrentSender(receiver, ¤tSender);}//if endmetacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);{//metacallreceiver ->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv);}//metacall end}//activate end4.receiver->qt_metacall(...)即是qApp->qt_metacall(...)這個函數是由moc元編譯器產生的,原型如下:int QApplication::qt_metacall(QMetaObject::Call _c, int _id, void **_a){//qt_metacall//_c是元方法標識,_id是槽函數索引,_a是參數列表地址_id = QCoreApplication::qt_metacall(_c, _id, _a);if (_id < 0)return _id;if (_c == QMetaObject::InvokeMetaMethod) {switch (_id) {...//這裡有10個case:槽函數}_id -= 10;}return _id;}//end qt_metacall槽函數的尋找方式:從QObject的moc檔案開始到最終子類的moc檔案結束,如果沒找到則減去該類中對應的槽函數個數,向下一層尋找,重複這個過程直到找到或_id<0為止。含有參數的槽函數調用,將_a數組內對應元素的轉換一下,_a內的元素只能等於或多於槽函數的參數,不能少於。三. moc元對象編譯器moc 是為了支援Qt的C++擴充特性而設計的,這裡只介紹有關訊號和槽的部分。元對象編譯器moc根據類聲明中的宏Q_OBJECT,以及signals及 slots部分的定義產生附加的C++代碼,moc編譯器產生moc_*.cpp附加檔案,這個附加源檔案是參與編譯的。signals和slots是moc的關鍵字,對signals會自動添加訊號函數的實現部分,會自動完成Q_OBJECT宏替換出的qt_metacall函數;並填寫staticMetaObject如下:const QMetaObject ClassName::staticMetaObject = {{&ParentName::staticMetaObject, //父類的元對象qt_meta_stringdata_ClassName, //元對象的字串資料qt_meta_data_ClassName,//填寫了一些建立串連時用到的資訊0//結束標誌}};在計算訊號和槽的位移量時,便是利用儲存在staticMetaObject中的qt_meta_data_ClassName和qt_meta_stringdata_ClassName數組的資料。下面舉個簡單的例子,如下代碼,在moc編譯後會產生的檔案是什麼樣的:#ifndef __MAIN_H#define __MAIN_H#include <QObject>class MyClass : public QObject{Q_OBJECTpublic:MyClass(QObject *parent = 0);~MyClass();signals:void Signal_MyClass();public slots:void Slot_MyClass();};#endifmoc產生的檔案:/****************************************************************************** Meta object code from reading C++ file 'main.h'**** Created: ??? ?? 16 09:52:39 2012** by: The Qt Meta Object Compiler version 63 (Qt 4.6.3)**** WARNING! All changes made in this file will be lost!*****************************************************************************/#include "main.h"#if !defined(Q_MOC_OUTPUT_REVISION)#error "The header file 'main.h' doesn't include <QObject>."#elif Q_MOC_OUTPUT_REVISION != 63#error "This file was generated using the moc from 4.6.3. It"#error "cannot be used with the include files from this version of Qt."#error "(The moc has changed too much.)"#endifQT_BEGIN_MOC_NAMESPACEstatic const uint qt_meta_data_MyClass[] = {// content:6, // revision0, // classname0, 0, // classinfo2, 14, // methods0, 0, // properties0, 0, // enums/sets0, 0, // constructors0, // flags1, // signalCount//以上與QMetaObjectPrivate結構體中的資料成員的第一對應// signals: signature, parameters, type, tag, flags9, 8, 8, 8, 0x05,// slots: signature, parameters, type, tag, flags26, 8, 8, 8, 0x0a,0 // eod};static const char qt_meta_stringdata_MyClass[] = {"MyClass\0\0Signal_MyClass()\0Slot_MyClass()\0"};//用字串記錄元對象資訊void MyClass::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a){if (_c == QMetaObject::InvokeMetaMethod) {Q_ASSERT(staticMetaObject.cast(_o));MyClass *_t = static_cast<MyClass *>(_o);switch (_id) {case 0: _t->Signal_MyClass(); break;case 1: _t->Slot_MyClass(); break;default: ;}}Q_UNUSED(_a);}const QMetaObjectExtraData MyClass::staticMetaObjectExtraData = {0, qt_static_metacall};const QMetaObject MyClass::staticMetaObject = {{ &QObject::staticMetaObject, qt_meta_stringdata_MyClass,qt_meta_data_MyClass, &staticMetaObjectExtraData }};// staticMetaObject的初始化#ifdef Q_NO_DATA_RELOCATIONconst QMetaObject &MyClass::getStaticMetaObject() { return staticMetaObject; }#endif //Q_NO_DATA_RELOCATIONconst QMetaObject *MyClass::metaObject() const{return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;}void *MyClass::qt_metacast(const char *_clname){if (!_clname) return 0;if (!strcmp(_clname, qt_meta_stringdata_MyClass))return static_cast<void*>(const_cast< MyClass*>(this));return QObject::qt_metacast(_clname);}int MyClass::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 < 2)qt_static_metacall(this, _c, _id, _a);_id -= 2;}return _id;}// SIGNAL 0void MyClass::Signal_MyClass(){QMetaObject::activate(this, &staticMetaObject, 0, 0);}//自訂訊號的實現,沒有參數所以只有一句QT_END_MOC_NAMESPACEQt 的元對象系統不依賴於視窗的訊息,只依賴與QMetaObject類,只要將訊號與槽正確的串連。上層的應用類將得到的訊息,事件等轉換為訊號,訊號再通過元對象調用槽函數響應。訊號和槽可以很好的將兩個對象聯絡起來,就像兩人見面打招呼一樣,一人發出訊號,另一人做出相應。如果想要類的對象可以主動發出訊號,在類的聲明前加上如下幾句,就可以像普通函數一樣使用訊號了:#if defined signals#undef signals#define signals public#endif下面是Q_OBJECT宏的定義:# define QT_TR_FUNCTIONS \ 用來支援字元轉換static inline QString tr(const char *s, const char *c = 0) \{ return staticMetaObject.tr(s, c); } \static inline QString tr(const char *s,const char *c,int n) \{ return staticMetaObject.tr(s, c, n); }#define Q_OBJECT \public: \static const QMetaObject staticMetaObject; \static const QMetaObject &getStaticMetaObject(); \virtual const QMetaObject *metaObject() const; \virtual void *qt_metacast(const char *); \QT_TR_FUNCTIONS \virtual int qt_metacall(QMetaObject::Call, int, void **); \private:

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.