当前位置:首页 > 嵌入式培训 > 嵌入式学习 > 讲师博文 > moc文件分析

moc文件分析 时间:2018-09-26      来源:未知

Qt 不是使用“标准的”C++语言编写,而是对其进行了一定程度的扩展。我们可以从Qt增加的关键字看出来:signals、slots或emit。但是使用gcc编译时,编译器并不认识这些非标准c++的关键字,那么就需要Qt自己将扩展的关键字处理成标准的C++代码。Qt在编译之前会分析源文件,当发现包含了 Q_OBJECT 宏,则会生成另外一个标准的C++源文件,这个源文件中包含了 Q_OBJECT 宏的实现代码,这个源文件名字是将原文件名前面加上 moc_ 构成,这个新的文件同样将进入编译系统,终被链接到二进制代码中去,此时,Qt将自己增加的扩展转换成了标准的C++文件,moc 全称是 Meta-Object Compiler,也就是“元对象编译器”。这就是moc文件的由来。

下面我们来分析一下Moc文件:

一 示例代码如下:

#include

class CTestMoc : public QObject

{

Q_OBJECT

public:

CTestMoc(){}

~CTestMoc(){}

signals:

void Test1();

void Test2(int iTemp);

private slots:

void OnTest1();

void OnTest2(int iTemp);

};

二 Q_OBJECT宏

#define Q_OBJECT \

public: \

Q_OBJECT_CHECK \

static const QMetaObject staticMetaObject; \

virtual const QMetaObject *metaObject() const; \

virtual void *qt_metacast(const char *); \

QT_TR_FUNCTIONS \

virtual int qt_metacall(QMetaObject::Call, int, void **); \

private: \

Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \

struct QPrivateSignal {};

此宏在QObjectdefs.h头文件中定义

1 Q_OBJECT_CHECK 定义如下:

#define Q_OBJECT_CHECK \

template inline void qt_check_for_QOBJECT_macro(const ThisObject &_q_argument) const \

{ int i = qYouForgotTheQ_OBJECT_Macro(this, &_q_argument); i = i + 1; }

宏展开终会调用qYouForgotTheQ_OBJECT_Macro这个内联函数。这个函数始终返回0,但是很不明白,为什么之后还要添加一句 i=i?,刨根之后,发现Q_OBJECT_CHECK宏并没有做什么工作。

inline int qYouForgotTheQ_OBJECT_Macro(T, T) { return 0; }

2 static const QMetaObject staticMetaObject 静态的元对象,这个对象在moc文件里会构建,在那里就能看到整个信号&槽的全貌。

3 virtual const QMetaObject *metaObject() const; 返回一个元对象。

4 virtual void *qt_metacast(const char *); 元对象中的字符数据转换。

5 virtual int qt_metacall(QMetaObject::Call, int, void **); 元对象调用入口,注意此函数是public的,槽函数调用也是由这个函数开始。

6 static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); 由qt_metacall函数调用,槽函数调用真正处理函数。Q_DECL_HIDDEN_STATIC_METACALL这个宏看到后和linux系统有关,其它系统这个宏是一个空的宏。

三 Moc文件分析

先说结论在这里。

1 Qt的信号&槽之间的调用不是通过指针方式调用的而是通过索引方式来调用的.

2 信号也是一个函数。

Moc文件有几个重要数据结构,把这几个结构之间关系讲清楚大家就清楚Qt的信号槽机制是如何工作的了。

第一个结构是 qt_meta_stringdata_CTestMoc_t 定义如下:

struct qt_meta_stringdata_CTestMoc_t {

QByteArrayData data[7];

char stringdata[45];

};

data字段是一个由byte数组组成的数组,数组大小根据信号&槽个数有关,这个数组在调用QObject的connect函数时用来匹配信号名或槽名。

stringdata 存放的是字符资源,存放全部的信号名、槽名、类名。

static const qt_meta_stringdata_CTestMoc_t qt_meta_stringdata_CTestMoc = {

{

QT_MOC_LITERAL(0, 0, 8),

QT_MOC_LITERAL(1, 9, 5),

QT_MOC_LITERAL(2, 15, 0),

QT_MOC_LITERAL(3, 16, 5),

QT_MOC_LITERAL(4, 22, 5),

QT_MOC_LITERAL(5, 28, 7),

QT_MOC_LITERAL(6, 36, 7)

},

"CTestMoc\0Test1\0\0Test2\0iTemp\0OnTest1\0"

"OnTest2\0"

};

qt_meta_stringdata_CTestMoc 这个就是一个 qt_meta_stringdata_CTestMoc_t结构体的实例。

QT_MOC_LITERAL(0, 0, 8), 这个宏生成一个byte数组,第一参数是索引,大家可以看到索引是由 0 - 6 共7个组成,对应的是data字段的长度7,第二个参数是在stringdata字段中的开始位置,第三个参数是长度。

例如 QT_MOC_LITERAL(0, 0, 8) 索引是0, 开始位置是0, 长度是8,对应的字符是"CTestMoc",后面的以此类推。

第二个结构是 static const uint qt_meta_data_CTestMoc[]

这个结构体描述的是信号&槽在调用时的索引、参数、返回值等信息。

static const uint qt_meta_data_CTestMoc[] = {

// content:

7, // revision

0, // classname

0, 0, // classinfo

4, 14, // methods

0, 0, // properties

0, 0, // enums/sets

0, 0, // constructors

0, // flags

2, // signalCount

// signals: name, argc, parameters, tag, flags

1, 0, 34, 2, 0x06,

3, 1, 35, 2, 0x06,

// slots: name, argc, parameters, tag, flags

5, 0, 38, 2, 0x08,

6, 1, 39, 2, 0x08,

// signals: parameters

QMetaType::Void,

QMetaType::Void, QMetaType::Int, 4,

// slots: parameters

QMetaType::Void,

QMetaType::Void, QMetaType::Int, 4,

0 // eod

 };

这个数组的前14个uint 描述的是元对象的私有信息,定义在qmetaobject_p.h文件的QMetaObjectPrivate结构体当中,QMetaObjectPrivate结构体我们不做深入分析,但是,在这个结构体中4, 14, // methods这个信息描述的是信号&槽的个数和在表中的偏移量,即14个uint之后是信息&槽的信息

qt_meta_data_CTestMoc这个表中我们可以看到每描述一个信号或槽需要5个uint

例如,从表的第14个uint开始描述的信号信息

// signals: name, argc, parameters, tag, flags

1, 0, 34, 2, 0x06,

3, 1, 35, 2, 0x06,

name:对应的是qt_meta_stringdata_CTestMoc 索引,例如1 对应的是Test1

argc:参数个数

parameters : 参数的在qt_meta_data_CTestMoc这个表中的索引位置。

例如 // signals: parameters

QMetaType::Void,

QMetaType::Void, QMetaType::Int, 4,

void 是信号的返回值,QMetaType::Int是参数类型, 4 是参数名,在qt_meta_stringdata_CTestMoc中的索引值。

tag:这个字段的数值对应的是qt_meta_stringdata_CTestMoc 索引,在这个moc文件里对应的是一个空字符串,具体怎么用,在源代码里没看懂。

flags:是一个特征值,是在 enum MethodFlags 枚举中定义。

enum MethodFlags {

AccessPrivate = 0x00,

AccessProtected = 0x01,

AccessPublic = 0x02,

AccessMask = 0x03, //mask

MethodMethod = 0x00,

MethodSignal = 0x04,

MethodSlot = 0x08,

MethodConstructor = 0x0c,

MethodTypeMask = 0x0c,

MethodCompatibility = 0x10,

MethodCloned = 0x20,

MethodScriptable = 0x40,

MethodRevisioned = 0x80

};

大家可以看到,槽对应的是MethodSlot 0x08, 信号对应的是MethodSignal 和AccessPublic 相或。

第三部分 QObject 中静态函数 qt_static_metacall 实现

void CTestMoc::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)

{

if (_c == QMetaObject::InvokeMetaMethod) {

CTestMoc *_t = static_cast(_o);

switch (_id) {

case 0: _t->Test1(); break;

case 1: _t->Test2((*reinterpret_cast< int(*)>(_a[1]))); break;

case 2: _t->OnTest1(); break;

case 3: _t->OnTest2((*reinterpret_cast< int(*)>(_a[1]))); break;

default: ;

}

..........

}

现在看这个就比较直观了,qt_metacall方法通过索引调用其它内部方法。Qt动态机制不采用指针,而由索引实现。实际调用方法的工作由编译器实现。这使得信号和槽的机制执行效率比较高。

参数由一个指向指针数组的指针进行传递,并在调用方法时进行适当的转换。当然,使用指针是将不同类型的参数放在一个数组的唯一办法。参数索引从1开始,因为0号代表函数返回值。

第四部分 QObject 中静态staticMetaObject的赋值

const QMetaObject CTestMoc::staticMetaObject = {

{ &QObject::staticMetaObject, qt_meta_stringdata_CTestMoc.data,

qt_meta_data_CTestMoc, qt_static_metacall, 0, 0}

};

ok,通过这个静态变量就保存了moc文件的信号&槽的调用索引信息。在信号&槽绑定的时候就是通过这些信息一步一步建立的绑定关系。

第四部分 信号就是函数。

大家可以Moc文件中看到信号的实现。

上一篇:Android.mk分析

下一篇:ARM处理器中SWI异常中断处理程序的实现

热点文章推荐
华清学员就业榜单
高薪学员经验分享
热点新闻推荐
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2022 北京华清远见科技集团有限公司 版权所有 ,京ICP备16055225号-5京公海网安备11010802025203号

回到顶部