最近在用Qt开发项目,它的Signal和Slot机制引起了我的兴趣,闲暇无聊,看了下源代码,写下了一些自己的心得。但其中难免有错误之处,望各位看官不吝指出。
第一节 Signal和Slot的钥匙
我们知道Qt 通过”connect” 函数,将一个Signal 和Slot 对应了起来。为了形成对应,必有一结构来维护和保存这个对应关系。这个结构就是我们的幕后英雄 QMetaObject 。一般我们只会在由Qt 自动生成的moc 打头的cpp 文件里看到。
它主要有以下三个变量,
const QMetaObject *superdata ;
const char *stringdata ;
const uint *data ;
superdata ,父类的QMetaObject 指针,当我们在本类找不到相应的Signal 和Slot 时,可以去父类中找。
换句话说,父类的Signal 和Slot ,子类一样可以使用。
stringdata ,一个字符串,保存了类名,Signal ,Slot 函数名,和他们的参数名字
data ,一个数组,从这个数组中包含了“QMetaObjectPrivate “结构的信息,Signal 信息,Slot 信息还有其他的诸如properties 的信息,Signal 信息和Slot 信息保存了Signal 和Slot 函数名字的起始位置,结合上面的stringdata ,我们可以获得函数名,同时从保存信息的位置,确定了Signal 和Slot 的索引值
我们来看以下的一个例子,
class QTestA : public QObject
{
Q_OBJECT
public:
QTestA (QObject *parent );
~QTestA ();
signals:
void SignalA1 ();
void SignalA2 (int i );
public slots:
void SlotA1 ();
void SlotA2 (char *szBuf ,int nSize );
private:
};
这是一个继承QObject 的类,它有两个Singal 和两个Slot 。
以下是它的QMetaObject 内容,取自由Qt 工程自动生成的moc 打头的cpp 文件。
static const uint qt_meta_data_QTestA [] = {
// content:
2, // revision
0, // classname
0, 0, // classinfo
4, 12, // methods
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
///////////////////////////////////// 以上部分 是QMetaObjectPrivate 结构信息
// signals: signature, parameters, type, tag, flags
8, 7, 7, 7, 0x05,
21, 19, 7, 7, 0x05,
// slots: signature, parameters, type, tag, flags
35, 7, 7, 7, 0x0a,
56, 44, 7, 7, 0x0a,
0 // eod
};
它的data 成员变量内容,索引号0 到11 是 “QMetaObjectPrivate “结构的信息。
“QMetaObjectPrivate “结构如下:
struct QMetaObjectPrivate
{
int revision ;
int className ;
int classInfoCount , classInfoData ;
int methodCount , methodData ;
int propertyCount , propertyData ;
int enumeratorCount , enumeratorData ;
int constructorCount , constructorData ;
};
我们可以看到 qt_meta_data_QTestA [4] 也就是QMetaObjectPrivate 结构中的methodCount 变量 的值为4 ,说明有四个方法(本例中2 个Signal 和,2 个Slot 加起来正好是4 )。又可以看到qt_meta_data_QTestA [5] 也就是QMetaObjectPrivate 结构中的methodData 变量 的值为12 ,说明Method 的信息从qt_meta_data_QTestA 的第12 个数组元素开始(即qt_meta_data_QTestA[12] ),正好是 “ // signals: signature, parameters, type, tag, flags ”
它下面的两行就是我们所设定的两个Signal 函数的信息。
而在“ // slots: signature, parameters, type, tag, flags ”的下面是两个Slot 函数的信息。
他们的信息都是5 个一组
static const char qt_meta_stringdata_QTestA [] = {
"QTestA/0/0SignalA1()/0i/0SignalA2(int)/0"
"SlotA1()/0szBuf,nSize/0SlotA2(char*,int)/0"
};
这是他的stringdata 变量的内容,里面包含了类名( QTestA ),第一个Signal 函数名( SignalA1() ),
,第二个Singal 函数参数名(i ),第二个Signal 函数名( SignalA2(int) ), 第一个Slot 函数名( SlotA1() )
,第二个Slot 函数参数名( Buf,nSize ),第二个Slot 函数名( SlotA2(char*,int) )。
每一部分都用/0 结束,这样便于字符串操作,只要加上 偏移,就能得到该字符串,而不用管有多长。因为有关字符串的操作,都会碰到/0 后自动终止。
结合 data 成员变量内容,来自于data 成员变量内容第一条Signal 函数信息” 8, 7, 7, 7, 0x05”
其中8 ,表示第一个Signal 函数的名字的在“ qt_meta_stringdata_QTestA ”字符串的偏移位置是8 (注意/0 算一个字符,别数错了:p ),所以,指向它的名字的指针位置就是 qt_meta_stringdata_QTestA+8 。如果我们要拷贝它的名字,就非常简单。
出来的值就是 ” SignalA1() ”
比如以下代码,
char szSignal[256] = {0};
strcpy(szSignal, qt_meta_stringdata_QTestA+8);
看这时候,用/0 的分割的好处就出来了,我们只需要知道它从什么位置开始,不需要知道在什么位置结束。因为strrcpy 碰到/0 就不会再拷贝了。
第二条Signal 函数的信息是“ 21, 19, 7, 7, 0x05”
21 表示,第二个Signal 函数名字的偏移位置是21 ,所以他的值是 SignalA2(int)
同理,第一条Slot 函数的信息是” 35, 7, 7, 7, 0x0a ”
所以第一个Slot 函数的偏移量是35, 得出的值是 SlotA1()
第二条Slot 函数的信息是“56, 44, 7, 7, 0x0a ”,得出的值是 SlotA2(char*,int) 。
也许有看管会问,如何知道一条信息是slot 还是signal 呢,看最后一个参数貌似0x05 是表示的Signal ,
0x0a 表示的是Slot ,但我也看到过其他值。非常奇怪。
同时这几条信息的排列顺序就是,函数的索引值
// signals: signature, parameters, type, tag, flags
8, 7, 7, 7, 0x05, // 表示SignalA1 函数的信息,它的索引值0
21, 19, 7, 7, 0x05,// 表示SignalA2 函数的信息,它的索引值1
// slots: signature, parameters, type, tag, flags
35, 7, 7, 7, 0x0a,// 表示SlotA1 函数的信息,它的索引值2
56, 44, 7, 7, 0x0a,// 表示SlotA2 函数的信息,它的索引值3
索引值将会在这个函数使用
int QTestA ::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 ) {
switch (_id ) {
case 0: SignalA1 (); break ;
case 1: SignalA2 ((*reinterpret_cast < int (*)>(_a [1]))); break ;
case 2: SlotA1 (); break ;
case 3: SlotA2 ((*reinterpret_cast < char *(*)>(_a [1])),(*reinterpret_cast < int (*)>(_a [2]))); break ;
default : ;
}
_id -= 4;
}
return _id ;
}