×
Namespaces

Variants
Actions

深入浅出MetaObject,Signal-Slot

From Nokia Developer Wiki
Jump to: navigation, search
Article Metadata

代码示例
兼容于
平台:
Symbian

文章
cuizhan 在 03 Nov 2011 创建
最后由 hamishwillee 在 11 Oct 2012 编辑


Contents

介绍

大家用Qt都用了很久了,肯定都听说过MetaObject在Qt中是很重要的,但是MetaObject到底是什么。Signal-Slot到底是如何利用MetaObject进行通信的。这篇文章在这里做一个简单的介绍,起个抛砖引玉的作用,欢迎大家留言指正。

引言

那么如何使我们自己定义的类拥有Qt的MetaObject呢?
比如我们要使自己的类拥有自己的Signal-Slot那么大家都知道,我们必须。。。。。。
1 继承自QObject。
2 在类的申明最开始必须包含Q_OBJECT宏。
有同学看过Q_OBJECT宏的定义么?没关系,我们一起做个例子来看看。


开始

class ClickedLabel : public QLabel     
{
Q_OBJECT
signals:
void Clicked(ClickedLabel* clicked);
public:
ClickedLabel(const QString &text,QWidget *parent=0): QLabel(text,parent)
{
}
~ClickedLabel() { }
protected:
void mouseReleaseEvent( QMouseEvent* )
{
emit Clicked(this);
}
public slots:
void OnCLicked( ClickedLabel* )
{
QMessageBox::information(topLevelWidget(),
"Message from Qt", "Label Clicked!");
}
};

这里我定义了一个自己的类继承自QLabel,看看里面都有些什么, 一个构造函数,一个析构函数。来。重点来了。大家注意,一个Signal:Clicked(),还有一个Slot,OnClicked。这个Slot的作用是当接收到信号的时候,显示一个对话框。最后由于我们的QLabel没有响应鼠标响应事件,所以,我们在这个类里重写mouseEvent事件接收函数,它做一个简单的事情,就是发送一个Clicked() Signal。
好,让我们再来看看main函数做了什么。

#include "main.moc"
int main(int argc,char* argv[])
{
QApplication app(argc,argv);
ClickedLabel label("<h2>test</h2>");
QObject::connect( &label, SIGNAL( Clicked(ClickedLabel*) ),&label, SLOT( OnCLicked(ClickedLabel*) ) );
label.show();
return app.exec();
}

简单吧,只需要连接Clicked Signal和 OnClicked Slot。大家发挥发挥想象力,当我们程序开始运行的时候,会展现一个“text” 窗口 -> 当我们双击这个窗口 -> 会弹出一个Dialog。 好,让我们看看signal-slot是怎么工作地吧~~。 但是之前。我们还是要来看看Q_OBJECT。

Q_OBJECT

我们进入\include\QtCore\qobjectdefs.h文件

#define Q_OBJECT \
public: \
Q_OBJECT_CHECK \
static const QMetaObject staticMetaObject; \
Q_OBJECT_GETSTATICMETAOBJECT \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
QT_TR_FUNCTIONS \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
...

这个宏给我们定义了很多的东西,让我们一起来看一看吧。
1 Q_OBJECT_CHECK: 又是一个宏,做类型检查等工作,此处不是本文重点,飘过~。
2 static const QMetaObject staticMetaObject: 哈哈,这里,大家来看看, 这个名字熟悉不?对!它就是我们这个类的metaobject。看,还是静态的哦。
3 virtual const QMetaObject *metaObject() const:这个就是获取我们自己的泪的metaobject。
4 virtual int qt_metacall: 用于siganl-slot的调用。
好。Q_OBJECT给我们声明了这么多东西,不错吧,Qt真好!
但是。等等,这些都是函数和成员的申明啊,我们Qt是基于c++的,只有函数的申明,没有函数的定义,编译器能编译通过么?还是Qt有自己的编译器?
一大串“???????????????????飘过。

答案来了。。。。因为我们有MOC。

MOC

什么是MOC? MOC的全称是”Meta-Object Compiler“。在我们一般的C++程序(非Qt程序),编译过程大概分为两个阶段(什么语法检查这些略过),既我们所说的”编译“->"链接"。MOC就在我们这个过程之中插了一脚,MOC会检查在头文件中包含了Q_OBJECT宏的类,帮这些类生成相应的moc_class.cpp文件,这个文件包含了,Q_OBJECT的声明的对象和函数的定义,所以在编译的时候,编译器就不会报错啦~。


好,让我们看看到生成了什么

QT_BEGIN_MOC_NAMESPACE
static const uint qt_meta_data_ClickedLabel[] = {
// content:
5, // revision
0, // classname
0, 0, // classinfo
2, 14, // methods
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
1, // signalCount
// signals: signature, parameters, type, tag, flags
22, 14, 13, 13, 0x05,
// slots: signature, parameters, type, tag, flags
45, 13, 13, 13, 0x0a,
0 // eod
};
static const char qt_meta_stringdata_ClickedLabel[] = {
"ClickedLabel\0\0clicked\0Clicked(ClickedLabel*)\0"
"OnCLicked(ClickedLabel*)\0"
};
const QMetaObject ClickedLabel::staticMetaObject = {
{ &QLabel::staticMetaObject, qt_meta_stringdata_ClickedLabel,
qt_meta_data_ClickedLabel, 0 }
};
#ifdef Q_NO_DATA_RELOCATION
const QMetaObject &ClickedLabel::getStaticMetaObject() { return staticMetaObject; }
#endif //Q_NO_DATA_RELOCATION
const QMetaObject *ClickedLabel::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
}
void *ClickedLabel::qt_metacast(const char *_clname)
{
if (!_clname) return 0;
if (!strcmp(_clname, qt_meta_stringdata_ClickedLabel))
return static_cast<void*>(const_cast< ClickedLabel*>(this));
return QLabel::qt_metacast(_clname);
}
int ClickedLabel::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) {
case 0: Clicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
case 1: OnCLicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
default: ;
}
_id -= 2;
}
return _id;
}
// SIGNAL 0
void ClickedLabel::Clicked(ClickedLabel * _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
QT_END_MOC_NAMESPACE

好,大家不要被这么多代码吓到了~没什么的。后面让我们一步步来解释这些。

MetaObject 数据结构

还记得我们之前在Q_OBJECT里面申明的staticMetaObject,看main.moc中是如何实现的。 在这之前我们看看MetaObject的数据结构是什么。
打开\include\QtCore\qobjectdefs.h

 struct { // private data                
const QMetaObject *superdata; //[1]
const char *stringdata; //[2]
const uint *data; //[3]
const void *extradata; //[4]
};

这就是metaobject的数据结构。
1 superdata: 指向父类的metaobject的指针。
2 stringdata: 一个字符指针,其实是一个字符串数组,里面存储的metaobject最原始的信息。
3 data: 一个uint指针,指向一个uint数组,这个数组是用于配合上面stringdata,来解析metaobject的。
4 extradata: 用于将来扩展用。

好,我们来看一下。MOC帮我如何实现的

const QMetaObject ClickedLabel::staticMetaObject = {
 
{
&QLabel::staticMetaObject, //[1]
qt_meta_stringdata_ClickedLabel,
//[2]
qt_meta_data_ClickedLabel, //[3]
0 //[4]
}
 
};

大家还记得我们上面讲的metaobject的结构吧,一个指向父类的指针,一个字符串数据,一个uint数组,还有个指向扩展用的数据结构的指针。
来,让我们一起来看看,
1 指向父类metaobject的指针:大家应该还记得我们的父类是QObject,所以这里&QLabel::staticMetaObject。 2 原始数据,一个字符串数组,qt_meta_stringdata_ClickedLabel,这个也是MOC帮我们实现的。实现如下:

static const char qt_meta_stringdata_ClickedLabel[] = {
"ClickedLabel\0\0clicked\0Clicked(ClickedLabel*)\0"
"OnCLicked(ClickedLabel*)\0"

metaobject原始存储的就是一个字符串,你能看到,构造函数,signal,slot,是吧。让我们再看看metaobject第3个成员
3 unit数组qt_meta_data_ClickedLabel

static const uint qt_meta_data_ClickedLabel[] = {
// content:
5, // revision
0, // classname
0, 0, // classinfo
2, 14, // methods
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
1, // signalCount
// signals: signature, parameters, type, tag, flags
22, 14, 13, 13, 0x05,
// slots: signature, parameters, type, tag, flags
45, 13, 13, 13, 0x0a, 0 // eod
};

大家看看,上面第4行, 第一个数字”22“,可以在原始字符串中-qt_meta_stringdata_ClickedLabel,第22个字符正好就是我的Signal名字的开始位置。 这里暂时到这里,让我们看看双击以后signal Clicked()到底是如何触发slot onClicked的。

Signal-Slot

当双击后会发生什么,我们来一步步的看:
1 双击窗体产生一个MouseClicked事件,通过Applicaiton::Notify发送给我们的ClickedLabel对象。
2 ClickedLabel对象通过mouseReleaseEvent方法接收到该事件,并触发一个Clicked() signal。
3 这里我们到main.moc看下signal的实现,具体如下

void ClickedLabel::Clicked(ClickedLabel * _t1)
{
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

其实就是把自己和自己的metaobject传递过去。接下来看看QMetaObject::activate到底做了什么。
4 QMetaObject::activate这个函数的定义可以在\src\corelib\kernel\qobject.cpp中找到。在这里只做简要的说明,敢兴趣的同学可以编译源码后,跟进去看看。:)

void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
void **argv)
{
int signalOffset;
int methodOffset;
//通过metaobject计算当前signal method的偏移量。
 
computeOffsets(m, &signalOffset, &methodOffset);
//拼接signal 的index
int signal_index = signalOffset + local_signal_index;
//做一些检查以及验证
if (!sender->d_func()->isSignalConnected(signal_index))
 
return; // nothing connected to these signals, and no spy
if (sender->d_func()->blockSig)
return;
.......
//获取当前线程的数据,用于判断signal-slot的sender和receiver是否在同一个线程中,那么如果是默认的自动连接话,那么其实是queen connect,后面会看到。
QThreadData *currentThreadData = QThreadData::current();
.....
QObject * const receiver = c->receiver;
// determine if this connection should be sent immediately or
// put into the event queue
//此处就是判断什么时候用队列连接,什么时候用直接连接,或者用队列阻塞的方式连接。我们的例子会用直接连接。
 
if ((c->connectionType == Qt::AutoConnection
&& (currentThreadData != sender->d_func()->threadData
|| receiver->d_func()->threadData != sender->d_func()->threadData))
|| (c->connectionType == Qt::QueuedConnection))
{
 
queued_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv);
 
continue;
 
} else if (c->connectionType == Qt::BlockingQueuedConnection)
{
 
blocking_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv);
 
continue;
 
}
//直接连接,我们例子走这里。
metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
......

5 最后会调用MOC生成的代码中,我们进入main.moc看看

int ClickedLabel::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) {
case 0: Clicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
//此处调用我们的OnClicked Slot
 
case 1: OnCLicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
default: ;
}
_id -= 2;
}
return _id;
}

6 最后弹出对话框。整个过程结束。

总结

通过上面的例子,我们看到了MetaObject是如何作用在Signal-Slot上的,那么大家有没有想过,假如是直接连接的话,Signal-Slot的方式和一般的函数直接调用上是否有差别。答案是肯定的,由于Signal-slot在触发的时候需要对字符串进行解析,而且通过函数层层调用,当然会有效率上会有差别,经过测试,Signal-Slot在效率上会比一般的函数直接调用慢10倍。但是,这样的时间也是在我们可以接受的范围内的,至少signal-slot比虚函数快。所以,大家尽可能的放心使用啦。

代码下载

File:Cuizhan GoDeepIntoSignalSlot.zip

相关链接

Qt 开发

This page was last modified on 11 October 2012, at 04:19.
751 page views in the last 30 days.
×