如果一个类的声明中包含Q_OBJECT宏,那么qmake将为这个类生成 meta信息,这个信息在前一篇中所提到的moc文件中。这一篇通过解析这个一个示例moc文件来阐述这些meta信息的存储方式和格式;本篇先说明了一 下QMetaObject的数据结构,然后呈现了一个简单的类TestObject类及其生成的moc文件,最后对这个moc文件个内容进行了详细解释。
QMetaObject的数据定义:
QMetaObject包含唯一的数据成员如下(见头文件qobjectdefs.h)
struct QMetaObject
{
private:
struct { // private data
const QMetaObject superdata; //父类QMetaObject实例的指针
const char stringdata; //一段字符串内存块,包含MetaObject信息之字符串信息
const uint data; //一段二级制内存块,包含MetaObject信息之二进制信息
const void extradata; //预留字段,暂未使用
} d;
}
QMetaObjectPrivate的数据定义:
QMetaObjectPrivate是QMetaObject的私有实现类,其数据定 义部分如下(见头文件qmetaobject_p.h)。该数据结构全是int类型,一些是直接的int型信息,比如classInfoCount、 methodCount等,还有一些是用于在QMetaObject的stringdata和data内存块中定位信息的索引值。下文结合这两个内存块的 结构再分析个字段的含义。
struct QMetaObjectPrivate
{
int revision;
int className;
int classInfoCount, classInfoData;
int methodCount, methodData;
int propertyCount, propertyData;
int enumeratorCount, enumeratorData;
int constructorCount, constructorData; //since revision 2
int flags; //since revision 3
int signalCount; //since revision
}
下文利用一个示例QObject子类及其moc文件,来分析QMetaObject的信息结构。
示例类TestObject:
TestObject类继承自QObject,定义了两个Property:propertyA,propertyB;两个classinfo:Author,Version;一个枚举:TestEnum。
示例类TestObject的moc文件:
qt_meta_data_TestObject::定义的正是QMetaObject::d.data指向的信息块;
qt_meta_stringdata_TestObject:定义的是QMetaObject::d.dataString指向的信息块;
const QMetaObject TestObject::staticMetaObject :定义TestObject类的MetaObject实例,从中可以看出QMetaObject各个字段是如何被赋值的;
const QMetaObject *TestObject::metaObject() const:重写了QObject::metaObject函数,返回上述的MetaObject实例指针。
TestObject::qt_metacall()是重写QObject的方法,依据传入的参数来调用signal&slot或访问property,动态方法调用属性访问正是依赖于这个方法,在第四篇中会再讲到该方法。
TestObject::clicked()和TestObject::pressed()正是对两个signal的实现,可见,signal其实就是一种方法,只不过这种方法由qt meta system来实现,不用我们自己实现。
TestObject类的所有meta信息就存储在 qt_meta_data_TestObject和qt_meta_stringdata_TestObject这两个静态数据中。 QMetaObject的接口的实现正是基于这两块数据。下面就对这两个数据进行分块说明。
static const uint qt_meta_data_TestObject[] = {
数据块一:
// content:
4, // revision
0, // classname
2, 14, // classinfo
4, 18, // methods
2, 38, // properties
1, 44, // enums/sets
0, 0, // constructors
0, // flags
2, // signalCount
这块数据可以被看做meta信息的头部,正好和QMetaObjectPrivate数据结构相对应,在QMetaObject的实现中,正是将这块数据映射为QMetaObjectPrivate进行使用的。
第一行数据"4":版本号;
第二行数据"0":类型名,该值是qt_meta_stringdata_TestObject的索引,qt_meta_stringdata_TestObject[0]这个字符串不正是类型名"TestObject"吗。
第三行数据"2,14",第一个表明有2个classinfo被定义,第二个是说具体的 classinfo信息在qt_meta_data_TestObject中的索引,qt_meta_data_TestObject[14]的位置两个 classinfo名值对的定义;
第四行数据"4,18",指明method的信息,模式同上;
第五行数据"2,38",指明property的信息,模式同上;
第六行数据"1,14",指明enum的信息,模式同上。
数据块二:
// classinfo: key, value
22, 11,
44, 29,
classinfo信息块。第一行"22,11",22表明 qt_meta_stringdata_TestObject[22]处定义的字符串是classinfo的key,11表明 qt_meta_stringdata_TestObject[11]处的字符串就是value。第二行"44,29"定义第二个classinfo。
数据块三:
// signals: signature, parameters, type, tag, flags
53, 52, 52, 52, 0x05,
63, 52, 52, 52, 0x05,
signal信息块。第一行"53, 52, 52, 52, 0x05"定义第一个signal clicked()。qt_meta_stringdata_TestObject[53]是signal名称字符串。parameters 52, type 52, tag 52, flags如何解释暂未知。
数据块四:
// slots: signature, parameters, type, tag, flags
73, 52, 52, 52, 0x0a,
91, 52, 52, 52, 0x0a,
slots信息,模式类似signal。
数据块五:
// properties: name, type, flags
113, 105, 0x0a095007,
123, 105, 0x0a095007,
property性信息,模式类signal和slots,105如何和type对应暂未知。
数据块六:
// enums: name, flags, count, data
133, 0x0, 2, 48,
// enum data: key, value
142, uint(TestObject::EnumValueA),
153, uint(TestObject::EnumValueB),
enum信息,第一行定义的是枚举名,flag,值的数目,data48不知是什么。
几行定义的是各枚举项的名称和值。名称同上都是qt_meta_stringdata_TestObject的索引值。
0 // eod
};
static const char qt_meta_stringdata_TestObject[] = {
这块数据就是meta信息所需的字符串。是一个字符串的序列。
"TestObject\0Long Huihu\0Author\0"
"TestObjectV1.0\0Version\0\0clicked()\0"
"pressed()\0onEventA(QString)\0onEventB(int)\0"
"QString\0propertyA\0propertyB\0TestEnum\0"
"EnumValueA\0EnumValueB\0"
};
可以看出,meta信息在moc文件中以静态数据的形式被定义,其排列有点类似可执行文件中静态数据信息的排布。
本篇从Qt MetaObject源代码解读相关接口的实现,这些接口都定义于qmetaobject.cpp中。
QMetaObject::className()
inline const char *QMetaObject::className() const
{ return d.stringdata; }
从前一篇可知,d.stringdata就是那块字符串数据,包含若干c字符串(以'\0')结尾。如果把d.stringdata当做一个c字符串指针的话,就是这个字符串序列的第一个字符串,正是类名。
QMetaObject::superClass()
inline const QMetaObject *QMetaObject::superClass() const
{ return d.superdata; }
QMetaObject::classInfoCount()
int QMetaObject::classInfoCount() const
{
int n = priv(d.data)->classInfoCount;
const QMetaObject *m = d.superdata;
while (m) {
n += priv(m->d.data)->classInfoCount;
m = m->d.superdata;
}
return n;
}
从代码可以看出,返回该类的所有classinfo数目,包括所有基类的。
函数priv是一个简单inline函数:
static inline const QMetaObjectPrivate priv(const uint data)
{ return reinterpret_cast(data); }
由前一篇可知,d.data指向的是那块二进制信息,priv将d.data解释为QMetaObjectPrivate。
QMetaObjectPrivate是QMetaObject的私有实现类,其数据定义部分如下(见头文件qmetaobject_p.h)。和前一篇的示例moc文件内容一对应,其含义一目了然。
struct QMetaObjectPrivate
{
int revision;
int className;
int classInfoCount, classInfoData;
int methodCount, methodData;
int propertyCount, propertyData;
int enumeratorCount, enumeratorData;
int constructorCount, constructorData; //since revision 2
int flags; //since revision 3
int signalCount; //since revision
}
QMetaObject:: classInfoOffset ()
int classInfoOffset () const
{
int offset = 0;
const QMetaObject *m = d.superdata;
while (m) {
offset += priv(m->d.data)->classInfoCount;
m = m->d.superdata;
}
return offset;
}
该类的含义是返回这个类所定义的classinfo的起始索引值,相当于它的祖先类所定义的classinfo的数量。
QMetaObject:: classInfo (int index)
QMetaClassInfo classInfo ( int index ) const
{
int i = index;
i -= classInfoOffset();
if (i < 0 && d.superdata)
return d.superdata->classInfo(index);
QMetaClassInfo result;
if (i >= 0 && i < priv(d.data)->classInfoCount) {
result.mobj = this;
result.handle = priv(d.data)->classInfoData + 2*i;
}
return result;
}
这个代码的流程比较简单。priv(d.data)->classInfoData是classinfo的信息在d.data中的偏移;每条 classinfo信息占2个UINT的大小,因此"priv(d.data)->classInfoData + 2*i"这个表达式的值就是第i个classinfo的信息在d.data中的偏移。
QMetaObject:: indexOfClassInfo ()
int indexOfClassInfo ( const char * name ) const
{
int i = -1;
const QMetaObject m = this;
while (m && i < 0) {
for (i = priv(m->d.data)->classInfoCount-1; i >= 0; --i)
if (strcmp(name, m->d.stringdata
+ m->d.data[priv(m->d.data)->classInfoData + 2i]) == 0) {
i += m->classInfoOffset();
break;
}
m = m->d.superdata;
}
return i;
}
按照继承层次,从下往上寻找名字为name的classinfo。
参考前一函数的解释,表达式m->d.data[priv(m->d.data)->classInfoData + 2i]的值是第i个classinfo信息的第一个32位值。该值是字符信息块d.stringdata中的索引值。因此 m->d.stringdata+ m->d.data[priv(m->d.data)->classInfoData + 2i]就是classinfo名称的字符串。
int constructorCount () const
int QMetaObject::constructorCount() const
{
if (priv(d.data)->revision < 2)
return 0;
return priv(d.data)->constructorCount;
}
QMetaMethod constructor ( int index ) const
QMetaMethod QMetaObject::constructor(int index) const
{
int i = index;
QMetaMethod result;
if (priv(d.data)->revision >= 2 && i >= 0 && i < priv(d.data)->constructorCount) {
result.mobj = this;
result.handle = priv(d.data)->constructorData + 5*i;
}
return result;
}
int indexOfConstructor ( const char * constructor ) const
int QMetaObject::indexOfConstructor(const char constructor) const
{
if (priv(d.data)->revision < 2)
return -1;
for (int i = priv(d.data)->constructorCount-1; i >= 0; --i) {
if (strcmp(constructor, d.stringdata
+ d.data[priv(d.data)->constructorData + 5i]) == 0) {
return i;
}
}
return -1;
}
int enumeratorCount () const
int enumeratorOffset () const
QMetaEnum enumerator ( int index ) const
int indexOfEnumerator ( const char * name ) const
这组函数与classinfo那一组的实现及其相似。
int methodCount () const 略;
int methodOffset () const 略;
QMetaMethod method ( int index ) const
{
int i = index;
i -= methodOffset();
if (i < 0 && d.superdata)
return d.superdata->method(index);
QMetaMethod result;
if (i >= 0 && i < priv(d.data)->methodCount) {
result.mobj = this;
result.handle = priv(d.data)->methodData + 5*i;
}
return result;
}
该函数的实现方式也一目了然。
int indexOfMethod ( const char * method ) const 略;
int indexOfSignal ( const char * signal ) const
{
const QMetaObject *m = this;
int i = QMetaObjectPrivate::indexOfSignalRelative(&m, signal);
if (i >= 0)
i += m->methodOffset();
return i;
}
int QMetaObjectPrivate::indexOfSignalRelative(const QMetaObject baseObject, const char signal)
{
int i = -1;
while (baseObject) {
const QMetaObject const m = baseObject;
for (i = priv(m->d.data)->methodCount-1; i >= 0; --i)
if ((m->d.data[priv(m->d.data)->methodData + 5i + 4] & MethodTypeMask) == MethodSignal
&& strcmp(signal, m->d.stringdata
+ m->d.data[priv(m->d.data)->methodData + 5i]) == 0) {
break;
}
if (i >= 0)
break;
*baseObject = m->d.superdata;
}
}
可以看出,查找signal的特别之处在于,通过method元数据的第五项来判断这是不是一个signal。
int indexOfSlot ( const char * slot ) const 略;
int propertyCount () const 略;
int propertyOffset () const 略;
int indexOfProperty ( const char * name ) const 略;
QMetaProperty property ( int index ) const
{
int i = index;
i -= propertyOffset();
if (i < 0 && d.superdata)
return d.superdata->property(index);
QMetaProperty result;
if (i >= 0 && i < priv(d.data)->propertyCount) {
int handle = priv(d.data)->propertyData + 3i;
int flags = d.data[handle + 2];
const char type = d.stringdata + d.data[handle + 1];
result.mobj = this;
result.handle = handle;
result.idx = i;
if (flags & EnumOrFlag) {
result.menum = enumerator(indexOfEnumerator(type));
if (!result.menum.isValid()) {
QByteArray enum_name = type;
QByteArray scope_name = d.stringdata;
int s = enum_name.lastIndexOf("::");
if (s > 0) {
scope_name = enum_name.left(s);
enum_name = enum_name.mid(s + 2);
}
const QMetaObject *scope = 0;
if (scope_name == "Qt")
scope = &QObject::staticQtMetaObject;
else
scope = QMetaObject_findMetaObject(this, scope_name);
if (scope)
result.menum = scope->enumerator(scope->indexOfEnumerator(enum_name));
}
}
}
return result;
}
该函数的特别之处在于,如果这个propery是一个枚举类型的话,就为返回值QMetaPropery赋上正确QMetaEnum属性值。
QMetaProperty userProperty () const
{
const int propCount = propertyCount();
for (int i = propCount - 1; i >= 0; --i) {
const QMetaProperty prop = property(i);
if (prop.isUser())
return prop;
}
return QMetaProperty();
}
从这个函数的实现来看,一个QObject应该只会有一个打开USER flag的property。
Original: https://www.cnblogs.com/zhoug2020/p/14376144.html
Author: 莫水千流
Title: Qt MetaObject 详解
相关阅读
Title: 王阳明心学语录
1 处朋友,务相下则得益,相上则损
译文:同朋友相处,一定要相互谦让,就会获得好处,而相互攀比互争高低,则只会受损。
2 曾国藩 :败人两物,非傲即惰
3 曾子曰:"吾日三省吾身——为人谋而不忠乎?与朋友交而不信乎?传不习乎?"
4 "只念念要存天理,即是立志。能不忘乎此,久则自然心中凝聚,犹道家所谓'节圣胎'也,此天理之存,驯至于美大神圣,亦只从此一念存养扩充去耳"
译文:王阳明认为只要时刻保佑并发扬存天理的意念,就能够达到精美、宏大、神圣的境界
5 陆程问:"静时亦觉意思好,才遇事便不同,如何?"
先生曰:"是徒知静养,而不用克己功夫也。如此,临事便要倾倒。人须在事上磨,方里得住,方能'静亦定、动亦定'"
6 "知者行之始,行者知之成。圣学只一个功夫,知行不可分作两事"
先生说:"认识是实践的起点,实践是认识的成果。圣人的学问只是一个功夫,认识和实践不能当作两回事"
7 "定者,心之本体,天理也。动静,所遇之时也"
译文:"定,恒定平静,是心的本体,也就是天理。动和静的变化,是天理在不同环境下的具体表现"
8 "精神、道德、言动、大率收敛为主,发散是不得已,天、地、人、物皆然"
译文: 先生说"精神、道德、言语、行动,大多以收敛为主,向外发散开来是不得已而为之。天、地、人、物都是这样"
9 "喜、怒、哀、乐本体自是中和的,才自家着些意思,便过不及,便是私"
译文: 先生说:"喜怒哀乐,其本体自然就是中正平和的,只是人本身有一些别的意念,就会过度或不足,便成了私欲"
10 "克己须要扫除廓清,一毫不存方是;有一毫在,则众恶相引而来。"
译文:先生说:"克己一定要彻底,应该不留存一丝一毫;有一毫的私念存在,那么众多的恶性便会接踵而至。"
11 曰仁云:"心犹镜也。圣人心如明镜,常人心如昏镜。近世格物之说如以镜照物,照上用功,不知镜伤昏在,何能照?先生格物如磨镜而使之明,磨上用功,明了后亦未废照。"
译文:徐爱说:"人心就像是镜子。圣人的心像明亮的镜子,而普通人的心像暗淡的镜子。近代朱熹的格物学说就像是用镜子照事物,但只会在照上用功,不晓得镜子本身海仍旧是暗淡的,这怎么可能会照清楚呢?先生的格物学说就像是在打磨镜子,使它变得明亮,把功夫下在打磨镜子上,镜子明亮后就不会影响照亮食物。"
12 王阳明认为圣道是没有精深、粗浅之分的,就像一间房子,本来只是一个样子,只是人的了解程度不一样罢了。——道无精粗,人之所见有精粗。
13 先生曰:"诸公近见时少疑问,何也?人不用功,莫不自以为己知为学,只循而行之是矣。殊不知私欲日生,如地上尘,一日不扫便有一层。着实用功便见道无终穷,愈探愈深,必使精白,无一毫不彻方可。"
译文:先生说:"最近见面,你们为什么都很少提问题呢?人若过不努力,就会自以为懂得了怎样做学问了,只会循着已知的方法做就行了。哪里知道私欲就好像地上的灰尘,会日日滋长,一天不去打扫就又多积一层。真正踏实用功的人认为圣道是不能够穷尽的,越探究越深奥,一定要做到精通明白,尽然透彻了才行。"
14 "善念发而知之,而充之;恶念发而知之,而遏之。知与充与遏者,志也,天聪明也。圣人只有此,学者当存此。"
译文:先生说:"认识到善念萌发便发展扩充它;认识到恶念萌发就努力遏止它。扩充善念、遏止恶念是心志的体现,也是上天赋予人的聪明才智。圣人是拥有这种聪明才智,而学者应当存养这种聪明才智。"
15 问:"孔门言志,由、求任政事,公西赤任礼乐,多少实用!及曾说来,却是耍的事,圣人却许它,是意如何?"
曰:"三子是有意必,有意必便偏著一遍,能此未必能彼。曾点这意思却无意必,便是'素其位而行,不愿乎其外,素夷狄行乎夷狄,素患难行乎患难,无入而不自得'矣。三子所谓'汝器也',曾带你便有'不器意。染三子之才,各卓然成章,非若世之空言无实者,故夫子亦皆许之。"
译文:
陆澄问:"孔子的门徒谈论他们的志向,子由和冉由想要担任政事,公西赤想要从事礼乐教化,多少有些致用的意思。但曾皙说起来像耍着玩一样,孔子反而赞许他,是什么意思呢?"
先生说:"前面三人的志向都是带着点主观的揣测、武断而又绝对,带有这样的倾向,便会偏执与一方面,能做这件事未必能做那件。曾皙的志向这种倾向,只不过是'在自己的条件下行事,身处夷狄,就做夷狄能做的事;身处患难,就做患难中能做的事,无论在哪,能依据情势,怡然自得'了。前面三人事孔子所说在单方面有才能的人,而曾皙事孔子所说在多个方面有才能的人。然而他们三人,各自才能卓著,而不是世间那些只讲讲而不实行的人,所以孔子也会赞许他们。"
16 "只存的此心常见在,便是学。过去未来事,思之何益?徒放心耳!"
"言语无序,亦足以见心之不存。"
译文:
先生说:"学习就是时时存养本心。过去和未来的事情,想了有什么用?徒然丧失了本心而已!"
先生说:"讲起话来语无伦次,也能够看出他并没有存养本心。"
17 问:"身之主为心,心之灵明是知,知之发动是意,意之所著哦位物。是如此否?
先生曰:"亦是。"
译文:
陆澄问:"身的主宰是心,心的灵明是认识,认识的起因是意念,意念的载体是事物。这么说对吗?"
先生说:"可以这么说。"
18 问格物。
先生曰:"格者,正也,正其不正以归于正也。"
问:"'知止'者,知至善只在吾心,元不在外野,而后志定。"
曰:"然。"
译文:
陆澄请教有关格物的学说。
先生说:"格,就是纠正。纠正不正确的使它归于正确。"
陆澄问:"'知止',就是明白至善原本不在心外,而只存在于我们心中,而后志向才能够安定。"
先生说:"是的。"
19 问:"格物于动处用功否?"
先生曰:"格物无间动静,静亦物也。孟子谓'必有事焉',是动静皆有事。"
译文:陆澄问:"格物是指在有所行动的时候用功吗?"
先生说:"格物没有动静之分,静的时候也是有事物存在的。孟子说'必有事焉',就是说不管动静都要用功。"
20 "功夫难处,全在格物致知上,此即诚意之事。意既诚,大段心亦正,身亦自修。但心正,修身功夫亦各有用力处。修身事已发边,正心是未发边。心正则中,身修则和。"
译文:先生说:"最难的功夫就是格物致知,这也就是之所以必须意诚的原因。意念真诚,基本上心旧自然中正,身自然也能够得到修养。但正心修身的功夫也各有侧重点。修身是在感情发出之后,正心是在感情未发之时。心正就是中正,身修就是平和。"
21 "自'格物'、'致知'至'平天下',只是一个'明明德'。虽亲民亦'明德'事也。'明德'是此心之德,即是仁。'仁者以天地万物为一体',始有一物失所,便是吾仁有未尽处。"
译文:先生说:"从'格物'、'致知'到'平天下',都是'明明德'。'亲民'也是'明明德'德事情。'明德'也就是本心的善,就是仁爱。' 仁者以天地万物为一体',假使对一件事感觉到失去,也就说明心中的人的还有不完善的地方。"
22 "只说'明明德'而不说'亲民',便似老、佛。"
译文:先生说:"只谈论'明明德'而不兼论'亲民'的事,就会累死佛道两家的学说。"
23 "至善者性也,性元无一毫之恶,故曰至善。止之,是复其本然而已。"
译文:先生说:"至善是仁的本性,本性原本是没有丝毫恶的,所以叫做至善。止至善,就是回复天性之本然而已。"
Original: https://www.cnblogs.com/mfryf/p/13092836.html
Author: 知识天地
Title: 王阳明心学语录