做者:曹羣
原文:https://mp.weixin.qq.com/s/Mp...
歡迎關注學而思網校技術團隊公衆號:html
Qt 是一個1991年由Qt Company開發的跨平臺C++圖形用戶界面應用程序開發框架。它既能夠開發GUI程序,也可用於開發非GUI程序,好比控制檯工具和服務器。Qt是面向對象的框架,使用特殊的代碼生成擴展(稱爲元對象編譯器(Meta Object Compiler, moc))以及一些宏,Qt很容易擴展,而且容許真正地組件編程。Qt是跨平臺開發框架,支持Windows、Linux、MacOS等不一樣平臺;Qt有大量的開發文檔和豐富的API,給開發者帶來了很大的方便;Qt的使用者也愈來愈多,有不少優秀的產品都基於Qt開發,如:WPS Offic 、Opera瀏覽器、Qt Creator等。Qt的核心機制就是信號和槽,接下來咱們經過源代碼分析一下實現原理。編程
下面是咱們要分析的Demo代碼:
// MainWindow.h數組
// MainWindow.cpp瀏覽器
咱們能夠建立一個Qt工程,名稱爲Demo,編寫上面的代碼,進行構建,在VS下能夠把Qt工程導成VS工程,編譯生成,運行結果以下:安全
點擊中間的按鈕,咱們能夠看到控制檯打印以下信息:服務器
咱們分析代碼,能夠看到在頭文件Test和MainWindow類中,都有Q_OBJECT這樣的宏,而後咱們能夠看到上面的可執行文件夾下多出來一個moc_MainWindow.cpp文件,那麼咱們能夠嘗試把這兩個宏去掉,再進行構建,發現加上了信號和槽的就沒法編譯過去,咱們去掉這些信號和槽後,就不會生成moc開頭的這個文件了,固然咱們就沒法實現信號和槽機了,那麼這個宏究竟是什麼,有了它編譯器又會作什麼?讓咱們看看這個宏:數據結構
原來這個宏就是一些靜態方法和虛方法,可是若是咱們加入到類中,不進行實現,那必定會報錯的,爲何還能夠正常運行呢?原來Qt幫咱們作了不少事情,在編譯器編譯Qt代碼以前,Qt先將Qt自身擴展的語法進行翻譯,這個操做是經過moc(Meta-Object Compiler)又稱「元對象編譯器」完成的。首先moc會分析源代碼,把包含Q_OBJECT的頭文件生成爲一個C++源文件,這個文件的名字會是源文件名前面加上moc_,以後和原文件一塊兒經過編譯器處理,那咱們想到,這個moc開頭的cpp中必定實現了上面宏裏面的方法,以及數據的賦值;接下來咱們看看moc_MainWindow.cpp這個文件:框架
咱們從上面的代碼中能夠看到,是對Q_OBJECT中的靜態數據進行了賦值,而且實現了那些方法,這些都是Qt的moc編譯器幫咱們生成的,對代碼進行了分析,對信號和槽生成了符號,以及特定的數據結構,下面這個主要是記錄了類、信號、槽的引用計數、大小、偏移,後面會用到。函數
經過把QT_MOC_LITERAL這個宏進行替換後,獲得以下數據 :工具
接下來咱們看看下面qt_meta_data_MainWindow這個數組結構:content有兩列,第一列是總數,第二列是在這個數組中描述開始的索引,如1, 14, // methods,說明有一個methods,咱們能夠看到slots就是從索引14開始的。
從最上面的源代碼中咱們能夠看到再關聯信號和槽的時候,用到了SIGNAL和SLOT這兩個宏,那麼這兩個宏到底有什麼做用呢?咱們分析一下:
從上面咱們能夠看到其實這兩個就是一個字符串拼接的宏,會在信號(signal)前面拼接"2",如」2clean()「;會在槽(slots)前面拼接"1",如」1onClean()「; 其中,qFlagLocation這個方法主要是把method存儲在QThreadData裏面FlaggedDebugSignatures中的const char* locations[Count];表中,用於定位代碼對應的行信息。
預編譯後以下:
經過上面的一些基本宏、數據結構的介紹,咱們知道Qt給咱們作了不少工做,幫咱們生成了moc代碼,給咱們提供了一些宏,讓咱們開發簡潔方便,那麼Qt又是如何把信號和槽進行關聯的呢,就是兩個不一樣的實例,又是如何進行經過信號槽機制進行通訊的呢?接下來咱們看看信號和槽關聯的實現原理:
這個方法就是咱們上面moc_MainWindow.cpp中。
咱們根據調試能夠看到QObject::d_ptr->metaObject是空的,因此這樣smeta就是上面這個staticMetaObject變量了。
// 首先咱們得了解一下這個QMetaObject 和 QMetaObjectPrivate 的定義:
在Qt中爲了實現二進制兼容性,通常會定義一個私有類,QMetaObjectPrivate就是QMetaObject的私有類,QMetaObject負責一些接口實現,QMetaObjectPrivate具體進行實現,這兩個類通常是經過P指針和D指針進行組合式的訪問,有一個宏:
咱們看上面的staticMetaObject是一個QMetaObject類型的變量,其中QMetaObject進行了賦值:
其中QMetaObject 是對外的結構,裏面的connect方法最終調用的仍是QMetaObjectPrivate裏面的connect進行實現的。QMetaObject裏的d成員填充了上面的staticMetaObject數據,而QMetaObjectPrivate裏面的成員填充qt_meta_stringdata_Test數組中的數據,咱們能夠看到填充前14個數據,這也是moc生成methodData時以14爲基數的緣由了,轉換方法以下:
具體實現以下:
其中int handle = priv(m->d.data)->methodData + 5i; 咱們能夠分析,其實就是14+5i ,那爲何是5呢?由於:
// signals: name, argc, parameters, tag, flags
1, 0, 24, 2, 0x06 / Public /,
// slots: name, argc, parameters, tag, flags
3, 0, 25, 2, 0x08 / Private /,
咱們能夠看到每個signals或者slots都有5個整形表示。
// MethodFlags是一個枚舉類型,咱們能夠看到MethodSignal = 0x04, MethodSlot = 0x08;
// slots: name, argc, parameters, tag, flags
3, 0, 25, 2, 0x08 / Private /,
enum ConnectionType { AutoConnection, DirectConnection, QueuedConnection, BlockingQueuedConnection, UniqueConnection = 0x80 };
咱們介紹一些鏈接類型:
最後到了信號和槽關聯核心的地方了:
首先,咱們先得了解如下數據結構:
上面的這三個數據結構很重要,QObject是咱們最熟悉的基類,QObjectPrivate是它的私有類,進行具體實現,QObjectPrivate繼承自QObjectData,在QObject裏面以組合的形式也進行P指針和D指針的方式進行訪問的。在信號和槽關聯過程當中,數據結構Connection是很重要的數據結構,下面的這個結構是ConnectionList的一個Vector:
有了上面的數據結構,咱們就能夠分析下面的連接過程了, 咱們看到下面的先是調用的QMetaObjectPrivate的connect, 以後又用QMetaObject::Connection進行了指針包裝:
QObjectPrivate::get(s) 方法其實就是獲取了一個QObjec裏面的QObjectPrivate實例,以後調用addConnection方法添加到鏈表中:
結構以下:
分析:
// 而後進入真正的QMetaObject::activate
咱們的例子是Autoconntion模式,因此就會執行下面的代碼進行回調:
咱們終於看到了,函數進行了回調到moc_MainWindow.cpp裏面,而後調用對應的槽onClean ;
最終調用到這裏後,打印輸出:"MainWindow::onClean"
最後就是調用完後,會回到onDestory這裏:
注意:若是咱們在onClean中進行了對m_testWidget對象的釋放操做(delete m_testWidget),再到onDestory()中 emit clean(); 後面進行訪問成員,那麼必定崩潰,因此要注意。
一、https://woboq.com/blog/how-qt...
二、Qt5.7源碼
三、本身用C++實現的信號和槽demo:http://note.youdao.com/notesh...