http://dev.wo.com.cn/bbs/redirect.jsp?fid=25127&tid=151886&goto=nextoldset
QT 源码之QT元对象系统和信号槽机制是本文要介绍的内容。QT的信号和槽机制是用来在对象间通讯的方法,当一个特定事件发生的时候,signal会被 emit 出来,slot 调用是用来响应相应的 signal 的。简单点说就是如何在一个类的一个函数中触发另一个类的另一个函数调用,而且还要把相关的参数传递过去.好像这和回调函数也有点关系,但是消息机制可比回调函数有用多了,也复杂多了。
下面的代码是我写的一个继承QLabel的类,是QLabel可以响应鼠标单击的消息。
- viewplaincopytoclipboardprint?
- #include<QLabel>
- #include<QWidget>
- #include<QMessageBox>
- #include<QApplication>
- classClickedLabel:publicQLabel
- {
- Q_OBJECT
- signals:
- voidClicked(ClickedLabel*clicked);
- public:
- ClickedLabel(constQString&text,QWidget*parent=0):QLabel(text,parent){};
- ~ClickedLabel(){};
- protected:
- voidmouseReleaseEvent(QMouseEvent*){emitClicked(this);};
- publicslots:
- voidOnCLicked(ClickedLabel*){QMessageBox::information(topLevelWidget(),"MessagefromQt","LabelClicked!");};
- };
- #include"main.moc"
- intmain(intargc,char*argv[])
- {
- QApplicationapp(argc,argv);
- ClickedLabellabel("<h2>test</h2>");
- QObject::connect(&label,SIGNAL(Clicked(ClickedLabel*)),&label,SLOT(OnCLicked(ClickedLabel*)));
- label.show();
- returnapp.exec();
- }
- #include<QLabel>
- #include<QWidget>
- #include<QMessageBox>
- #include<QApplication>
- classClickedLabel:publicQLabel
- {
- Q_OBJECT
- signals:
- voidClicked(ClickedLabel*clicked);
- public:
- ClickedLabel(constQString&text,QWidget*parent=0):QLabel(text,parent){};
- ~ClickedLabel(){};
- protected:
- voidmouseReleaseEvent(QMouseEvent*){emitClicked(this);};
- publicslots:
- voidOnCLicked(ClickedLabel*){QMessageBox::information(topLevelWidget(),"MessagefromQt","LabelClicked!");};
- };
- #include"main.moc"
- intmain(intargc,char*argv[])
- {
- QApplicationapp(argc,argv);
- ClickedLabellabel("<h2>test</h2>");
- QObject::connect(&label,SIGNAL(Clicked(ClickedLabel*)),&label,SLOT(OnCLicked(ClickedLabel*)));
- label.show();
- returnapp.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宏的定义:
- #defineQ_OBJECT
- public:
- Q_OBJECT_CHECK
- staticconstQMetaObjectstaticMetaObject;
- virtualconstQMetaObject*metaObject()const;
- virtualvoid*qt_metacast(constchar*);
- QT_TR_FUNCTIONS
- virtualintqt_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 产生的文件里面可以找到这两个接口的实现:
- constQMetaObject*ClickedLabel::metaObject()const
- {
- return&staticMetaObject;
- }
- void*ClickedLabel::qt_metacast(constchar*_clname)
- {
- if(!_clname)return0;
- if(!strcmp(_clname,qt_meta_stringdata_ClickedLabel))
- returnstatic_cast<void*>(const_cast<ClickedLabel*>(this));
- returnQLabel::qt_metacast(_clname);
- }
- 3宏QT_TR_FUNCTIONS是和i18n相关的,我们暂时不用去管它。
- #defineQT_TR_FUNCTIONS
- staticinlineQStringtr(constchar*s,constchar*c=0)
- {returnstaticMetaObject.tr(s,c);}
- 4最后是接口函数qt_metacall,他的作用是查表,调用函数
- intClickedLabel::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){
- case0:Clicked((*reinterpret_cast<ClickedLabel*(*)>(_a[1])));break;
- case1:OnCLicked((*reinterpret_cast<ClickedLabel*(*)>(_a[1])));break;
- }
- _id-=2;
- }
- return_id;
- }
- 我们来仔细看看QMetaObject,这就是meta-object的数据结构定义
- structQ_CORE_EXPORTQMetaObject
- {
- constchar*className()const;
- constQMetaObject*superClass()const;
- QObject*cast(QObject*obj)const;
- //...
- struct{//privatedata
- constQMetaObject*superdata;
- constchar*stringdata;
- constuint*data;
- constvoid*extradata;
- }d;
- };
下面看看我们生成的具体的代码:
- staticconstuintqt_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
- };
- staticconstcharqt_meta_stringdata_ClickedLabel[]={
- "ClickedLabel00clicked0Clicked(ClickedLabel*)0"
- "OnCLicked(ClickedLabel*)0"
- };
- constQMetaObjectClickedLabel::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的定义:
- structQMetaObjectPrivate
- {
- intrevision;
- intclassName;
- intclassInfoCount,classInfoData;
- intmethodCount,methodData;
- intpropertyCount,propertyData;
- intenumeratorCount,enumeratorData;
- };
很明显,利用qt_meta_data_ClickedLabel中存储的索引和qt_meta_stringdata_ClickedLabel中存储的值,我们很容易将QMetaObject构建起来。这中间的转换是通过
- staticinlineconstQMetaObjectPrivate*priv(constuint*data)
- {returnreinterpret_cast<constQMetaObjectPrivate*>(data);}
这个函数来完成的。
下面我们着重看看几个与 signal/slot 相关的代码
qobject.cpp 文件中关于 QObject::connect() 函数的代码,
- boolQObject::connect(constQObject*sender,constchar*signal,
- constQObject*receiver,constchar*method,
- Qt::ConnectionTypetype)
- {
- {
- constvoid*cbdata[]={sender,signal,receiver,method,&type};
- if(QInternal::activateCallbacks(QInternal::ConnectCallback,(void**)cbdata))
- returntrue;
- }
- #ifndefQT_NO_DEBUG
- boolwarnCompat=true;
- #endif
- if(type==Qt::AutoCompatConnection){
- type=Qt::AutoConnection;
- #ifndefQT_NO_DEBUG
- warnCompat=false;
- #endif
- }
- //判断是否是NULL
- if(sender==0||receiver==0||signal==0||method==0){
- qWarning("QObject::connect:Cannotconnect%s::%sto%s::%s",
- sender?sender->metaObject()->className():"(null)",
- (signal&&*signal)?signal+1:"(null)",
- receiver?receiver->metaObject()->className():"(null)",
- (method&&*method)?method+1:"(null)");
- returnfalse;
- }
- QByteArraytmp_signal_name;
- if(!check_signal_macro(sender,signal,"connect","bind"))
- returnfalse;
- constQMetaObject*smeta=sender->metaObject();
- ++signal;//skipcode
- intsignal_index=smeta->indexOfSignal(signal);
- if(signal_index<0){
- //checkfornormalizedsignatures
- 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);
- returnfalse;
- }
- }
- QByteArraytmp_method_name;
- intmembcode=method[0]-'0';
- if(!check_method_code(membcode,receiver,method,"connect"))
- returnfalse;
- ++method;//skipcode
- 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){
- //checkfornormalizedmethods
- tmp_method_name=QMetaObject::normalizedSignature(method);
- method=tmp_method_name.constData();
- switch(membcode){
- caseQSLOT_CODE:
- method_index=rmeta->indexOfSlot(method);
- break;
- caseQSIGNAL_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);
- returnfalse;
- }
- if(!QMetaObject::checkConnectArgs(signal,method)){
- qWarning("QObject::connect:Incompatiblesender/receiverarguments"
- "nt%s::%s-->%s::%s",
- sender->metaObject()->className(),signal,
- receiver->metaObject()->className(),method);
- returnfalse;
- }
- int*types=0;
- if((type==Qt::QueuedConnection||type==Qt::BlockingQueuedConnection)
- &&!(types=queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
- returnfalse;
- #ifndefQT_NO_DEBUG
- {
- QMetaMethodsmethod=smeta->method(signal_index);
- QMetaMethodrmethod=rmeta->method(method_index);
- if(warnCompat){
- if(smethod.attributes()&QMetaMethod::Compatibility){
- if(!(rmethod.attributes()&QMetaMethod::Compatibility))
- qWarning("QObject::connect:ConnectingfromCOMPATsignal(%s::%s)",smeta->className(),signal);
- }elseif(rmethod.attributes()&QMetaMethod::Compatibility&&membcode!=QSIGNAL_CODE){
- qWarning("QObject::connect:Connectingfrom%s::%stoCOMPATslot(%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);
- returntrue;
- }
上面这段代码首先调用了 QInternal 这个 namespace 里面 activateCallbacks 这个函数,然后根据 QMetaObject 信息检查了 sender、receiver 以及对应 signal/slots 的匹配性,得到元数据类。把 signal/slot 字符串转换成为了对应的 index,然后检查信号和槽的参数是否一致,槽函数的参数可以小于信号函数的参数。
最后得到method的元数据QMetaMethod,然后调用QMetaObject::connect的方法。
- boolQMetaObject::connect(constQObject*sender,intsignal_index,
- constQObject*receiver,intmethod_index,inttype,int*types)
- {
- QConnectionList*list=::connectionList();
- if(!list)
- returnfalse;
- QWriteLockerlocker(&list->lock);
- list->addConnection(const_cast<QObject*>(sender),signal_index,
- const_cast<QObject*>(receiver),method_index,type,types);
- returntrue;
- }
QMetaObject::connect代码中QWriteLocker是为了防止多线程操作引起问题。
一旦我们发送了信号,就应该调用相关槽中的方法了,这个过程其实就是查找全局的connect列表的过程。真正发出信号是在main.moc中。
- voidClickedLabel::Clicked(ClickedLabel*_t1)
- {
- void*_a[]={0,const_cast<void*>(reinterpret_cast<constvoid*>(&_t1))};
- QMetaObject::activate(this,&staticMetaObject,0,_a);
- }
- voidQMetaObject::activate(QObject*sender,intfrom_signal_index,intto_signal_index,void**argv)
- {
- //这里得到的是QObject的数据,首先判断是否为阻塞设置
- if(sender->d_func()->blockSig)
- return;
- //得到全局链表
- QConnectionList*constlist=::connectionList();
- if(!list)
- return;
- QReadLockerlocker(&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_iteratorit=list->sendersHash.find(sender);
- constQConnectionList::Hash::const_iteratorend=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*constcurrentThread=QThread::currentThread();
- constintcurrentQThreadId=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(inti=0;i<connections.size();++i){
- constintat=connections.constData()[connections.size()-(i+1)];
- QConnectionList*constlist=::connectionList();
- //得到连接
- QConnection&c=list->connections[at];
- c.inUse=0;
- if(!c.receiver||(c.signal<from_signal_index||c.signal>to_signal_index))
- continue;
- //判断是否放到队列中
- //determineifthisconnectionshouldbesentimmediatelyor
- //putintotheeventqueue
- 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设置当前发送者
- constintmethod=c.method;
- QObject*constpreviousSender=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);
- #ifdefined(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中实现的。
- intClickedLabel::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){
- case0:Clicked((*reinterpret_cast<ClickedLabel*(*)>(_a[1])));break;
- case1:OnCLicked((*reinterpret_cast<ClickedLabel*(*)>(_a[1])));break;
- }
- _id-=2;
- }
- return_id;
- }