Qt的Signal和Slot機制(二)
!-->![endif]-->!-->![endif]-->!-->
!-->![endif]-->!-->![endif]-->!-->
!-->![endif]-->!-->![endif]-->!-->
第二節 Signal和Slot的粘合劑
如果要連線一個Signal和Slot我們會用connect函式,下面我們就看一下connect是如何把Signal和Slot粘合在一起的。
以下是connect函式的宣告,
bool connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type)
首先我們先看一下connect函式用到的SIGNAL()和SLOT()這兩個巨集,其實他們就是分別生成Signal函式字串和Slot函式字串。字串裡包含了函式型別(用SIGNAL()函式型別就是2,用SLOT()函式型別就是1),函式名,函式引數列表。比如:
SIGNAL(SignalA2(int))生成了”2SignalA2(int)”的字串,2表示是Signal函式,SignalA2表示函式名,(int)表示函式列表。
SLOT(SlotA2(char*,int))生成了”1SlotA2(char*,int)”,1表示是Slot函式,SlotA2是函式名,(char*,int)表示引數列表。
看以下的例子
QTestA a;
QTestB b;
connect(&a,SIGNAL(SignalA2(int)),&b,SLOT(SlotB2(int)));
其實就是connect(&a, “2SignalA2(int)”,&b,”1SlotB2(int)”);
看到這裡,有看官會問,為啥要有在函式名前要放個標識阿。我們平常作的時候,const char *signal填入的總是Signal函式,const char *method填入的總是Slot函式。其實Qt還是很靈活的,const char *method引數裡你是可以填入一個Signal函式的,換句話說就是,你可以用一個Signal函式去觸發另一個Signal函式,Signal函式也可以作為被觸發函式。但是觸發函式只能是Signal函式。
接下來我們看一下,connect函式是怎樣一步步地把Signal函式和Slot(還有被觸發的Signal函式)連繫在一起的。
第一步,得到引數*signal字串裡Signal函式的id號(也就是觸發函式)
以下是相關程式碼:
QByteArray tmp_signal_name;
if(!check_signal_macro(sender, signal, "connect", "bind"))
returnfalse;
constQMetaObject *smeta= sender->metaObject();
constchar *signal_arg= signal;
++signal;//skip code
intsignal_index = smeta->indexOfSignal(signal);
if(signal_index < 0) {
//check for normalized signatures
tmp_signal_name= QMetaObject::normalizedSignature(signal - 1);
signal= tmp_signal_name.constData()+ 1;
signal_index= smeta->indexOfSignal(signal);
if(signal_index < 0) {
err_method_notfound(sender, signal_arg,"connect");
err_info_about_objects("connect", sender,receiver);
returnfalse;
}
}
首先呼叫check_signal_macro檢查*signal所指向的字串是不是Signal函式,怎麼判斷呢?就是看第一個字元(就是函式型別)是不是“2“,如果不是的話,則檢查失敗。
我們可以看一下check_signal_macro函式的實現部分
int sigcode= extract_code(signal);
if(sigcode != QSIGNAL_CODE){
if(sigcode == QSLOT_CODE)
qWarning("Object::%s: Attempt to %s non-signal %s::%s",
func, op, sender->metaObject()->className(), signal+1);
else
qWarning("Object::%s: Use the SIGNAL macro to %s %s::%s",
func, op, sender->metaObject()->className(), signal);
returnfalse;
}
return true;
通過extract_code函式得到此函式型別。
以下是extract_code的程式碼,
return (((int)(*member) - '0')& 0x3);
就是取第一個字元與0相減然後與3“且”。為什麼與“3”且呢?因為Qt中相關的函式型別就三種
分別是,
#define QMETHOD_CODE 0 // member type codes
#define QSLOT_CODE 1 //Slot型別
#define QSIGNAL_CODE 2 //Signal型別
得到型別後,比較如果不是QSIGNAL_CODE(Signal型別),則返回false,反之返回true
經過check_signal_macro檢查後,如果是Signal函式,則取出傳送方物件的QMetaObject值。
const QMetaObject *smeta = sender->metaObject();
smeta就是傳送方的QMetaObject物件指標,在上一章裡我們知道一個QObject類的Slot和Signal函式相關資訊都放在這個QMetaObject物件內。
然後我們會看到,觸發函式的id號是這樣被取得的,
const char *signal_arg = signal;
++signal; //skip code
int signal_index= smeta->indexOfSignal(signal);
注意++signal;,這主要是要查詢函式的id時,是用到函式名和引數列表,但signal的字串的第一個字元是函式型別,所以要忽略掉。
忽略掉函式型別後,呼叫indexOfSignal獲得函式的id號,但我們會發現這個id號不等於我們在QMetaObject裡儲存的id號,而是被加了偏移量。我們來看一下indexOfSignal函式的程式碼,看看為什麼要加偏移量以及偏移量是怎麼產生的?
int i =-1;
constQMetaObject *m= this;
while(m && i< 0) {
for(i = priv(m->d.data)->methodCount-1;i >= 0; --i)
if((m->d.data[priv(m->d.data)->methodData+ 5*i + 4] & MethodTypeMask)== MethodSignal
&& strcmp(signal, m->d.stringdata
+ m->d.data[priv(m->d.data)->methodData+ 5*i]) == 0) {
i += m->methodOffset();
break;
}
m= m->d.superdata;
}
在查詢索引號程式碼裡,首先先查詢在自己的QMetaObject裡有沒有此函式。
priv(m->d.data)->methodData表示Signal函式和Slot函式資訊的起始位置。
5*i是因為5個陣列元素為一條Signal函式或Slot函式資訊
priv(m->d.data)->methodData + 5*i+ 4就是表示函式型別的元素位置
m->d.data[priv(m->d.data)->methodData + 5*i+ 4]表示的就是函式型別
同理m->d.data[priv(m->d.data)->methodData+ 5*i]的是函式字串的在stringdata中的位置
if ((m->d.data[priv(m->d.data)->methodData+ 5*i + 4] & MethodTypeMask)== MethodSignal
&& strcmp(signal, m->d.stringdata
+ m->d.data[priv(m->d.data)->methodData+ 5*i]) == 0)
所以此語句的意思就是如果函式名一樣,且型別是Signal,那麼i就是索引值。如果找不到,去父類中查詢(因為我們可以使用父類的Signal來觸發)
而它的id號就是i+ m->methodOffset(),m->methodOffset()就是偏移量。
我們可以來看一下這個offset是如何確定的
int offset= 0;
constQMetaObject *m= d.superdata;
while(m) {
offset+= priv(m->d.data)->methodCount;
m= m->d.superdata;
}
return offset;
我們發現這個偏移量就是自己的Method數量(Signal+Slot),再加上所有的父類的Method數量。這樣可以形成一個唯一的id號,因為在父類中也會有和自己一樣的索引號(比如只要父類中也有Signal或Slot,它必然也有個Signal或Slot的索引值為0),為了不衝突所以要加一個偏移量。
第二步,得到引數*method字串裡被觸發函式的id號(Signal和Slot都有可能)
QByteArray tmp_method_name;
intmembcode = extract_code(method);
if(!check_method_code(membcode,receiver, method,"connect"))
returnfalse;
constchar *method_arg= method;
++method;// skip code
constQMetaObject *rmeta= receiver->metaObject();
intmethod_index = -1;
switch(membcode) {
caseQSLOT_CODE:
method_index= rmeta->indexOfSlot(method);
break;
caseQSIGNAL_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;
caseQSIGNAL_CODE:
method_index= rmeta->indexOfSignal(method);
break;
}
}
期過程和第一步非常相像。只是在這一步
switch (membcode) {
caseQSLOT_CODE:
method_index= rmeta->indexOfSlot(method);
break;
caseQSIGNAL_CODE:
method_index= rmeta->indexOfSignal(method);
break;
}
被觸發的函式可以是Signal,也可以是Slot
第三步,校驗觸發函式和被觸發函式的引數列表是否一致
if (!QMetaObject::checkConnectArgs(signal,method)) {
qWarning("QObject::connect: Incompatible sender/receiverarguments"
"/n %s::%s --> %s::%s",
sender->metaObject()->className(), signal,
receiver->metaObject()->className(), method);
returnfalse;
}
第四步,校驗引數型別是否合法
if ((type == Qt::QueuedConnection || type== Qt::BlockingQueuedConnection)
&& !(types = queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
returnfalse;
當是非同步觸發時(我們的connect模式為Qt::QueuedConnection,Qt::BlockingQueuedConnection或者自動模式,但是*send和*receive不屬於一個執行緒),我們需要校驗引數型別,如果不是Qt所認同的型別,就不能生成物件拷貝,來給被觸發函式使用。同理如果是指標的話,則不需要校驗,因為具體物件開發者自己維護。所以這就是為什麼有時候我們使用自定義的類或結構物件(不是指標),作為Signal和Slot的引數,會被提示“QObject::connect: Cannot queue arguments of type'%s'/n"
"(Make sure '%s' is registered usingqRegisterMetaType().”
解決的方法是呼叫qRegisterMetaType來註冊自定義類或結構,使之成為Qt認同的型別。
第五步,記錄Signal和Slot資訊
QMetaObject::connect(sender, signal_index,receiver, method_index,type, types);
QObject *s =const_cast<QObject*>(sender);
QObject*r = const_cast<QObject *>(receiver);
QOrderedMutexLockerlocker(&s->d_func()->threadData->mutex,
&r->d_func()->threadData->mutex);
QObjectPrivate::Connectionc;
c.receiver = r;
c.method = method_index;
c.connectionType = type;
c.argumentTypes= types;
s->d_func()->addConnection(signal_index, &c);
r->d_func()->refSender(s, signal_index);
將被觸發函式資訊“QObjectPrivate::Connection”(id號,被觸發物件指標,連線型別(BlockingQueuedConnection,QueuedConnection,direct)),通過addConnection,儲存到觸發物件(sender)的ConnectionList中,以後Signal函式就通過它直接呼叫被觸發函式,或者壓入到訊息佇列中。
我們看一下addConnection程式碼,
if (!connectionLists)
connectionLists= new QObjectConnectionListVector();
if(signal >= connectionLists->count())
connectionLists->resize(signal +1);//保證陣列比他的id號大,否則它無法插入
ConnectionList&connectionList = (*connectionLists)[signal];
connectionList.append(*c);
先是看有沒有connectionLists,connectionLists是一個元素為QList<Connection >的vector
換句話說,它裡面的每一個元素“QList<Connection >”就是一個Signal函式要相應得被觸發函式資訊集合,比方說(*connectionLists)[0],所有id號為0的Signal,它所對應的被觸發函式資訊“QObjectPrivate::Connection”(是Slot,也有可能是Signal)都放在這個list裡。因為一個Signal可以connect給不同的Slot函式或者Signal函式。所以這也是為什麼Signal函式id號要唯一的原因。否則會衝突(父類和子類有同樣的Signal id)。
另外,在讀程式碼中,始終無法明白為什要“儲存觸發函式資訊“到被觸發物件中,即以下這段程式碼
r->d_func()->refSender(s, signal_index);
還有就是tmp_method_name = QMetaObject::normalizedSignature(method);
是什麼意思還未了解。
相關文章
- Linux 下 signal 機制Linux
- 詳解Condition的await和signal等待/通知機制AI
- Linux訊號(signal)機制Linux
- qt事件機制QT事件
- xenomai核心解析之訊號signal(二)---xenomai訊號處理機制AI
- Qt 事件機制 學習QT事件
- 【Qt】connect機制原理QT
- [Vue] slot詳解,slot、slot-scope和v-slotVue
- Java併發程式設計,Condition的await和signal等待通知機制Java程式設計AI
- Qt 記憶體管理機制QT記憶體
- QT從入門到入土(三)——訊號和槽機制QT
- Qt 記憶體管理機制薦QT記憶體
- 淺談promise和js執行機制(二)PromiseJS
- session機制和cookie機制SessionCookie
- Qt5雙緩衝機制與例項QT
- Qt 自動連線機制訊號與槽QT
- jvm垃圾回收機制 二JVM
- 計算機中的二進位制計算機
- Delphi的元件讀寫機制(二) (轉)元件
- 請你說說 Vue 中 slot 和 slot-scope 的原理(2.6.11 深度解析)Vue
- qt傳送自定義signal,直接呼叫也可以,不使用emitQTMIT
- 【MySQL】InnoDB鎖機制之二MySql
- 流同步機制優化(二)優化
- Qt DLL總結【二】-建立及呼叫QT的 DLLQT
- 終端優化機制:墓碑機制和Doze優化
- 80386的分段機制、分頁機制和實體地址的形成
- Javascript事件模型系列(二)事件的捕獲-冒泡機制及事件委託機制JavaScript事件模型
- Qt原始碼解析之-從PIMPL機制到d指標QT原始碼指標
- Vue 開發之插槽(slot)的理解和使用Vue
- Session和Cookie機制SessionCookie
- Cookie和Session機制CookieSession
- 深入理解vue中的slot與slot-scopeVue
- Vue slot的用法Vue
- Vue 中的 slotVue
- Roguelike機制的原理和應用
- springMVC 的工作原理和機制SpringMVC
- Binder學習(二)Binder機制解析
- Qt中連線到同一signal的多個slots的執行順序問題QT