分类: LINUX
2008-10-31 08:57:57
Qte通讯之signal slot
如果一个类需要利用qte提供的signal 机制,则需要做的是在申明类的时候,添加这样一个宏:Q_OBJECT,并且继承自qobject,这个宏展开后是这样的:
#define Q_OBJECT \
public: \
QMetaObject *metaObject() const { \
return staticMetaObject(); \
} \
const char *className() const; \
static QMetaObject* staticMetaObject(); \
QT_TR_FUNCTION \
protected: \
void initMetaObject(); \
private: \
static QMetaObject *metaObj;
………………………………………………………………………………
#ifndef QT_NO_TRANSLATION
#define QT_TR_FUNCTION static QString tr(const char*); \
static QString tr(const char*, const char*);
#else
#define QT_TR_FUNCTION // inherit the one from QObject
#endif
换句话说,只要你引用这样一个宏,你的类里面就多了这么一些函数和一个成员变量。
假设有这么一个头文件:
#include
class ClassName:public QObject
{
Q_OBJECT
public slots:
int slot1();
signals:
void signal1();
};
看看通过moc编译后的结果(这只是其中两个我们关心的函数):
QMetaObject* ClassName::staticMetaObject()
{
if ( metaObj )
return metaObj;
QMetaObject* parentObject = QObject::staticMetaObject();
static const QUParameter param_slot_0[] = {
{ 0, &static_QUType_int, 0, QUParameter::Out }
};
static const QUMethod slot_0 = {"slot1", 1, param_slot_0 };
static const QMetaData slot_tbl[] = {
{ "slot1()", &slot_0, QMetaData::Public }
};
static const QUMethod signal_0 = {"signal1", 0, 0 };
static const QMetaData signal_tbl[] = {
{ "signal1()", &signal_0, QMetaData::Public }
};
metaObj = QMetaObject::new_metaobject(
"ClassName", parentObject,//这里传递的两个数据表示类名和父类名。
slot_tbl, 1,//这里是槽集,和槽的个数。
signal_tbl, 1,//这里是信号集,和信号的个数。
#ifndef QT_NO_PROPERTIES
0, 0,
0, 0,
#endif // QT_NO_PROPERTIES
0, 0 );
cleanUp_ClassName.setMetaObject( metaObj );
return metaObj;
}
QMetaObject的结构不是很简单,但你可以理解成这样一种结构,里面存放的是信号和槽的信息。QMetaData你可以理解为描述信号或槽的数据结构,比如它的名称,它的函数指针,还有它的访问权限。QUParameter是用来描述槽(信号)的参数的结构,比如它的名称,类型,属性等。QUMethod是用来描述具体的某个槽(信号)的,包括它的名称,参数个数,和具体的参数描述。
接下来看看QMetaObject的构造函数都做了些什么:
QMetaObject::QMetaObject( const char *class_name, const char *superclass_name,
QMetaData *slot_data, int n_slots,
QMetaData *signal_data, int n_signals,
#ifndef QT_NO_PROPERTIES
QMetaProperty *prop_data, int n_props,
QMetaEnum *enum_data, int n_enums,
#endif
QClassInfo *class_info, int n_info )
{
if ( !objectDict ) { // first meta object created
objectDict= new QObjectDictionary( 211,
TRUE, // no copying of keys
FALSE ); // case sensitive
CHECK_PTR( objectDict );
objectDict->setAutoDelete( TRUE ); // use as master dict
}
classname = class_name; // set meta data
superclassname = superclass_name;
slotDict = init( slotData = slot_data, n_slots );
signalDict = init( signalData = signal_data, n_signals );
d = new QMetaObjectPrivate;
reserved = 0;
#ifndef QT_NO_PROPERTIES
d->propData = prop_data;
d->numPropData = n_props;
d->enumData = enum_data;
d->numEnumData = n_enums;
#endif
d->classInfo = class_info;
d->numClassInfo = n_info;
objectDict->insert( classname, this ); // insert into object dict
superclass = objectDict->find( superclassname ); // get super class meta object
}
在里我觉得最关键是三步:
slotDict = init( slotData = slot_data, n_slots );
signalDict = init( signalData = signal_data, n_signals );
这两步就负责初试化两个dict存放信号的和存放槽的。
就是通过把它们的名字连接到响应的槽或信号的数据结构。
objectDict->insert( classname, this );
这步是负责把相应的类名和类指针连接到一起。
void ClassName::signal1()
{
activate_signal( staticMetaObject()->signalOffset() + 0 );
}
这里面可以看出,一个信号,经过编译后就变成了另外一个函数activate_signal这个函数待会再讲。
接下来看看connect的实现过程。
假设为:
Connect(this,SIGNAL(signal1()),this,SLOT(slot1()));
看看signal和slot被展开后的结果。
#if defined(_OLD_CPP_)
#define METHOD(a) "0""a"
#define SLOT(a) "1""a"
#define SIGNAL(a) "2""a"
#else
#define METHOD(a) "0"#a
#define SLOT(a) "1"#a
#define SIGNAL(a) "2"#a
#endif
你可以简单的理解为SIGNAL(signal1()) 将会被展开成“2signal1()”
SLOT(slot1())将会被展开成“1slot1()”
那connect又会做些什么呢?
bool QObject::connect( const QObject *sender, const char *signal,
const QObject *receiver, const char *member )
{
#if defined(CHECK_NULL)
if ( sender == 0 || receiver == 0 || signal == 0 || member == 0 ) {
qWarning( "QObject::connect: Cannot connect %s::%s to %s::%s",
sender ? sender->className() : "(null)",
signal ? signal+1 : "(null)",
receiver ? receiver->className() : "(null)",
member ? member+1 : "(null)" );
return FALSE;
}
#endif
QMetaObject *smeta = sender->queryMetaObject();//get metaobject
if ( !smeta ){ // no meta object
qWarning("the smeta is null,added by wf\n");
return FALSE;
}
#if defined(CHECK_RANGE)
if ( !check_signal_macro( sender, signal, "connect", "bind" ) ){
qWarning("the check_signal_macro is null,added by wf\n");
return FALSE;
}
#endif
QCString signal_name = signal;
signal++; // skip member type code
QMetaData *sm = smeta->signal(signal,TRUE);
if ( !sm ) {
signal--;
#ifdef DEBUG_WHITESPACE
static int bad = 0;
qDebug("Invalid whitespace in sender: (%d), %s::%s", ++bad, sender->className(), signal+1);
#endif
signal_name = qt_rmWS( signal ); // white space stripped
signal = signal_name;
signal++;
sm = smeta->signal(signal,TRUE);
}
#ifdef DEBUG_WHITESPACE
else {
static int good = 0;
if (!(good%10))
qDebug("Good senders: %d", good);
good++;
}
#endif
if ( !sm ) {
#if defined(CHECK_RANGE)
err_member_notfound( SIGNAL_CODE, sender, signal, "connect" );
err_info_about_candidates( SIGNAL_CODE, smeta, signal, "connect" );
err_info_about_objects( "connect", sender, receiver );
#endif
qWarning("the err_info check error,added by wf\n");
return FALSE;
}
signal = sm->name; // use name from meta object
int membcode = member[0] - '0'; // get member code
QObject *s = (QObject *)sender; // we need to change them
QObject *r = (QObject *)receiver; // internally
#if defined(CHECK_RANGE)
if ( !check_member_code( membcode, r, member, "connect" ) ){
qWarning("the check member code err,added by wf\n");
return FALSE;
}
#endif
QCString member_name = member;
member++; // skip code
QMetaData *rm = 0;
QMetaObject *rmeta = r->queryMetaObject();
if ( !rmeta ){ // no meta object
qWarning("the rmeta is err,added by wf\n");
return FALSE;
}
#ifdef DEBUG_WHITESPACE
static int member_bad = 0;
static int member_good = 0;
#endif
switch ( membcode ) { // get receiver member
case SLOT_CODE:
rm = rmeta->slot( member, TRUE );
if ( !rm ) {
member--;
#ifdef DEBUG_WHITESPACE
qDebug("Invalid whitespace in receiver: (%d), %s::%s", ++member_bad, receiver->className(), member+1);
#endif
member_name = qt_rmWS( member ); // white space stripped
member = member_name;
member++;
rm = rmeta->slot(member,TRUE);
}
#ifdef DEBUG_WHITESPACE
else {
if (!(member_good%10))
qDebug("Good receivers: %d", member_good);
member_good++;
}
#endif
break;
case SIGNAL_CODE:
rm = rmeta->signal( member, TRUE );
if ( !rm ) {
member--;
#ifdef DEBUG_WHITESPACE
qDebug("Invalid whitespace in receiver: %d, %s::%s", ++member_bad, receiver->className(), member+1);
#endif
member_name = qt_rmWS( member ); // white space stripped
member = member_name;
member++;
rm = rmeta->signal(member,TRUE);
}
#ifdef DEBUG_WHITESPACE
else {
static int member_good = 0;
if (!(member_good%10))
qDebug("Good receivers: %d", member_good);
member_good++;
}
#endif
break;
}
if ( !rm ) { // no such member
#if defined(CHECK_RANGE)
err_member_notfound( membcode, r, member, "connect" );
err_info_about_candidates( membcode, rmeta, member, "connect" );
err_info_about_objects( "connect", sender, receiver );
#endif
qWarning("the err check in rm is null is err,added by wf\n");
return FALSE;
}
#if defined(CHECK_RANGE)
if ( !s->checkConnectArgs(signal,receiver,member) )
qWarning( "QObject::connect: Incompatible sender/receiver arguments"
"\n\t%s::%s --> %s::%s",
s->className(), signal,
r->className(), member );
#endif
if ( !s->connections ) { // create connections dict
s->connections = new QSignalDict( 7, TRUE, FALSE );
CHECK_PTR( s->connections );
s->connections->setAutoDelete( TRUE );
}
QConnectionList *clist = s->connections->find( signal );
if ( !clist ) { // create receiver list
clist = new QConnectionList;
CHECK_PTR( clist );
clist->setAutoDelete( TRUE );
s->connections->insert( signal, clist );
}
QConnection *c = new QConnection(r, rm->ptr, rm->name);
CHECK_PTR( c );
clist->append( c );
if ( !r->senderObjects ) { // create list of senders
r->senderObjects = new QObjectList;
CHECK_PTR( r->senderObjects );
}
r->senderObjects->append( s ); // add sender to list
s->connectNotify( signal_name );
return TRUE;
}
这段代码还是比较长,我们只看最重要的:
QMetaObject *smeta = sender->queryMetaObject();
这函数最后会调用staticMetaObject(),而这个函数,我们上面已经分析过了,
它就是把此类的信号和槽分别放到一个dict里面存起来,并返回一个QMetaObject,这里面存放了这些信号和槽的信息。
QCString signal_name = signal;
signal++; // 这里加1刚好把前面的数字1或2去掉了。
QMetaData *sm = smeta->signal(signal,TRUE);
这几句话的作用就是通过smeta调用signal函数传一个字符串进去返回一个描信号的结构。
接下来如法炮制,通过queryMetaObject返回接收者的QMetaObject,再通过一个switch语句确定接收的函数是信号还是槽。
if ( !s->connections ) { // create connections dict
s->connections = new QSignalDict( 7, TRUE, FALSE );
CHECK_PTR( s->connections );
s->connections->setAutoDelete( TRUE );
}
QConnectionList *clist = s->connections->find( signal );
if ( !clist ) { // create receiver list
clist = new QConnectionList;
CHECK_PTR( clist );
clist->setAutoDelete( TRUE );
s->connections->insert( signal, clist );
}
QConnection *c = new QConnection(r, rm->ptr, rm->name);
CHECK_PTR( c );
clist->append( c );
if ( !r->senderObjects ) { // create list of senders
r->senderObjects = new QObjectList;
CHECK_PTR( r->senderObjects );
}
r->senderObjects->append( s );
这段代码的意思就是,建立一个QSignalDict的结构,把信号和连接在此信号的槽或者信号的连表中,注意,这里是说,一个信号,对应一个链表,而这个连表存放的是所有连接在此信号上的信号和槽。
最后接收者再吧发送者连接到自己的发送连表中。
如果用一句话来表达connect的作用就是:
把相应的slot插入到此信号将要触发的信号和槽的链表中。
再看看这是怎么触发的:
先前知道了,触发一个信号可以调用它本身,也可以写emit signal1,它会去调用activate_signal,现在看看这个函数吧:
void QObject::activate_signal( const char *signal )
{
if ( !connections )
return;
QConnectionList *clist = connections->find( signal );
if ( !clist || signalsBlocked() )
return;
typedef void (QObject::*RT)();
RT r;
QConnectionListIt it(*clist);
register QConnection *c;
register QObject *object;
while ( (c=it.current()) ) {
++it;
object = c->object();
object->sigSender = this;
r = (RT)*(c->member());
(object->*r)();
}
}
代码很简单,就是通过此信号的名字,在一个集合里面查找其对应的connectionlist,
然后对此list里面的每个connection,判断其发送者是不是等于当前的对象,如果是,就调用此信号对应的函数或槽。
总结一下它的整个过程:
首先通过Q_OBJECT的申明引入了一个staticMetaObject()函数,当调用connect的时候,它先调用这个函数把此类的信号和槽存放到一个相应的dict中去供以后查询所用。然后建立一个链表把连接到此信号的槽或者信号先放到一个connectionlist中去,最后把此链表与响应的信号名字连接这一起。触发的时候根据此名字即可以找到相应的函数,然后调用,一起ok了!
|