qt4.8.1源码下载5源码文件中的QMetaType结构体和QmetaType2结构体定义是不是有问题啊,问题看下图:

5403人阅读
Program(87)
http://blog.csdn.net/hqyhqyhq/article/details/8025566‘
QT 的信号和槽机制能十分方便的用来传输数据,但是如果数据种类比较多,分类比较多的时候,就需要更好地更高效的来传递数据的方法。以结构体作为参数是个很不错的选择。这几天写的程序正好需要以结构体来作为参数,但是网上搜的资料很少,讲的也不详细,我解决了问题后整理了一下,希望给有同样需求的同学一点帮助
1.首先是结构体的使用,需要使用Q_DECLARE_METATYPE宏
2.然后是把该结构体封装如一个QVariant
3.然后是对QVariant进行注册,因为信号和槽的参数类型并不认识QVariant
4.然后这个类中的信号就可以将QVariant作为参数了
5.接收类中,由于包含了发射类的头文件,所以不必再对结构体进行定义
&&& 这样就可以提取出容器内的结构体数据,并进行操作了,这对需要传输比较复杂的数据时效果比较好
error: ‘qt_metatype_id’ is not a member of ‘QMetaTypeId&QVariant&’
解决方案:
&&&& 多做修改,将结构体搬出类,而不是写在类public里面,可以写在同一个头文件中。
#include &QtGui/QApplication&
#include &QVariant&
structstruct1
&&&&doubleb;
structstruct2
&&&&struct1
Q_DECLARE_METATYPE(struct1)&
Q_DECLARE_METATYPE(struct2)
class Data()
&& public:
&&&&&&& int x,y;
int&main(int&argc,char&*argv[])
&&&&QApplication a(argc, argv);
&&&&struct1 v1 = {1, 2.0};
&&&&QVariant var1;
&&&&var1.setValue(v1);
&&&&if(var1.canConvert&struct1&())&&&
&&&&&&&&struct1 v11 = var1.value&struct1&();
&&&&struct2 v2 = {{2, 3.0}, 5};
&&&&QVariant var2;
&&&&if(var2.canConvert&struct2&())
&&&&&&&&var2.setValue(v2);
&&&&&&&&struct2 v22 = var2.value&struct2&();
&&&&returna.exec();
&&& 将结构体搬出类,而不是写在类public里面,可以写在同一个头文件中。这个注册函数暂时还没用到,看看宏定义,Q_DECLARE_METATYPE(),里面好像已近带了这个注册功能。
qRegisterMetaType&QVariant&(&QVariant&); //写在构造函数里
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1674463次
积分:14098
积分:14098
排名:第611名
原创:151篇
转载:371篇
评论:190条
(10)(5)(16)(8)(4)(3)(9)(2)(19)(16)(7)(8)(7)(13)(5)(10)(9)(12)(15)(21)(5)(14)(17)(9)(3)(3)(1)(2)(2)(2)(11)(16)(25)(9)(22)(20)(8)(14)(15)(16)(8)(18)(15)(13)(17)(10)(5)(2)(1)(1)(3)(5)(1)(2)(1)(7)(13)(4)(2)(1)(1)问题:如何让自定义的结构体和类高亮显示_codeblocks吧_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:9,305贴子:
问题:如何让自定义的结构体和类高亮显示收藏
就像下面这样图1
CodeBlocks中,自定义结构体不能高亮图2
VC安装VA插件后,自定义结构体可以高亮显示
创维OLED-S9D
我知道一个在CodeBlocks中让Node高亮的方法,方法是将Node关键词添加到“Setting-&Editor-&Syntax highlighting-&Keaywords”的关键词列表即可。不过这是个全局设定,且不会自动调整,如果不交Node,而叫NODE就不能高亮了。毕竟硬编码不是什么好方法。
同样的,自定义的函数名如果能高亮就好了,就像上面main那样
这个问题看起来好难之前在codeblocks forum上有人问过这个问题,看起来codeblocks现在不支持这个特性http:// forums.codeblocks.org / index.php?topic=13973.0这个帖子提到codelite有类似特性基本上就是要编程实现了文中提出这样的疑问But making the code more colorfull is a good idea?
百度有脑残自动删帖,无语啊“zhongguoren”!!!图1
百度自动删帖
登录百度帐号推荐应用
为兴趣而生,贴吧更懂你。或下次自动登录
现在的位置:
& 综合 & 正文
QT信号和槽以结构体为参数传递复杂数据
QT 的信号和槽机制能十分方便的用来传输数据,但是如果数据种类比较多,分类比较多的时候,就需要更好地更高效的来传递数据的方法。以结构体作为参数是个很不错的选择。这几天写的正好需要以结构体来作为参数,但是网上搜的资料很少,讲的也不详细,我解决了问题后整理了一下,希望给有同样需求的同学一点帮助
1.首先是结构体的使用,需要使用Q_DECLARE_METATYPE宏
struct DataStruct
QByteArray DstA
QByteArray ClusterId;
int DeviceE
int CommandID;
QByteArray AttributeID;
Q_DECLARE_METATYPE(DataStruct)
//这个宏具体的用法参考帮助文档
2.然后是把该结构体封装如一个QVariant
DataStruct askD
QVariant DataV
DataVar.setValue(askData);
3.然后是对QVariant进行注册,因为信号和槽的参数类型并不认识QVariant
qRegisterMetaType&QVariant&("QVariant"); //写在构造函数里
4.然后这个类中的信号就可以将QVariant作为参数了
void send_askData(QVariant dataVar);
5.接收类中,由于包含了发射类的头文件,所以不必再对结构体进行定义
connect(readThread,SIGNAL(send_askData(QVariant)),this,SLOT(AF_DATA_REQUEST(QVariant)));
在槽函数中
DataStruct askD
askData = dataVar.value&DataStruct&();
这样就可以提取出容器内的结构体数据,并进行操作了,这对需要传输比较复杂的数据时效果比较好
&&&&推荐文章:
【上篇】【下篇】Qt内部机制及逆向
标 题:Qt内部机制及逆向
作 者:zouzhin
时 间:<font color="#11-04-30 15:51:44 链 接:
【翻译】Qt内部机制及逆向
原作者:Daniel&Pistelli&;
翻&&&译:zouzhin
参加看雪有很长一段时间了,一直无所贡献,真是有愧各位同坛好友。前不久发了个Qt求助帖/showthread.php?t=132491,没人回复,刚好看到了国外牛人Daniel&Pistelli&写的《Qt&Internals&&&Reversing》,就翻译一下给需要的人做个参考。由于E文水平不高,有不对的地方多包涵,高手请直接看原文/files/qtrev.htm。
开始之前,简单补充介绍一下Qt:
用官网的话说:Qt一个开源跨平台应用程序和UI开发框架。优点有:
针对多个平台只需编写一次代码
使用&Qt&您只需编写一次应用程序和&UI,无须重新编写源代码,便可跨不同的桌面和嵌入式操作系统进行部署,既节省了时间又降低开发成本。
创建令人意想不到的用户体验
QQt&提供了应用程序生成块,包括庞大的可定制&widget&集合、图形画布、风格引擎和其他内容,您可用来生成新颖的用户界面。由于集成了&3D&图形、多媒体音频或视频、视觉效果、动画和自定义风格,使其在竞争中脱颖而出。
事半功倍(且倍道而进)
无论是使用全新的&Qt&Creator&跨平台&IDE&还是仅是&Qt&本身,Qt&都易学易用。而且由于有了&Qt&模块化的类库,您可以更多地关注创新,无须在平台本身编码上花费过多时间,这样就可将软件快速推向市场。
在单一应用程序中混合网络和本地代码
由于&Qt&集成了&WebKit&网络渲染引擎,&您可以快速地(查看混合方式)&将网络内容和服务集成到本地应用程序中,还可以利用网络环境提供您的服务和功能,让您的用户在使用过程中留下深刻印象。
用本文原作者的话:“in&my&opinion,&the&Qt&framework&will&be&used&more&and&more&by&software&developers”.个人也觉得Qt是有其优势,有兴趣的可以利用一下。
以下正式开始
(一)&&内部机制
我见过的最严谨的C++框架就是Qt框架,Qt将C++带入了一个新的高度。Qt引入的信号(signal)和槽(slot)技术很有创意,其中一点就是,一个对象可以不要声明就可以调用其它对象的方法。为了运作信号和槽,Qt采用了动态化机制(dynamism)。这种动态化机制可以由Qt框架自动实现,也可以由开发人员通过QMetaObject类手动实现。有关信号和槽的内容可以参考/4.7/signalsandslots.html。
我们看一个简单的信号和槽的例子:
#include&&QObject&
class&Counter&:&public&QObject
&&Q_OBJECT
&&Counter()&{&m_value&=&0;&};
&&int&value()&const&{&return&m_&};
public&slots:
&&void&setValue(int&value)
&&&&if&(value&!=&m_value)&
&&&&&&m_value&=&
&&&&&&emit&valueChanged(value);
&&void&valueChanged(int&newValue);
//&main.cpp
#include&&sas.h&
int&main(int&argc,&char&*argv[])
&&Counter&a,&b;
&&QObject::connect(&a,&SIGNAL(valueChanged(int)),
&&&&&b,&SLOT(setValue(int)));
&&a.setValue(12);&&&&&//&a.value()&==&12,&b.value()&==&12
&&b.setValue(48);&&&&&//&a.value()&==&12,&b.value()&==&48&
&&return&0;
SIGNAL和SLOT宏将括号中的内容封装成一个字符串,同时还附加一个ID号,如下所示:
#define&SLOT(a)&&&&&&&&&&&1&#a
#define&SIGNAL(a)&&&&&&&&&2&#a
所以,也可以直接这么写connect函数:
QObject::connect(&a,&&2valueChanged(int)&,&&b,&&1setValue(int)&);
signals和slots是Qt关键词,可以在头文件中找到,只用于Qt的元编译器(moc)。
#&if&defined(QT_NO_KEYWORDS)
#&&define&QT_NO_EMIT
#&&&define&slots
#&&&define&signals&protected
#&define&Q_SLOTS
#&define&Q_SIGNALS&protected
#&define&Q_PRIVATE_SLOT(d,&signature)
#&define&Q_EMIT
#ifndef&QT_NO_EMIT
#&define&emit
emit宏没什么需要解释的,signals宏有点不同,它限定Qt信号为protected方法,而slots宏可以是任意类型。
我们接下来看看Q_OBJECT宏:
/*&tmake&ignore&Q_OBJECT&*/
#define&Q_OBJECT_CHECK&\
&&&&template&&typename&T&&inline&void&qt_check_for_QOBJECT_macro(const&T&&_q_argument)&
&&&&{&int&i&=&qYouForgotTheQ_OBJECT_Macro(this,&&_q_argument);&i&=&i;&}
template&&typename&T&
inline&int&qYouForgotTheQ_OBJECT_Macro(T,&T)&{&return&0;&}
template&&typename&T1,&typename&T2&
inline&void&qYouForgotTheQ_OBJECT_Macro(T1,&T2)&{}
#endif&//&QT_NO_MEMBER_TEMPLATES
/*&tmake&ignore&Q_OBJECT&*/
#define&Q_OBJECT&\
&&&&Q_OBJECT_CHECK&\
&&&&static&const&QMetaObject&staticMetaO&\
&&&&virtual&const&QMetaObject&*metaObject()&&\
&&&&virtual&void&*qt_metacast(const&char&*);&\
&&&&QT_TR_FUNCTIONS&\
&&&&virtual&int&qt_metacall(QMetaObject::Call,&int,&void&**);&\
staticMetaObject是&QMetaObject对象,因为需要给属于同一类的全部实例共享,所以它是静态的。metaObject方法仅仅返回staticMetaObject。QT_TR_FUNCTIONS是一个用于所有tr函数的宏,用来实现多语言支持。qt_metacast用于按照类名或它的某个基类名进行动态转换(dynamic&cast)【Qt显然不依赖运行时类型检查(RTTI)】。qt_metacall通过索引调用内部信号和槽。以下是QMetaObject的声明:
struct&Q_CORE_EXPORT&QMetaObject
&&&&const&char&*className()&
&&&&const&QMetaObject&*superClass()&
&&&&QObject&*cast(QObject&*obj)&
#ifndef&QT_NO_TRANSLATION
&&&&//&###&Qt&4:&Merge&overloads
&&&&QString&tr(const&char&*s,&const&char&*c)&
&&&&QString&trUtf8(const&char&*s,&const&char&*c)&
&&&&QString&tr(const&char&*s,&const&char&*c,&int&n)&
&&&&QString&trUtf8(const&char&*s,&const&char&*c,&int&n)&
#endif&//&QT_NO_TRANSLATION
&&&&int&methodOffset()&
&&&&int&enumeratorOffset()&
&&&&int&propertyOffset()&
&&&&int&classInfoOffset()&
&&&&int&methodCount()&
&&&&int&enumeratorCount()&
&&&&int&propertyCount()&
&&&&int&classInfoCount()&
&&&&int&indexOfMethod(const&char&*method)&
&&&&int&indexOfSignal(const&char&*signal)&
&&&&int&indexOfSlot(const&char&*slot)&
&&&&int&indexOfEnumerator(const&char&*name)&
&&&&int&indexOfProperty(const&char&*name)&
&&&&int&indexOfClassInfo(const&char&*name)&
&&&&QMetaMethod&method(int&index)&
&&&&QMetaEnum&enumerator(int&index)&
&&&&QMetaProperty&property(int&index)&
&&&&QMetaClassInfo&classInfo(int&index)&
&&&&QMetaProperty&userProperty()&
&&&&static&bool&checkConnectArgs(const&char&*signal,&const&char&*method);
&&&&static&QByteArray&normalizedSignature(const&char&*method);
&&&&static&QByteArray&normalizedType(const&char&*type);
&&&&//&internal&index-based&connect
&&&&static&bool&connect(const&QObject&*sender,&int&signal_index,
&&&&&&&&&&&&&&&&&&&&&&&&const&QObject&*receiver,&int&method_index,
&&&&&&&&&&&&&&&&&&&&&&&&int&type&=&0,&int&*types&=&0);
&&&&//&internal&index-based&disconnect
&&&&static&bool&disconnect(const&QObject&*sender,&int&signal_index,
&&&&&&&&&&&&&&&&&&&&&&&&&&&const&QObject&*receiver,&int&method_index);
&&&&//&internal&slot-name&based&connect
&&&&static&void&connectSlotsByName(QObject&*o);
&&&&//&internal&index-based&signal&activation
&&&&static&void&activate(QObject&*sender,&int&signal_index,&void&**argv);
&&&&static&void&activate(QObject&*sender,&int&from_signal_index,&int&to_signal_index,&void&**argv);
&&&&static&void&activate(QObject&*sender,&const&QMetaObject&*,&int&local_signal_index,&void&**argv);
&&&&static&void&activate(QObject&*sender,&const&QMetaObject&*,&int&from_local_signal_index,&
&&&&&&int&to_local_signal_index,&void&
&&&&//&internal&guarded&pointers
&&&&static&void&addGuard(QObject&**ptr);
&&&&static&void&removeGuard(QObject&**ptr);
&&&&static&void&changeGuard(QObject&**ptr,&QObject&*o);
&&&&static&bool&invokeMethod(QObject&*obj,&const&char&*member,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Qt::ConnectionType,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&QGenericReturnArgument&ret,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&QGenericArgument&val0&=&QGenericArgument(0),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&QGenericArgument&val1&=&QGenericArgument(),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&QGenericArgument&val2&=&QGenericArgument(),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&QGenericArgument&val3&=&QGenericArgument(),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&QGenericArgument&val4&=&QGenericArgument(),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&QGenericArgument&val5&=&QGenericArgument(),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&QGenericArgument&val6&=&QGenericArgument(),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&QGenericArgument&val7&=&QGenericArgument(),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&QGenericArgument&val8&=&QGenericArgument(),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&QGenericArgument&val9&=&QGenericArgument());
&&&&//&[&...&several&invokeMethod&overloads&...]
&&&&enum&Call&{
&&&&&&&&InvokeMetaMethod,
&&&&&&&&ReadProperty,
&&&&&&&&WriteProperty,
&&&&&&&&ResetProperty,
&&&&&&&&QueryPropertyDesignable,
&&&&&&&&QueryPropertyScriptable,
&&&&&&&&QueryPropertyStored,
&&&&&&&&QueryPropertyEditable,
&&&&&&&&QueryPropertyUser
#ifdef&QT3_SUPPORT
&&&&QT3_SUPPORT&const&char&*superClassName()&
&&&&struct&{&//&private&data
&&&&&&&&const&QMetaObject&*
&&&&&&&&const&char&*
&&&&&&&&const&uint&*
&&&&&&&&const&QMetaObject&**
需要注意的是它的d结构。d结构的第一个成员是一个QMetaObject类指针,指向父Qt元数据类。用户设计的类可以从多个类派生,但只能拥有一个QObject(或从它派生)基类,这同时也是超类。看一个Qt对话框的例子,它经常从多个类派生:
class&ConvDialog&:&public&QDialog,&private&Ui::ConvDialog
&&&&Q_OBJECT
Moc将产生以下代码:
const&QMetaObject&ConvDialog::staticMetaObject&=&{
&&&&{&&QDialog::staticMetaObject,&qt_meta_stringdata_ConvDialog,
&&&&&&qt_meta_data_ConvDialog,&0&}
如果在QDialog前先继承Ui::ConvDialog,moc将会生成:
const&QMetaObject&ConvDialog::staticMetaObject&=&{
&&&&{&&Ui::ConvDialog::staticMetaObject,&qt_meta_stringdata_ConvDialog,
&&&&&&qt_meta_data_ConvDialog,&0&}
这是错误的,因为Ui::ConvDialog不是QObject的一个派生类,由此不拥有staticMetaObject成员,这样做只会导致一个编译错误。
d结构的第二个成员是一个字符数值,表示类的字面元数据。第三个成员是一个无符号整型数组,这个数组是一个表,包含所有元数据的偏移、特征等等。所以,如果你想枚举一个类的信号和槽,那就应该遍历这个表,通过偏移量从stringdata数组中获得方法名。它也涉及到属性(properties)和枚举类型(enums)。第四个成员是以null结尾的QMetaObject类数组,用来保存附加类的元数据信息,它一般由QMetaObject_findMetaObject函数引用。
static&const&QMetaObject&*QMetaObject_findMetaObject(const&QMetaObject&*self,&const&char&*name)
&&&&while&(self)&{
&&&&&&&&if&(strcmp(self-&d.stringdata,&name)&==&0)
&&&&&&&&&&&&return&
&&&&&&&&if&(self-&d.extradata)&{
&&&&&&&&&&&&const&QMetaObject&**e&=&self-&d.
&&&&&&&&&&&&while&(*e)&{
&&&&&&&&&&&&&&&&if&(const&QMetaObject&*m&=QMetaObject_findMetaObject((*e),&name))
&&&&&&&&&&&&&&&&&&&&return&m;
&&&&&&&&&&&&&&&&++e;
&&&&&&&&&&&&}
&&&&&&&&self&=&self-&d.
&&&&return&
这个函数只被property方法调用,property又被propertyCount,&propertyOffset&和&indexOfProperty调用。
下面是我们的Counter&类被moc生成的代码:
/****************************************************************************
**&Meta&object&code&from&reading&C++&file&'sas.h'
**&Created:&Mon&3.&Nov&15:20:11&2008
**&&&&&&by:&The&Qt&Meta&Object&Compiler&version&59&(Qt&4.4.3)
**&WARNING!&All&changes&made&in&this&file&will&be&lost!
*****************************************************************************/
#include&&../sas.h&
#if&!defined(Q_MOC_OUTPUT_REVISION)
#error&&The&header&file&'sas.h'&doesn't&include&&QObject&.&
#elif&Q_MOC_OUTPUT_REVISION&!=&59
#error&&This&file&was&generated&using&the&moc&from&4.4.3.&It&
#error&&cannot&be&used&with&the&include&files&from&this&version&of&Qt.&
#error&&(The&moc&has&changed&too&much.)&
QT_BEGIN_MOC_NAMESPACE
static&const&uint&qt_meta_data_Counter[]&=&{
&//&content:
&&&&&&&1,&&&&&&&//&revision
&&&&&&&0,&&&&&&&//&classname
&&&&&&&0,&&&&0,&//&classinfo
&&&&&&&2,&&&10,&//&methods
&&&&&&&0,&&&&0,&//&properties
&&&&&&&0,&&&&0,&//&enums/sets
&//&signals:&signature,&parameters,&type,&tag,&flags
&&&&&&18,&&&&9,&&&&8,&&&&8,&0x05,
&//&slots:&signature,&parameters,&type,&tag,&flags
&&&&&&42,&&&36,&&&&8,&&&&8,&0x0a,
&&&&&&&0&&&&&&&&//&eod
static&const&char&qt_meta_stringdata_Counter[]&=&{
&&&&&Counter\0\0newValue\0valueChanged(int)\0&
&&&&&value\0setValue(int)\0&
const&QMetaObject&Counter::staticMetaObject&=&{
&&&&{&&QObject::staticMetaObject,&qt_meta_stringdata_Counter,
&&&&&&qt_meta_data_Counter,&0&}
const&QMetaObject&*Counter::metaObject()&const
&&&&return&&staticMetaO
void&*Counter::qt_metacast(const&char&*_clname)
&&&&if&(!_clname)&return&0;
&&&&if&(!strcmp(_clname,&qt_meta_stringdata_Counter))
&&&&&&&&return&static_cast&void*&(const_cast&&Counter*&(this));
&&&&return&QObject::qt_metacast(_clname);
int&Counter::qt_metacall(QMetaObject::Call&_c,&int&_id,&void&**_a)
&&&&_id&=&QObject::qt_metacall(_c,&_id,&_a);
&&&&if&(_id&&&0)
&&&&&&&&return&_
&&&&if&(_c&==&QMetaObject::InvokeMetaMethod)&{
&&&&&&&&switch&(_id)&{
&&&&&&&&case&0:&valueChanged((*reinterpret_cast&&int(*)&(_a[1])));&
&&&&&&&&case&1:&setValue((*reinterpret_cast&&int(*)&(_a[1])));&
&&&&&&&&_id&-=&2;
&&&&return&_
//&SIGNAL&0
void&Counter::valueChanged(int&_t1)
&&&&void&*_a[]&=&{&0,&const_cast&void*&(reinterpret_cast&const&void&(&_t1))&};
&&&&QMetaObject::activate(this,&&staticMetaObject,&0,&_a);
QT_END_MOC_NAMESPACE
qt_metacall方法通过索引调用其它内部方法。Qt动态机制不采用指针,而由索引实现。实际调用方法的工作由编译器实现。这使得信号和槽的机制执行效率比较高。
参数由一个指向指针数组的指针进行传递,并在调用方法时进行适当的转换。当然,使用指针是将不同类型的参数放在一个数组的唯一办法。参数索引从1开始,因为0号代表函数返回值。我们这个例子中信号和槽被声明为void,所以没有值返回。如果需要返回数据,那么包含在switch中的代码将与下面代码类似:
&&&&if&(_c&==&QMetaObject::InvokeMetaMethod)&{
&&&&&&&&switch&(_id)&{
&&&&&&&&case&0:&valueChanged((*reinterpret_cast&&int(*)&(_a[1])));&
&&&&&&&&case&1:&setValue((*reinterpret_cast&&int(*)&(_a[1])));&
&&&&&&&&case&2:&{&int&_r&=&exampleMethod((*reinterpret_cast&&int(*)&(_a[1])));
&&&&&&&&&&&&if&(_a[0])&*reinterpret_cast&&int*&(_a[0])&=&_r;&}&&
另外一个需要关注的是valueChanged,它将调用QMetaObject的activate方法。
void&QMetaObject::activate(QObject&*sender,&int&from_signal_index,&int&to_signal_index,&void&**argv)
&&&&//&[...&other&code&...]
&&&&//&emit&signals&in&the&following&order:&from_signal_index&&=&signals&&=&to_signal_index,&signal&&&0
&&&&for&(int&signal&=&from_signal_
&&&&&&&&&(signal&&=&from_signal_index&&&&signal&&=&to_signal_index)&||&(signal&==&-2);
&&&&&&&&&(signal&==&to_signal_index&?&signal&=&-2&:&++signal))
&&&&&&&&if&(signal&&=&connectionLists-&count())&{
&&&&&&&&&&&&signal&=&to_signal_
&&&&&&&&&&&&
&&&&&&&&const&QObjectPrivate::ConnectionList&&connectionList&=&connectionLists-&at(signal);
&&&&&&&&int&count&=&connectionList.count();
&&&&&&&&for&(int&i&=&0;&i&&&&++i)&{
&&&&&&&&&&&&const&QObjectPrivate::Connection&*c&=&&connectionList[i];
&&&&&&&&&&&&if&(!c-&receiver)
&&&&&&&&&&&&&&&&
&&&&&&&&&&&&QObject&*&const&receiver&=&c-&
&&&&&&&&&&&&//&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,&*c,&argv);
&&&&&&&&&&&&&&&&
&&&&&&&&&&&&}&else&if&(c-&connectionType&==&Qt::BlockingQueuedConnection)&{
&&&&&&&&&&&&&&&&blocking_activate(sender,&signal,&*c,&argv);
&&&&&&&&&&&&&&&&
&&&&&&&&&&&&}
&&&&&&&&&&&&const&int&method&=&c-&
&&&&&&&&&&&&QObjectPrivate::Sender&currentS
&&&&&&&&&&&&currentSender.sender&=&
&&&&&&&&&&&&currentSender.signal&=&signal&&&0&?&from_signal_index&:&
&&&&&&&&&&&&QObjectPrivate::Sender&*&const&previousSender&=
&&&&&&&&&&&&&&&&QObjectPrivate::setCurrentSender(receiver,&&currentSender);
&&&&&&&&&&&&locker.unlock();
&&&&&&&&&&&&if&(qt_signal_spy_callback_set.slot_begin_callback&!=&0)&{
&&&&&&&&&&&&&&&&qt_signal_spy_callback_set.slot_begin_callback(receiver,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&method,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&argv&?&argv&:&empty_argv);
&&&&&&&&&&&&}
#if&defined(QT_NO_EXCEPTIONS)
&&&&&&&&&&&&receiver-&qt_metacall(QMetaObject::InvokeMetaMethod,&method,&argv&?&argv&:&empty_argv);
&&&&&&&&&&&&try&{
&&&&&&&&&&&&&&&&receiver-&qt_metacall(QMetaObject::InvokeMetaMethod,&method,&argv&?&argv&:&empty_argv);
&&&&&&&&&&&&}&catch&(...)&{
&&&&&&&&&&&&&&&&locker.relock();
&&&&&&&&&&&&&&&&QObjectPrivate::resetCurrentSender(receiver,&&currentSender,&previousSender);
&&&&&&&&&&&&&&&&--connectionLists-&inU
&&&&&&&&&&&&&&&&Q_ASSERT(connectionLists-&inUse&&=&0);
&&&&&&&&&&&&&&&&if&(connectionLists-&orphaned&&&&!connectionLists-&inUse)
&&&&&&&&&&&&&&&&&&&&delete&connectionL
&&&&&&&&&&&&&&&&
&&&&&&&&&&&&}
&&&&&&&&&&&&locker.relock();
&&&&&&&&&&&&if&(qt_signal_spy_callback_set.slot_end_callback&!=&0)
&&&&&&&&&&&&&&&&qt_signal_spy_callback_set.slot_end_callback(receiver,&method);
&&&&&&&&&&&&QObjectPrivate::resetCurrentSender(receiver,&&currentSender,&previousSender);
&&&&&&&&&&&&if&(connectionLists-&orphaned)
&&&&&&&&&&&&&&&&
&&&&&&&&if&(connectionLists-&orphaned)
&&&&&&&&&&&&
&&&&--connectionLists-&inU
&&&&Q_ASSERT(connectionLists-&inUse&&=&0);
&&&&if&(connectionLists-&orphaned&&&&!connectionLists-&inUse)
&&&&&&&&delete&connectionL
&&&&locker.unlock();
&&&&if&(qt_signal_spy_callback_set.signal_end_callback&!=&0)
&&&&&&&&qt_signal_spy_callback_set.signal_end_callback(sender,&from_signal_index);
这个方法需要做很多工作,包括检查当前的事件是需要立即处理还是先放进事件序列中,并分别调用不同的activate方法,而后继续处理ConnectionList中的下一个事件。如果事件需要立即处理,首先从事件中获得将要调用的方法的ID,从而调用接收方的qt_metacall。过程如下:
const&QObjectPrivate::ConnectionList&&connectionList&=&connectionLists-&at(signal);
int&count&=&connectionList.count();
for&(int&i&=&0;&i&&&&++i)&{
&&const&QObjectPrivate::Connection&*c&=&&connectionList[i];
&&QObject&*&const&receiver&=&c-&
&&const&int&method&=&c-&
&&receiver-&qt_metacall(QMetaObject::InvokeMetaMethod,&method,&argv&?&argv&:&empty_argv);
从中我们可以明白信号和槽的内部机制:当调用connect函数时,信号和槽的签名转换成ID,然后储存在Connection类中。每次一个信号发出后,与此信号ID相关的事件即被确定,同时对应的槽被调用。
至于动态调用(dynamic&invokes),主要通过QMetaObject类提供的invokeMethod方法实现。这个方法不同于信号和槽,它需要从它的返回类型、名字和参数类型构造一个签名,然后查找元数据来得到其ID,最后调用对象的qt_metacall方法。
bool&QMetaObject::invokeMethod(QObject&*obj,&const&char&*member,&Qt::ConnectionType&type,
&&&&&&&&&&&&&&&&&QGenericReturnArgument&ret,
&&&&&&&&&&&&&&&&&QGenericArgument&val0,
&&&&&&&&&&&&&&&&&QGenericArgument&val1,
&&&&&&&&&&&&&&&&&QGenericArgument&val2,
&&&&&&&&&&&&&&&&&QGenericArgument&val3,
&&&&&&&&&&&&&&&&&QGenericArgument&val4,
&&&&&&&&&&&&&&&&&QGenericArgument&val5,
&&&&&&&&&&&&&&&&&QGenericArgument&val6,
&&&&&&&&&&&&&&&&&QGenericArgument&val7,
&&&&&&&&&&&&&&&&&QGenericArgument&val8,
&&&&&&&&&&&&&&&&&QGenericArgument&val9)
&&&&if&(!obj)
&&&&&&&&return&
&&&&QVarLengthArray&char,&512&&
&&&&int&len&=&qstrlen(member);
&&&&if&(len&&=&0)
&&&&&&&&return&
&&&&sig.append(member,&len);
&&&&sig.append('(');
&&&&enum&{&MaximumParamCount&=&11&};
&&&&const&char&*typeNames[]&=&{ret.name(),&val0.name(),&val1.name(),&val2.name(),&val3.name(),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&val4.name(),&val5.name(),&val6.name(),&val7.name(),&val8.name(),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&val9.name()};
&&&&int&paramC
&&&&for&(paramCount&=&1;&paramCount&&&MaximumParamC&++paramCount)&{
&&&&&&&&len&=&qstrlen(typeNames[paramCount]);
&&&&&&&&if&(len&&=&0)
&&&&&&&&&&&&
&&&&&&&&sig.append(typeNames[paramCount],&len);
&&&&&&&&sig.append(',');
&&&&if&(paramCount&==&1)
&&&&&&&&sig.append(')');&//&no&parameters
&&&&&&&&sig[sig.size()&-&1]&=&')';
&&&&sig.append('\0');
&&&&int&idx&=&obj-&metaObject()-&indexOfMethod(sig.constData());
&&&&if&(idx&&&0)&{
&&&&&&&&QByteArray&norm&=&QMetaObject::normalizedSignature(sig.constData());
&&&&&&&&idx&=&obj-&metaObject()-&indexOfMethod(norm.constData());
&&&&if&(idx&&&0)
&&&&&&&&return&
&&&&//&check&return&type
&&&&if&(ret.data())&{
&&&&&&&&const&char&*retType&=&obj-&metaObject()-&method(idx).typeName();
&&&&&&&&if&(qstrcmp(ret.name(),&retType)&!=&0)&{
&&&&&&&&&&&&//&normalize&the&return&value&as&well
&&&&&&&&&&&&//&the&trick&here&is&to&make&a&function&signature&out&of&the&return&type
&&&&&&&&&&&&//&so&that&we&can&call&normalizedSignature()&and&avoid&duplicating&code
&&&&&&&&&&&&QByteArray&
&&&&&&&&&&&&int&len&=&qstrlen(ret.name());
&&&&&&&&&&&&unnormalized.reserve(len&+&3);
&&&&&&&&&&&&unnormalized&=&&_(&;&&&&&&&&//&the&function&is&called&&_&
&&&&&&&&&&&&unnormalized.append(ret.name());
&&&&&&&&&&&&unnormalized.append(')');
&&&&&&&&&&&&QByteArray&normalized&=&QMetaObject::normalizedSignature(unnormalized.constData());
&&&&&&&&&&&&normalized.truncate(normalized.length()&-&1);&//&drop&the&ending&')'
&&&&&&&&&&&&if&(qstrcmp(normalized.constData()&+&2,&retType)&!=&0)
&&&&&&&&&&&&&&&&return&
&&&&void&*param[]&=&{ret.data(),&val0.data(),&val1.data(),&val2.data(),&val3.data(),&val4.data(),
&&&&&&&&&&&&&&&&&&&&&val5.data(),&val6.data(),&val7.data(),&val8.data(),&val9.data()};
&&&&if&(type&==&Qt::AutoConnection)&{
&&&&&&&&type&=&QThread::currentThread()&==&obj-&thread()
&&&&&&&&&&&&&&&?&Qt::DirectConnection
&&&&&&&&&&&&&&&:&Qt::QueuedC
&&&&if&(type&==&Qt::DirectConnection)&{
&&&&&&&&return&obj-&qt_metacall(QMetaObject::InvokeMetaMethod,&idx,&param)&&&0;
&&&&}&else&{
&&&&&&&&if&(ret.data())&{
&&&&&&&&&&&&qWarning(&QMetaObject::invokeMethod:&Unable&to&invoke&methods&with&return&values&in&queued&&
&&&&&&&&&&&&&&&&&&&&&&connections&);
&&&&&&&&&&&&return&
&&&&&&&&int&nargs&=&1;&//&include&return&type
&&&&&&&&void&**args&=&(void&**)&qMalloc(paramCount&*&sizeof(void&*));
&&&&&&&&int&*types&=&(int&*)&qMalloc(paramCount&*&sizeof(int));
&&&&&&&&types[0]&=&0;&//&return&type
&&&&&&&&args[0]&=&0;
&&&&&&&&for&(int&i&=&1;&i&&&paramC&++i)&{
&&&&&&&&&&&&types[i]&=&QMetaType::type(typeNames[i]);
&&&&&&&&&&&&if&(types[i])&{
&&&&&&&&&&&&&&&&args[i]&=&QMetaType::construct(types[i],&param[i]);
&&&&&&&&&&&&&&&&++
&&&&&&&&&&&&}&else&if&(param[i])&{
&&&&&&&&&&&&&&&&qWarning(&QMetaObject::invokeMethod:&Unable&to&handle&unregistered&datatype&'%s'&,
&&&&&&&&&&&&&&&&&&&&&&&&&typeNames[i]);
&&&&&&&&&&&&&&&&for&(int&x&=&1;&x&&&i;&++x)&{
&&&&&&&&&&&&&&&&&&&&if&(types[x]&&&&args[x])
&&&&&&&&&&&&&&&&&&&&&&&&QMetaType::destroy(types[x],&args[x]);
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&qFree(types);
&&&&&&&&&&&&&&&&qFree(args);
&&&&&&&&&&&&&&&&return&
&&&&&&&&&&&&}
&&&&&&&&if&(type&==&Qt::QueuedConnection)&{
&&&&&&&&&&&&QCoreApplication::postEvent(obj,&new&QMetaCallEvent(idx,&0,&-1,&nargs,&types,&args));
&&&&&&&&}&else&{
&&&&&&&&&&&&if&(QThread::currentThread()&==&obj-&thread())&{
&&&&&&&&&&&&&&&&qWarning(&QMetaObject::invokeMethod:&Dead&lock&detected&in&BlockingQueuedConnection:&&
&&&&&&&&&&&&&&&&&&&&&&&&&&Receiver&is&%s(%p)&,
&&&&&&&&&&&&&&&&&&&&&&&&&obj-&metaObject()-&className(),&obj);
&&&&&&&&&&&&}
&&&&&&&&&&&&//&blocking&queued&connection
#ifdef&QT_NO_THREAD
&&&&&&&&&&&&QCoreApplication::postEvent(obj,&new&QMetaCallEvent(idx,&0,&-1,&nargs,&types,&args));
&&&&&&&&&&&&QSemaphore&
&&&&&&&&&&&&QCoreApplication::postEvent(obj,&new&QMetaCallEvent(idx,&0,&-1,&nargs,&types,&args,&&semaphore));
&&&&&&&&&&&&semaphore.acquire();
#endif&//&QT_NO_THREAD
&&&&return&
方法的ID由indexOfMethod返回。如果方法的签名不能创建,invokeMethod返回false。
(二)&&逆向
逆向时我们需要使用Qt提供的元数据。先看看元数据表:
QT_BEGIN_MOC_NAMESPACE
static&const&uint&qt_meta_data_Counter[]&=&{
&//&content:
&&&&&&&1,&&&&&&&//&revision
&&&&&&&0,&&&&&&&//&classname
&&&&&&&0,&&&&0,&//&classinfo
&&&&&&&2,&&&10,&//&methods
&&&&&&&0,&&&&0,&//&properties
&&&&&&&0,&&&&0,&//&enums/sets
&//&signals:&signature,&parameters,&type,&tag,&flags
&&&&&&18,&&&&9,&&&&8,&&&&8,&0x05,
&//&slots:&signature,&parameters,&type,&tag,&flags
&&&&&&42,&&&36,&&&&8,&&&&8,&0x0a,
&&&&&&&0&&&&&&&&//&eod
这个表不仅仅告诉了我们方法的数量,同时也给出了方法的偏移。下面是此结构的C++头:
struct&QMetaObjectPrivate
&&&&int&classN
&&&&int&classInfoCount,&classInfoD
&&&&int&methodCount,&methodD
&&&&int&propertyCount,&propertyD
&&&&int&enumeratorCount,&enumeratorD
我们已经知道了方法的数量和偏移,接下来就是分析方法本身的数据。这个数据可以分为五个整数,其意义分别是:签名、参数、类型、标签、特征。签名(signature)段是一个字符串数据中的偏移,对应valueChanged(int)方法的部分声明(不包含返回类型)。参数(parameters)段是对应参数名的偏移。我们例子中的名字就是'newValue'。名字用逗号分开,所以,如果我们的槽有两个参数,那么会显示为'newValue1,newValue2'。类型(type)段对应方法的返回类型。如果像我们例子中一样为空字符串,那么可以确定方法是void类型。标签(tag)段暂时不用(Tags&are&special&macros&recognized&by&moc&that&make&it&possible&to&add&extra&information&about&a&method.&For&the&moment,&moc&doesn't&support&any&special&tags.)。最后一个段特征(flags)不是一个偏移量。特征值有:
enum&MethodFlags&&{
&&&&AccessPrivate&=&0x00,
&&&&AccessProtected&=&0x01,
&&&&AccessPublic&=&0x02,
&&&&AccessMask&=&0x03,&//mask
&&&&MethodMethod&=&0x00,
&&&&MethodSignal&=&0x04,
&&&&MethodSlot&=&0x08,
&&&&MethodTypeMask&=&0x0c,
&&&&MethodCompatibility&=&0x10,
&&&&MethodCloned&=&0x20,
&&&&MethodScriptable&=&0x40
关于方法的知识就这些,下面我们研究枚举和属性。我们先在代码中加入这两类代码:
class&Counter&:&public&QObject
&&Q_OBJECT
&&Q_PROPERTY(Priority&priority&READ&priority&WRITE&setPriority)
&&&&Q_ENUMS(Priority)
&&Counter()&{&m_value&=&0;&};
&&enum&Priority&{&High,&Low,&VeryHigh,&VeryLow&};
&&&&&void&setPriority(Priority&priority)&{&m_priority&=&&};
&&&&&Priority&priority()&const&{&return&m_&};
&&int&value()&const&{&return&m_&};
public&slots:
&&&&void&setValue(int&value)
&&&&&&if&(value&!=&m_value)&
&&&&&&&&m_value&=&
&&&&&&&&emit&valueChanged(value);
&&&&void&valueChanged(int&newValue);
&&Priority&m_
这时moc会生成:
static&const&uint&qt_meta_data_Counter[]&=&{
&//&content:
&&&&&&&1,&&&&&&&//&revision
&&&&&&&0,&&&&&&&//&classname
&&&&&&&0,&&&&0,&//&classinfo
&&&&&&&2,&&&10,&//&methods
&&&&&&&1,&&&20,&//&properties
&&&&&&&1,&&&23,&//&enums/sets
&//&signals:&signature,&parameters,&type,&tag,&flags
&&&&&&18,&&&&9,&&&&8,&&&&8,&0x05,
&//&slots:&signature,&parameters,&type,&tag,&flags
&&&&&&42,&&&36,&&&&8,&&&&8,&0x0a,
&//&properties:&name,&type,&flags
&&&&&&65,&&&56,&0x0009510b,
&//&enums:&name,&flags,&count,&data
&&&&&&56,&0x0,&&&&4,&&&27,
&//&enum&data:&key,&value
&&&&&&74,&uint(Counter::High),
&&&&&&79,&uint(Counter::Low),
&&&&&&83,&uint(Counter::VeryHigh),
&&&&&&92,&uint(Counter::VeryLow),
&&&&&&&0&&&&&&&&//&eod
static&const&char&qt_meta_stringdata_Counter[]&=&{
&&&&&Counter\0\0newValue\0valueChanged(int)\0&
&&&&&value\0setValue(int)\0Priority\0priority\0&
&&&&&High\0Low\0VeryHigh\0VeryLow\0&
同样,我们也可以得到属性和枚举的数量和偏移。对属性而言,包含3个整数:名字(name)、类型(type)和特征(flags)。类型对应的是属性的类型,我们例子中就是Priority。特征值可分为:
enum&PropertyFlags&&{
&&&&Invalid&=&0x,
&&&&Readable&=&0x,
&&&&Writable&=&0x,
&&&&Resettable&=&0x,
&&&&EnumOrFlag&=&0x,
&&&&StdCppSet&=&0x,
//&&&&Override&=&0x,
&&&&Designable&=&0x,
&&&&ResolveDesignable&=&0x,
&&&&Scriptable&=&0x,
&&&&ResolveScriptable&=&0x,
&&&&Stored&=&0x,
&&&&ResolveStored&=&0x,
&&&&Editable&=&0x,
&&&&ResolveEditable&=&0x,
&&&&User&=&0x,
&&&&ResolveUser&=&0x
对于枚举,有名字(name)、特征(flags)、数量(count)和数据(data)。特征段没有使用。数量段表示枚举中条目的数量。数据段是一个元数据表的偏移,指向枚举中的条目。每个条目由两个整数表示:key和value。Key指向当前条目的名字,value是此条目的实际值。
为了获得二进制文件的元数据信息,作者特意用Python写了一个IDA脚本,可以从一个Q_OBJECT类中提取出方法、属性和枚举。代码如下:
#&---------------------------------------------
#&MetaData&Parser&Class
#&---------------------------------------------
#&change&for&64&bit&exes
b64bit&=&False
#&i'm&assuming&that&the&exe&is&Little&Endian
#&the&external&methods&used&by&this&class&are&Byte(addr)&and&Dword(addr)
def&AddressSize():
&&&&if&b64bit&==&True:
&&&&&&&&return&8
&&&&return&4
def&ReadAddress(addr):
&&&&if&b64bit&==&True:
&&&&&&&&return&(Dword(addr+4)&&&&32)&|&Dword(addr)
&&&&return&Dword(addr)
class&MetaParser:
&&&&def&__init__(self,&stringsaddr,&tableaddr):
&&&&&&&&self.MetaStrings&=&stringsaddr
&&&&&&&&self.MetaTable&=&tableaddr
&&&&def&ReadString(self,&addr):
&&&&&&&&c&=&addr
&&&&&&&&b&=&Byte(c)
&&&&&&&&str&=&&&
&&&&&&&&while&b&!=&0:
&&&&&&&&&&&&#&set&a&limit,&just&in&case
&&&&&&&&&&&&if&(c&-&addr)&&&1000:
&&&&&&&&&&&&&&&&return&&error&
&&&&&&&&&&&&str&+=&chr(b)
&&&&&&&&&&&&c&+=&1
&&&&&&&&&&&&b&=&Byte(c)
&&&&&&&&return&str
&&&&#&read&metadata
&&&&struct&QMetaObjectPrivate
&&&&&&&&int&
&&&&&&&&int&classN
&&&&&&&&int&classInfoCount,&classInfoD
&&&&&&&&int&methodCount,&methodD
&&&&&&&&int&propertyCount,&propertyD
&&&&&&&&int&enumeratorCount,&enumeratorD
&&&&#&---------------------------------------------
&&&&#&enums&(quick&way&to&convert&them&to&python)
&&&&#&---------------------------------------------
&&&&class&Enum:
&&&&&&&&def&__init__(self,&**entries):&self.__dict__.update(entries)
&&&&MethodFlags&=&Enum(&&&&&&&&&&&&&\
&&&&&&&&AccessPrivate&=&0x00,&&&&&&&\
&&&&&&&&AccessProtected&=&0x01,&&&&&\
&&&&&&&&AccessPublic&=&0x02,&&&&&&&&\
&&&&&&&&AccessMask&=&0x03,&&&&&&&&&&\
&&&&&&&&MethodMethod&=&0x00,&&&&&&&&\
&&&&&&&&MethodSignal&=&0x04,&&&&&&&&\
&&&&&&&&MethodSlot&=&0x08,&&&&&&&&&&\
&&&&&&&&MethodTypeMask&=&0x0c,&&&&&&\
&&&&&&&&MethodCompatibility&=&0x10,&\
&&&&&&&&MethodCloned&=&0x20,&&&&&&&&\
&&&&&&&&MethodScriptable&=&0x40)
&&&&PropertyFlags&=&Enum(&&&&&&&&&&&&&&&\
&&&&&&&&Invalid&=&0x,&&&&&&&&&&&\
&&&&&&&&Readable&=&0x,&&&&&&&&&&\
&&&&&&&&Writable&=&0x,&&&&&&&&&&\
&&&&&&&&Resettable&=&0x,&&&&&&&&\
&&&&&&&&EnumOrFlag&=&0x,&&&&&&&&\
&&&&&&&&StdCppSet&=&0x,&&&&&&&&&\
&&&&&&&&Designable&=&0x,&&&&&&&&\
&&&&&&&&ResolveDesignable&=&0x,&\
&&&&&&&&Scriptable&=&0x,&&&&&&&&\
&&&&&&&&ResolveScriptable&=&0x,&\
&&&&&&&&Stored&=&0x,&&&&&&&&&&&&\
&&&&&&&&ResolveStored&=&0x,&&&&&\
&&&&&&&&Editable&=&0x,&&&&&&&&&&\
&&&&&&&&ResolveEditable&=&0x,&&&\
&&&&&&&&User&=&0x,&&&&&&&&&&&&&&\
&&&&&&&&ResolveUser&=&0x)
&&&&#&---------------------------------------------
&&&&#&methods
&&&&#&---------------------------------------------
&&&&def&GetClassName(self):
&&&&&&&&stringaddr&=&Dword(self.MetaTable&+&4)&+&self.MetaStrings
&&&&&&&&return&self.ReadString(stringaddr)
&&&&def&GetMethodNumber(self):
&&&&&&&&return&Dword(self.MetaTable&+&16)
&&&&def&GetMethodSignature(self,&method_index):
&&&&&&&&if&method_index&&=&self.GetMethodNumber():
&&&&&&&&&&&&return&&error:&method&index&out&of&range&
&&&&&&&&method_offset&=&(Dword(self.MetaTable&+&20)&*&4)&+&(method_index&*&(5&*&4))
&&&&&&&&#&get&accessibility
&&&&&&&&access_flags&=&self.GetMethodAccess(method_index)
&&&&&&&&access_type&=&&private:&&&&
&&&&&&&&if&access_flags&==&self.MethodFlags.AccessProtected:
&&&&&&&&&&&&access_type&=&&protected:&&
&&&&&&&&elif&access_flags&==&self.MethodFlags.AccessPublic:
&&&&&&&&&&&&access_type&=&&public:&&&&&
&&&&&&&&#&read&return&type
&&&&&&&&rettype&=&self.ReadString(Dword(self.MetaTable&+&method_offset&+&8)&+&self.MetaStrings)
&&&&&&&&if&rettype&==&&&:
&&&&&&&&&&&&rettype&=&&void&
&&&&&&&&#&read&partial&signature
&&&&&&&&psign&=&self.ReadString(Dword(self.MetaTable&+&method_offset)&+&self.MetaStrings)
&&&&&&&&#&retrieve&argument&types
&&&&&&&&par_index&=&psign.find(&(&)
&&&&&&&&arg_types&=&psign[(par_index&+&1):(len(psign)&-&1)].split(&,&)
&&&&&&&&#&read&argument&names
&&&&&&&&arg_names&=&self.ReadString(Dword(self.MetaTable&+&method_offset&+&4)&\
&&&&&&&&&&&&+&self.MetaStrings).split(&,&)
&&&&&&&&#&if&argument&types&and&names&are&not&the&same&number,
&&&&&&&&#&then&show&signature&without&argument&names
&&&&&&&&if&len(arg_types)&!=&len(arg_names):
&&&&&&&&&&&&return&access_type&+&rettype&+&&&&&+&psign
&&&&&&&&#&build&signatrue&with&argument&names
&&&&&&&&ntypes&=&len(arg_types)
&&&&&&&&x&=&0
&&&&&&&&args&=&&&
&&&&&&&&while&x&&&ntypes:
&&&&&&&&&&&&if&x&!=&0:
&&&&&&&&&&&&&&&&args&+=&&,&&
&&&&&&&&&&&&if&arg_types[x]&==&&&:
&&&&&&&&&&&&&&&&args&+=&arg_names[x]
&&&&&&&&&&&&elif&arg_names[x]&==&&&:
&&&&&&&&&&&&&&&&args&+=&arg_types[x]
&&&&&&&&&&&&else:
&&&&&&&&&&&&&&&&args&+=&(arg_types[x]&+&&&&&+&arg_names[x])
&&&&&&&&&&&&#&increment&loop
&&&&&&&&&&&&x&+=&1
&&&&&&&&return&access_type&+&rettype&+&&&&&+&psign[0:(par_index&+&1)]&+&args&+&&)&
&&&&def&GetMethodFlags(self,&method_index):
&&&&&&&&if&method_index&&=&self.GetMethodNumber():
&&&&&&&&&&&&return&-1
&&&&&&&&method_offset&=&(Dword(self.MetaTable&+&20)&*&4)&+&(method_index&*&(5&*&4))
&&&&&&&&return&Dword(self.MetaTable&+&method_offset&+&16)
&&&&def&GetMethodType(self,&method_index):
&&&&&&&&return&self.GetMethodFlags(method_index)&&&self.MethodFlags.MethodTypeMask
&&&&def&GetMethodAccess(self,&method_index):
&&&&&&&&return&self.GetMethodFlags(method_index)&&&self.MethodFlags.AccessMask
&&&&def&GetPropertyNumber(self):
&&&&&&&&return&Dword(self.MetaTable&+&24)
&&&&def&GetPropertyDecl(self,&property_index):
&&&&&&&&if&property_index&&=&self.GetPropertyNumber():
&&&&&&&&&&&&return&&error:&property&index&out&of&range&
&&&&&&&&property_offset&=&(Dword(self.MetaTable&+&28)&*&4)&+&(property_index&*&(3&*&4))
&&&&&&&&#&read&name
&&&&&&&&pr_name&=&self.ReadString(Dword(self.MetaTable&+&property_offset)&+&self.MetaStrings)
&&&&&&&&#&read&type
&&&&&&&&pr_type&=&self.ReadString(Dword(self.MetaTable&+&property_offset&+&4)&+&self.MetaStrings)
&&&&&&&&return&pr_type&+&&&&&+&pr_name
&&&&def&GetPropertyFlags(self,&property_index):
&&&&&&&&if&property_index&&=&self.GetPropertyNumber():
&&&&&&&&&&&&return&-1
&&&&&&&&property_offset&=&(Dword(self.MetaTable&+&28)&*&4)&+&(property_index&*&(3&*&4))
&&&&&&&&return&Dword(self.MetaTable&+&property_offset&+&8)
&&&&def&PropertyFlagsToString(self,&flags):
&&&&&&&&if&flags&==&0:
&&&&&&&&&&&&return&&Invalid&
&&&&&&&&fstr&=&&&
&&&&&&&&if&flags&&&self.PropertyFlags.Readable:
&&&&&&&&&&&&fstr&+=&&&|&Readable&
&&&&&&&&if&flags&&&self.PropertyFlags.Writable:
&&&&&&&&&&&&fstr&+=&&&|&Writable&
&&&&&&&&if&flags&&&self.PropertyFlags.Resettable:
&&&&&&&&&&&&fstr&+=&&&|&Resettable&
&&&&&&&&if&flags&&&self.PropertyFlags.EnumOrFlag:
&&&&&&&&&&&&fstr&+=&&&|&EnumOrFlag&
&&&&&&&&if&flags&&&self.PropertyFlags.StdCppSet:
&&&&&&&&&&&&fstr&+=&&&|&StdCppSet&
&&&&&&&&if&flags&&&self.PropertyFlags.Designable:
&&&&&&&&&&&&fstr&+=&&&|&Designable&
&&&&&&&&if&flags&&&self.PropertyFlags.ResolveDesignable:
&&&&&&&&&&&&fstr&+=&&&|&ResolveDesignable&
&&&&&&&&if&flags&&&self.PropertyFlags.Scriptable:
&&&&&&&&&&&&fstr&+=&&&|&Scriptable&
&&&&&&&&if&flags&&&self.PropertyFlags.ResolveScriptable:
&&&&&&&&&&&&fstr&+=&&&|&ResolveScriptable&
&&&&&&&&if&flags&&&self.PropertyFlags.Stored:
&&&&&&&&&&&&fstr&+=&&&|&Stored&
&&&&&&&&if&flags&&&self.PropertyFlags.ResolveStored:
&&&&&&&&&&&&fstr&+=&&&|&ResolveStored&
&&&&&&&&if&flags&&&self.PropertyFlags.Editable:
&&&&&&&&&&&&fstr&+=&&&|&Editable&
&&&&&&&&if&flags&&&self.PropertyFlags.ResolveEditable:
&&&&&&&&&&&&fstr&+=&&&|&ResolveEditable&
&&&&&&&&if&flags&&&self.PropertyFlags.User:
&&&&&&&&&&&&fstr&+=&&&|&User&
&&&&&&&&if&flags&&&self.PropertyFlags.ResolveUser:
&&&&&&&&&&&&fstr&+=&&&|&ResolveUser&
&&&&&&&&return&fstr[3:]
&&&&def&GetEnumNumber(self):
&&&&&&&&return&Dword(self.MetaTable&+&32)
&&&&def&GetEnumDecl(self,&enum_index):
&&&&&&&&if&enum_index&&=&self.GetPropertyNumber():
&&&&&&&&&&&&return&&error:&property&index&out&of&range&
&&&&&&&&enum_offset&=&(Dword(self.MetaTable&+&36)&*&4)&+&(enum_index&*&(4&*&4))
&&&&&&&&#&read&name
&&&&&&&&enum_name&=&self.ReadString(Dword(self.MetaTable&+&enum_offset)&+&self.MetaStrings)
&&&&&&&&#&read&number&of&items
&&&&&&&&items_num&=&Dword(self.MetaTable&+&enum_offset&+&8)
&&&&&&&&#&items&addr
&&&&&&&&items_addr&=&(Dword(self.MetaTable&+&enum_offset&+&12)&*&4)&+&self.MetaTable
&&&&&&&&decl&=&&enum&&&+&enum_name&+&&\n{\n&
&&&&&&&&#&add&items
&&&&&&&&x&=&0
&&&&&&&&while&x&&&items_num:
&&&&&&&&&&&&#&read&item&name
&&&&&&&&&&&&item_name&=&self.ReadString(Dword(items_addr)&+&self.MetaStrings)
&&&&&&&&&&&&#&read&data
&&&&&&&&&&&&item_data&=&&0x%X&&%&Dword(items_addr&+&4)
&&&&&&&&&&&&#&add
&&&&&&&&&&&&decl&+=&&&&&&&&+&item_name&+&&&=&&&+&item_data&+&&,\n&
&&&&&&&&&&&&#&inc&loop
&&&&&&&&&&&&x&+=&1
&&&&&&&&&&&&items_addr&+=&8
&&&&&&&&&&&&
&&&&&&&&decl&+=&&\n};&
&&&&&&&&return&decl
#&---------------------------------------------
#&Display&MetaData
#&---------------------------------------------
def&DisplayMethod(parser,&method_index):
&&&&print(str(method_index)&+&&&-&&&+&parser.GetMethodSignature(method_index))
def&DisplayProperty(parser,&property_index):
&&&&print(str(property_index)&+&&&-&&&+&parser.GetPropertyDecl(property_index))
&&&&flags&=&parser.GetPropertyFlags(property_index)
&&&&print(&&&&&flags:&&&+&parser.PropertyFlagsToString(flags))
def&DisplayEnum(parser,&enum_index):
&&&&print(&[&&+&str(enum_index)&+&&]\n&&+&parser.GetEnumDecl(enum_index)&+&&\n&)
def&DisplayMetaData(stringsaddr,&tableaddr):
&&&&parser&=&MetaParser(stringsaddr,&tableaddr)
&&&&print(&\n-------------------------------------------------&)
&&&&print(&---&&&+&&Qt&MetaData&Displayer&by&Daniel&Pistelli&)
&&&&print(&---&&&+&&metadata&of&the&class:&&&+&parser.GetClassName()&+&&\n&)
&&&&num_methods&=&parser.GetMethodNumber()
&&&&num_properties&=&parser.GetPropertyNumber()
&&&&num_enums&=&parser.GetEnumNumber()
&&&&#&---------------------------------------------
&&&&#&methods
&&&&#&---------------------------------------------
&&&&#&signals
&&&&print(&---&Signals:\n&)
&&&&while&x&&&num_methods:
&&&&&&&&#&print&if&it's&a&signal
&&&&&&&&if&parser.GetMethodType(x)&==&parser.MethodFlags.MethodSignal:
&&&&&&&&&&&&DisplayMethod(parser,&x)
&&&&&&&&#&increment&loop
&&&&&&&&x&+=&1&
&&&&#&slots
&&&&print(&\n---&Slots:\n&)
&&&&while&x&&&num_methods:
&&&&&&&&#&print&if&it's&a&slot
&&&&&&&&if&parser.GetMethodType(x)&==&parser.MethodFlags.MethodSlot:
&&&&&&&&&&&&DisplayMethod(parser,&x)
&&&&&&&&#&increment&loop
&&&&&&&&x&+=&1&
&&&&#&other&methods
&&&&print(&\n---&Other&Methods:\n&)
&&&&while&x&&&num_methods:
&&&&&&&&#&print&if&it's&a&slot
&&&&&&&&if&parser.GetMethodType(x)&==&parser.MethodFlags.MethodMethod:
&&&&&&&&&&&&DisplayMethod(parser,&x)
&&&&&&&&#&increment&loop
&&&&&&&&x&+=&1
&&&&#&---------------------------------------------
&&&&#&properties
&&&&#&---------------------------------------------
&&&&print(&\n---&Properties:\n&)
&&&&while&x&&&num_properties:
&&&&&&&&DisplayProperty(parser,&x)
&&&&&&&&#&increment&loop
&&&&&&&&x&+=&1
&&&&#&---------------------------------------------
&&&&#&enums
&&&&#&---------------------------------------------
&&&&print(&\n---&Enums:\n&)
&&&&while&x&&&num_enums:
&&&&&&&&DisplayEnum(parser,&x)
&&&&&&&&#&increment&loop
&&&&&&&&x&+=&1
&&&&print(&-------------------------------------------------\n&)
#&---------------------------------------------
#&---------------------------------------------
addrtoparse&=&ScreenEA()
if&addrtoparse&!=&0:
&&&&stringsaddr&=&ReadAddress(addrtoparse&+&AddressSize())
&&&&tableaddr&=&ReadAddress(addrtoparse&+&AddressSize()&*&2)
&&&&if&stringsaddr&!=&0&or&tableaddr&!=&0:
&&&&&&&&DisplayMetaData(stringsaddr,&tableaddr)
DisplayMetaData函数接收两个参数:元数据表地址和元数据字符串地址。这是因为有时类的结构要在运行时才能确定,就像下面的VC++例子:
.text:00401D60&sub_401D60&&&&&&proc&near&&&;&DATA&XREF:&.rdata:
.text:00401D60&&push&&&&ebp
.text:00401D61&&mov&&&&&ebp,&esp
.text:00401D63&&mov&&&&&eax,&ds:?staticMetaObject@QObject@@2UQMetaObject@@B&
&&&&&&;&QMetaObject&const&QObject::staticMetaObject
.text:00401D68&&mov&&&&&dword_403070,&eax
.text:00401D6D&&mov&&&&&dword_403074,&offset&Str2&;&&Counter&
.text:00401D77&&mov&&&&&dword_403078,&offset&unk_4021A8
.text:00401D81&&mov&&&&&dword_40307C,&0
.text:00401D8B&&pop&&&&&ebp
.text:00401D8C&&retn
.text:00401D8C&sub_401D60&&&&&&endp
但有时候,类结构可以静态确定:
.data:&&dd&0&&&&&&&&&&&&&&&&&&&&;&SuperData
.data:&&dd&offset&MetaStrings&&&;&&Counter&
.data:&&dd&offset&MetaTable
这时,脚本可以直接在SuperData地址上执行,这是QMetaObject中的第一个d结构。Counter类执行脚本后的输出是:
-------------------------------------------------
---&Qt&MetaData&Displayer&by&Daniel&Pistelli
---&metadata&of&the&class:&Counter
---&Signals:
0&-&protected:&void&valueChanged(int&newValue)
---&Slots:
1&-&public:&&&&void&setValue(int&value)
---&Other&Methods:
---&Properties:
0&-&Priority&priority
&&&&flags:&Readable&|&Writable&|&EnumOrFlag&|&StdCppSet&|&Designable&|&Scriptable&|&Stored&|&ResolveEditable
---&Enums:
enum&Priority
&&&&High&=&0x0,
&&&&Low&=&0x1,
&&&&VeryHigh&=&0x2,
&&&&VeryLow&=&0x3,
-------------------------------------------------
是不是很爽?方法的签名甚至包括了参数的名字(当可用时)。
(作者还测试了QWidget类,这里略去数百字)……
如何找到类的元数据呢?让我们看Counter类的结构:
.text:&&mov&&&&&eax,&ds:_ZN7QObjectC2EPS_
.text:&&mov&&&&&[esp+88h+var_84],&ecx
.text:004012FB&&mov&&&&&[ebp+var_3C],&2
.text:&&call&&&&eax&;&_ZN7QObjectC2EPS_
.text:&&mov&&&&&eax,&[ebp+var_4C]
.text:&&mov&&&&&dword&ptr&[eax],&offset&virtual_ptrs
最后一个汇编指令设置了虚函数表指针。Q_OBJECT类的虚函数表类似于:
.rdata:&off_402158&dd&offset&metaObject
.rdata:0040215C&&&&&&&&&&&&dd&offset&qt_metacast
.rdata:&&&&&&&&&&&&dd&offset&qt_metacall
metaObject方法可以获得元数据表:
.text:&metaObject&&&&&&proc&near&&&&&&&&&&&&&
.text:&&push&&&&ebp
.text:&&mov&&&&&eax,&offset&dword_406000&;&QMetaObject&class&layout
.text:&&mov&&&&&ebp,&esp
.text:&&pop&&&&&ebp
.text:&&retn
.text:&metaObject&&&&&&endp
dword_406000对应QMetaObject类的结构,我们需要它来执行脚本。
获得类的元数据以后,下一步就是将方法(属性)的名字和实际反汇编代码联系起来。脚本输出了每个方法和属性的索引。要想从索引得到方法的地址,必须考虑moc生成的qt_metacall方法:
int&Counter::qt_metacall(QMetaObject::Call&_c,&int&_id,&void&**_a)
&&&&_id&=&QObject::qt_metacall(_c,&_id,&_a);
&&&&if&(_id&&&0)
&&&&&&&&return&_
&&&&if&(_c&==&QMetaObject::InvokeMetaMethod)&{
&&&&&&&&switch&(_id)&{
&&&&&&&&case&0:&valueChanged((*reinterpret_cast&&int(*)&(_a[1])));&
&&&&&&&&case&1:&setValue((*reinterpret_cast&&int(*)&(_a[1])));&
&&&&&&&&_id&-=&2;
#ifndef&QT_NO_PROPERTIES
&&&&&&else&if&(_c&==&QMetaObject::ReadProperty)&{
&&&&&&&&void&*_v&=&_a[0];
&&&&&&&&switch&(_id)&{
&&&&&&&&case&0:&*reinterpret_cast&&Priority*&(_v)&=&priority();&
&&&&&&&&_id&-=&1;
&&&&}&else&if&(_c&==&QMetaObject::WriteProperty)&{
&&&&&&&&void&*_v&=&_a[0];
&&&&&&&&switch&(_id)&{
&&&&&&&&case&0:&setPriority(*reinterpret_cast&&Priority*&(_v));&
&&&&&&&&_id&-=&1;
&&&&}&else&if&(_c&==&QMetaObject::ResetProperty)&{
&&&&&&&&_id&-=&1;
&&&&}&else&if&(_c&==&QMetaObject::QueryPropertyDesignable)&{
&&&&&&&&_id&-=&1;
&&&&}&else&if&(_c&==&QMetaObject::QueryPropertyScriptable)&{
&&&&&&&&_id&-=&1;
&&&&}&else&if&(_c&==&QMetaObject::QueryPropertyStored)&{
&&&&&&&&_id&-=&1;
&&&&}&else&if&(_c&==&QMetaObject::QueryPropertyEditable)&{
&&&&&&&&_id&-=&1;
&&&&}&else&if&(_c&==&QMetaObject::QueryPropertyUser)&{
&&&&&&&&_id&-=&1;
#endif&//&QT_NO_PROPERTIES
&&&&return&_
这个方法可以解析方法名和属性get/set方法名。
qt_metacall的地址可以从虚函数表获得。在分析此函数代码前,我们先看这个枚举:
enum&Call&{
&&&&InvokeMetaMethod,&&&&//&0
&&&&ReadProperty,&&&&//&1
&&&&WriteProperty,&&&&//&2
&&&&ResetProperty,&&&&//&3
&&&&QueryPropertyDesignable,&&//&4
&&&&QueryPropertyScriptable,&&//&5
&&&&QueryPropertyStored,&&//&6
&&&&QueryPropertyEditable,&&//&7
&&&&QueryPropertyUser&&&&//&8
然后看反汇编:
.text:&qt_metacall&&&&&proc&near
.text:&var_28&=&dword&ptr&-28h
.text:&var_24&=&dword&ptr&-24h
.text:&var_20&=&dword&ptr&-20h
.text:&var_1C&=&dword&ptr&-1Ch
.text:&var_10&=&dword&ptr&-10h
.text:&var_C&&=&dword&ptr&-0Ch
.text:&var_8&&=&dword&ptr&-8
.text:&var_4&&=&dword&ptr&-4
.text:&arg_0&&=&dword&ptr&&8
.text:&arg_4&&=&dword&ptr&&0Ch
.text:&arg_8&&=&dword&ptr&&10h
.text:&arg_C&&=&dword&ptr&&14h
.text:&&push&&&&ebp
.text:&&mov&&&&&ebp,&esp
.text:&&sub&&&&&esp,&28h
.text:&&mov&&&&&[ebp+var_C],&ebx
.text:&&mov&&&&&eax,&[ebp+arg_0]
.text:004014DC&&mov&&&&&ebx,&[ebp+arg_8]
.text:004014DF&&mov&&&&&[ebp+var_8],&esi
.text:&&mov&&&&&esi,&[ebp+arg_4]&;&esi&=&_c
.text:&&mov&&&&&[ebp+var_4],&edi
.text:&&mov&&&&&edi,&[ebp+arg_C]
.text:004014EB&&mov&&&&&[ebp+var_10],&eax
.text:004014EE&&mov&&&&&[esp+28h+var_20],&ebx
.text:&&mov&&&&&[esp+28h+var_1C],&edi
.text:&&mov&&&&&[esp+28h+var_24],&esi
.text:004014FA&&mov&&&&&[esp+28h+var_28],&eax
.text:004014FD&&call&&&&_ZN7QObject11qt_metacallEN11QMetaObject4CallEiPPv
.text:&&test&&&&eax,&eax&&&&&&&&;&eax&=&_id
.text:&&mov&&&&&ebx,&eax
.text:&&js&&&&&&short&loc_40151C&;&_id&&&0&?
.text:&&test&&&&esi,&esi
.text:0040150A&&jnz&&&&&short&loc_401530&;&_c&!=&InvokeMetaMethod
.text:0040150C&&test&&&&eax,&eax&&&&&&&&;&_id&==&0&?
.text:0040150E&&jz&&&&&&loc_4015B8
.text:&&cmp&&&&&eax,&1&&&&&&&&&&;&_id&==&1&?
.text:&&jz&&&&&&short&loc_401590
假如esi不等于0,那么就是一个InvokeMetaMethod。考虑&_id&==&0&的情况:
.text:&loc_4015B8:
.text:&&mov&&&&&eax,&[edi+4]
.text:004015BB&&mov&&&&&edx,&[ebp+var_10]
.text:004015BE&&mov&&&&&eax,&[eax]
.text:&&mov&&&&&[esp+28h+var_28],&edx
.text:&&mov&&&&&[esp+28h+var_24],&eax
.text:&&call&&&&sub_401490
由此我们很快就可以确定sub_401490就是valueChanged信号:
.text:&;&void&__cdecl&signal_valueChanged(int&this,&int&newValue)
.text:&signal_valueChanged&proc&near
.text:&var_18&=&dword&ptr&-18h
.text:&var_14&=&dword&ptr&-14h
.text:&var_10&=&dword&ptr&-10h
.text:&var_C&&=&dword&ptr&-0Ch
.text:&var_8&&=&dword&ptr&-8
.text:&var_4&&=&dword&ptr&-4
.text:&this&&&=&dword&ptr&&8
.text:&newValue&=&dword&ptr&&0Ch
.text:&&push&&&&ebp
.text:&&xor&&&&&ecx,&ecx
.text:&&mov&&&&&ebp,&esp
.text:&&lea&&&&&eax,&[ebp+newValue]
.text:&&sub&&&&&esp,&18h
.text:0040149B&&mov&&&&&[ebp+var_8],&0
.text:&&mov&&&&&edx,&offset&dword_406000
.text:&&mov&&&&&[ebp+var_4],&eax
.text:004014AA&&lea&&&&&eax,&[ebp+var_8]
.text:004014AD&&mov&&&&&[esp+18h+var_C],&eax
.text:&&mov&&&&&eax,&[ebp+this]
.text:&&mov&&&&&[esp+18h+var_10],&ecx
.text:&&mov&&&&&[esp+18h+var_14],&edx
.text:004014BC&&mov&&&&&[esp+18h+var_18],&eax
.text:004014BF&&call&&&&ds:_ZN11QMetaObject8activateEP7QObjectPKS_iPPv
.text:&&leave
.text:&&retn
接下来就无需解释了。同样的方法也可用来解决属性的get/set。对于一个复杂文件,可以结合前面的元数据脚本,编写一个解析器自动解析switch块中的函数。但是要注意的是:switch块是平台和编译器相关的,因此脚本需要频繁调整。此外,一些小的方法可能被内联(inline)进qt_metacall方法中。上传的附件

我要回帖

更多关于 qmetatype 的文章

 

随机推荐