×
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.
487 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×