研一的时候开始使用Qt,感觉用Qt开发图形界面比MFC的一套框架来方便的多。后来由于项目的需要,也没有再接触Qt了。现在要重新拾起来,于是要从基础学起。

Now,开始学习Qt事件处理机制。

元对象系统的构成

  1. QObject为所有需要利用元对象系统的对象提供一个基类。
  2. Q_OBJECT宏,在类的声明体内激活meta-object功能,比如动态属性、信号和槽。
  3. Meta Object Compiler(MOC),为每个QObject派生类生成代码,以支持meta-object功能。
  4. QObject定义了从一个QObject对象访问meta-object功能的接口,Q_OBJECT宏用来告诉编译器该类需要激活meta-object功能,编译器在扫描一个源文件时,如果发现类的声明中有这个宏,就会生成一些代码来为支持meta-object功能——主要是生成该类对应MetaObject类以及对QObject的函数override(重载)。

QObject和QMetaObject

QMetaObject包含了QObject的所谓的元数据,也就是QObject信息的一些描述信息:除了类型信息外,还包含QT中特有的signal&slot信息。

virtual QObject::metaObject();

该方法返回一个QObject对应的metaObject对象,如上文所说,如果一个类的声明中包含了Q_OBJECT宏,编译器会生成代码来实现这个类对应的QMetaObject类,并重载QObject::metaObject()方法来返回这个QMetaObject类的实例引用。这样当通过QObject类型的引用调用metaObejct方法时,返回的是这个引用的所指的真实对象的metaobject。

如果一个类从QObject派生,确没有声明Q_OBJECT宏,那么这个类的metaobject对象不会被生成,这样这个类所声明的signal slot都不能使用,而这个类实例调用metaObject()返回的就是其父类的metaobject对象,这样导致的后果就是你从这个类实例获得的元数据其实都是父类的数据,这显然给你的代码埋下隐患。因此如果一个类从QOBject派生,它都应该声明Q_OBJECT宏,不管这个类有没有定义signal&slot和Property。

这样每个QObject类都有一个对应的QMetaObject类,形成一个平行的类型层次。

QMetaObject提供的信息

下面通过QMetaObject的接口来解释QMetaObject提供的信息。

1)基本信息

struct Q_CORE_EXPORT QMetaObject
{
const char *className() const;
const QMetaObject *superClass() const;
    
     struct { // private data
const QMetaObject *superdata; //父类QMetaObject实例的指针
const char *stringdata; //一段字符串内存块,包含MetaObject信息之字符串信息
const uint *data; //一段二级制内存块,包含MetaObject信息之二进制信息
const void *extradata; //额外字段,暂未使用
} d;
   ...
};

2)classinfo:提供额外的类信息-名值对。用户可以在类的生命中以Q_CLASSINFO(name,value)的方式添加。

 int classInfoOffset() const;
int classInfoCount() const;
int indexOfClassInfo(const char *name) const;
QMetaClassInfo classInfo(int index) const;

example:

class MyClass : public QObject
{
Q_OBJECT
Q_CLASSINFO("author", "Sabrina Schweinsteiger")
Q_CLASSINFO("url", "http://doc.moosesoft.co.uk/1.0/") public:
...
};

3)constructor:提供该类的构造方法信息。

 int constructorCount() const;
int indexOfConstructor(const char *constructor) const;
QMetaMethod constructor(int index) const;

4)enum:描述该类声明体重所包含的枚举类型信息。

 int enumeratorOffset() const;
int enumeratorCount() const;
int indexOfEnumerator(const char *name) const;
QMetaEnum enumerator(int index) const;

5)method:描述类中所包含方法信息:包括property,signal,slot等。

 int methodOffset() const;
int methodCount() const;
int indexOfMethod(const char *method) const;
int indexOfSignal(const char *signal) const;
int indexOfSlot(const char *slot) const;
QMetaMethod method(int index) const;

6)property:类型的属性信息。

 int propertyOffset() const;
int propertyCount() const;
int indexOfProperty(const char *name) const;
QMetaProperty property(int index) const;

注意:对于类里面定义的函数,构造函数,枚举,只有加上一些宏才表示你希望为方法提供meta信息。比如 Q_ENUMS用来注册宏,Q_INVACABLE用来注册方法(包括构造函数)。Qt这么设计的原因应该是避免meta信息的臃肿。

举例说明MOS(Meta Object System)

TestObject继承QObject,定义了两个Property:PropertyA和PropertyB;两个classinfo:Author,version;一个枚举:TestEnum。

    #include <QObject>
class TestObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString propertyA READ getPropertyA WRITE getPropertyA RESET resetPropertyA DESIGNABLE true SCRIPTABLE true STORED true USER false)
Q_PROPERTY(QString propertyB READ getPropertyB WRITE getPropertyB RESET resetPropertyB)
Q_CLASSINFO("Author", "Long Huihu")
Q_CLASSINFO("Version", "TestObjectV1.0")
Q_ENUMS(TestEnum)
public:
enum TestEnum {
EnumValueA,
EnumValueB
};
public:
TestObject();
signals:
void clicked();
void pressed();
public slots:
void onEventA(const QString &);
void onEventB(int );
}

TestObject的moc文件:

#include "TestObject.h"
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'TestObject.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 62
#error "This file was generated using the moc from 4.6.0. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif
QT_BEGIN_MOC_NAMESPACE
static const uint qt_meta_data_TestObject[] = {
// content:
, // revision
, // classname
, , // classinfo
, , // methods
, , // properties
, , // enums/sets
, , // constructors
, // flags
, // signalCount
// classinfo: key, value
, ,
, ,
// signals: signature, parameters, type, tag, flags
, , , , 0x05,
, , , , 0x05,
// slots: signature, parameters, type, tag, flags
, , , , 0x0a,
, , , , 0x0a,
// properties: name, type, flags
, , 0x0a095007,
, , 0x0a095007,
// enums: name, flags, count, data
, 0x0, , ,
// enum data: key, value
, uint(TestObject::EnumValueA),
, uint(TestObject::EnumValueB),
// eod
};
static const char qt_meta_stringdata_TestObject[] = {
"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"
};
const QMetaObject TestObject::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_TestObject,
qt_meta_data_TestObject, }
};
#ifdef Q_NO_DATA_RELOCATION
const QMetaObject &TestObject::getStaticMetaObject() { return staticMetaObject; }
#endif //Q_NO_DATA_RELOCATION
const QMetaObject *TestObject::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
}
void *TestObject::qt_metacast(const char *_clname)
{
if (!_clname) return ;
if (!strcmp(_clname, qt_meta_stringdata_TestObject))
return static_cast<void*>(const_cast< TestObject*>(this));
return QObject::qt_metacast(_clname);
}
int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < )
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
case : clicked(); break;
case : pressed(); break;
case : onEventA((*reinterpret_cast< const QString(*)>(_a[]))); break;
case : onEventB((*reinterpret_cast< int(*)>(_a[]))); break;
default: ;
}
_id -= ;
}
#ifndef QT_NO_PROPERTIES
else if (_c == QMetaObject::ReadProperty) {
void *_v = _a[];
switch (_id) {
case : *reinterpret_cast< QString*>(_v) = getPropertyA(); break;
case : *reinterpret_cast< QString*>(_v) = getPropertyB(); break;
}
_id -= ;
} else if (_c == QMetaObject::WriteProperty) {
void *_v = _a[];
switch (_id) {
case : getPropertyA(*reinterpret_cast< QString*>(_v)); break;
case : getPropertyB(*reinterpret_cast< QString*>(_v)); break;
}
_id -= ;
} else if (_c == QMetaObject::ResetProperty) {
switch (_id) {
case : resetPropertyA(); break;
case : resetPropertyB(); break;
}
_id -= ;
} else if (_c == QMetaObject::QueryPropertyDesignable) {
_id -= ;
} else if (_c == QMetaObject::QueryPropertyScriptable) {
_id -= ;
} else if (_c == QMetaObject::QueryPropertyStored) {
_id -= ;
} else if (_c == QMetaObject::QueryPropertyEditable) {
_id -= ;
} else if (_c == QMetaObject::QueryPropertyUser) {
_id -= ;
}
#endif // QT_NO_PROPERTIES
return _id;
}
// SIGNAL 0
void TestObject::clicked()
{
QMetaObject::activate(this, &staticMetaObject, , );
}
// SIGNAL 1
void TestObject::pressed()
{
QMetaObject::activate(this, &staticMetaObject, , );
}
QT_END_MOC_NAMESPACE
  1. qt_meta_data_TestObject:定义的正是QMetaObject::d.data指向的信息块;
  2. qt_meta_stringdata_TestObject:定义的是QMetaObject::d.dataString指向的信息块;
  3. const QMetaObject TestObject::staticMetaObject:定义TestObject类的MetaObject实例,从中可以看出QMetaObject各个字段是如何被赋值的;
  4. const QMetaObject *TestObject::metaObject() const:重写了QObject::metaObject函数,返回上述的MetaObject实例指针。
  5. TestObject::qt_metacall()是重写QObject的方法,依据传入的参数来调用signal&slot或访问property,动态方法调用属性访问正是依赖于这个方法。
  6. 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:
, // revision
, // classname , , // classinfo , , // methods , , // properties
, , // enums/sets
, , // constructors
, // flags
, // signalCount 这块数据可以被看做meta信息的头部,正好和QMetaObjectPrivate数据结构相对应,在QMetaObject的实现中,正是将这块数据映射为QMetaObjectPrivate进行使用的。 第一行数据“”:版本号; 第二行数据“”:类型名,该值是qt_meta_stringdata_TestObject的索引,qt_meta_stringdata_TestObject[]这个字符串不正是类型名“TestObject”吗。 第三行数据“,”,第一个表明有2个classinfo被定义,第二个是说具体的 classinfo信息在qt_meta_data_TestObject中的索引,qt_meta_data_TestObject[]的位置两个 classinfo名值对的定义; 第四行数据“,”,指明method的信息,模式同上; 第五行数据“,”,指明property的信息,模式同上;
第六行数据“,”,指明enum的信息,模式同上。 数据块二:
// classinfo: key, value
, ,
, , classinfo信息块。第一行“,”,22表明 qt_meta_stringdata_TestObject[]处定义的字符串是classinfo的key,11表明 qt_meta_stringdata_TestObject[]处的字符串就是value。第二行“,”定义第二个classinfo。 数据块三:
// signals: signature, parameters, type, tag, flags
, , , , 0x05,
, , , , 0x05, signal信息块。第一行“, , , , 0x05”定义第一个signal clicked()。qt_meta_stringdata_TestObject[]是signal名称字符串。parameters , type , tag , flags如何解释暂未知。 数据块四:
// slots: signature, parameters, type, tag, flags
, , , , 0x0a,
, , , , 0x0a, slots信息,模式类似signal。 数据块五:
// properties: name, type, flags
, , 0x0a095007,
, , 0x0a095007, property性信息,模式类signal和slots,105如何和type对应暂未知。 数据块六:
// enums: name, flags, count, data
, 0x0, , ,
// enum data: key, value
, uint(TestObject::EnumValueA),
, uint(TestObject::EnumValueB), enum信息,第一行定义的是枚举名,flag,值的数目,data48不知是什么。 几行定义的是各枚举项的名称和值。名称同上都是qt_meta_stringdata_TestObject的索引值。 // 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"
};

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 4
// revision 5 introduces changes in normalized signatures, no new members
// revision 6 added qt_static_metacall as a member of each Q_OBJECT and inside QMetaObject itself
};

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<const QMetaObjectPrivate*>(data);
}

d.data指向的是那块二进制信息,priv将d.data解释为QMetaObjectPrivate。

QMetaObject::classInfoOffet()

int QMetaObject::classInfoOffset() const
{
int offset = ;
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 QMetaObject::classInfo(int index) const
{
int i = index;
i -= classInfoOffset();
if (i < && d.superdata)
return d.superdata->classInfo(index); QMetaClassInfo result;
if (i >= && i < priv(d.data)->classInfoCount) {
result.mobj = this;
result.handle = priv(d.data)->classInfoData + *i;
}
return result;
} class Q_CORE_EXPORT QMetaClassInfo
{
public:
inline QMetaClassInfo() : mobj(),handle() {}
const char *name() const;
const char *value() const;
inline const QMetaObject *enclosingMetaObject() const { return mobj; }
private:
const QMetaObject *mobj;
uint handle;
friend struct QMetaObject;
};

这个代码的流程比较简单。priv(d.data)->classInfoData是classinfo的信息在d.data中的偏移;每条classinfo信息占2个UINT的大小,因此“priv(d.data)->classInfoData + 2*i”这个表达式的值就是第i个classinfo的信息在d.data中的偏移。

QMetaObject::indexOfClassInfo()

int QMetaObject::indexOfClassInfo(const char *name) const
{
int i = -;
const QMetaObject *m = this;
while (m && i < ) {
for (i = priv(m->d.data)->classInfoCount-; i >= ; --i)
if (strcmp(name, m->d.stringdata
+ m->d.data[priv(m->d.data)->classInfoData + *i]) == ) {
i += m->classInfoOffset();
break;
}
m = m->d.superdata;
}
return i;
}

按照继承层次,从下往上寻找名字为name的classinfo。

参考前一函数的解释,表达式m->d.data[priv(m->d.data)->classInfoData + 2*i]的值是第i个classinfo信息的第一个32位值。该值是字符信息块d.stringdata中的索引值。因此 m->d.stringdata+ m->d.data[priv(m->d.data)->classInfoData + 2*i]就是classinfo名称的字符串。

int constructorCount() const

 int QMetaObject::constructorCount() const
{
if (priv(d.data)->revision < )
return ;
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 >= && i >= && i < priv(d.data)->constructorCount) {
result.mobj = this;
result.handle = priv(d.data)->constructorData + *i;
}
return result;
}

int indexOfConstructor  ( const char * constructor ) const

  int QMetaObject::indexOfConstructor(const char *constructor) const
{
if (priv(d.data)->revision < )
return -;
for (int i = priv(d.data)->constructorCount-; i >= ; --i) {
const char *data = d.stringdata + d.data[priv(d.data)->constructorData + *i];
if (data[] == constructor[] && strcmp(constructor + , data + ) == ) {
return i;
}
}
return -;
}
 int enumeratorCount () const

 int enumeratorOffset () const

 QMetaEnum enumerator ( int index ) const

 int indexOfEnumerator ( const char * name ) const

查找Signal

一般函数的查找方式

QMetaMethod QMetaObject::method(int index) const
{
int i = index;
i -= methodOffset();
if (i < && d.superdata)
return d.superdata->method(index); QMetaMethod result;
if (i >= && i < priv(d.data)->methodCount) {
result.mobj = this;
result.handle = priv(d.data)->methodData + *i;
}
return result;
}
int methodCount () const 略;
int methodOffset () const 略;
int indexOfMethod(const char *method)const;

signal的查找函数

int QMetaObject::indexOfSignal(const char *signal) const
{
const QMetaObject *m = this;
int i = QMetaObjectPrivate::indexOfSignalRelative(&m, signal, false);
if (i < ) {
m = this;
i = QMetaObjectPrivate::indexOfSignalRelative(&m, signal, true);
}
if (i >= )
i += m->methodOffset();
return i;
} int QMetaObjectPrivate::indexOfSignalRelative(const QMetaObject **baseObject,
const char *signal,
bool normalizeStringData)
{
int i = indexOfMethodRelative<MethodSignal>(baseObject, signal, normalizeStringData);
#ifndef QT_NO_DEBUG
const QMetaObject *m = *baseObject;
if (i >= && m && m->d.superdata) {
int conflict = m->d.superdata->indexOfMethod(signal);
if (conflict >= )
qWarning("QMetaObject::indexOfSignal: signal %s from %s redefined in %s",
signal, m->d.superdata->d.stringdata, m->d.stringdata);
}
#endif
return i;
}
template<int MethodType>
static inline int indexOfMethodRelative(const QMetaObject **baseObject,
const char *method,
bool normalizeStringData)
{
for (const QMetaObject *m = *baseObject; m; m = m->d.superdata) {
int i = (MethodType == MethodSignal && priv(m->d.data)->revision >= )
? (priv(m->d.data)->signalCount - ) : (priv(m->d.data)->methodCount - );
const int end = (MethodType == MethodSlot && priv(m->d.data)->revision >= )
? (priv(m->d.data)->signalCount) : ;
if (!normalizeStringData) {
for (; i >= end; --i) {
const char *stringdata = m->d.stringdata + m->d.data[priv(m->d.data)->methodData + *i];
if (method[] == stringdata[] && strcmp(method + , stringdata + ) == ) {
*baseObject = m;
return i;
}
}
} else if (priv(m->d.data)->revision < ) {
for (; i >= end; --i) {
const char *stringdata = (m->d.stringdata + m->d.data[priv(m->d.data)->methodData + * i]);
const QByteArray normalizedSignature = QMetaObject::normalizedSignature(stringdata);
if (normalizedSignature == method) {
*baseObject = m;
return i;
}
}
}
}
return -;
}

可以看出,查找signal的特别之处在于,通过method元数据的第五项来判断这是不是一个signal。

 int indexOfSlot ( const char * slot ) const 略;
int propertyCount () const 略;
int propertyOffset () const 略;
int indexOfProperty ( const char * name ) const 略;
QMetaProperty QMetaObject::property(int index) const
{
int i = index;
i -= propertyOffset();
if (i < && d.superdata)
return d.superdata->property(index); QMetaProperty result;
if (i >= && i < priv(d.data)->propertyCount) {
int handle = priv(d.data)->propertyData + *i;
int flags = d.data[handle + ];
const char *type = d.stringdata + d.data[handle + ];
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 > ) {
scope_name = enum_name.left(s);
enum_name = enum_name.mid(s + );
}
const QMetaObject *scope = ;
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 QMetaObject::userProperty() const
{
const int propCount = propertyCount();
for (int i = propCount - ; i >= ; --i) {
const QMetaProperty prop = property(i);
if (prop.isUser())
return prop;
}
return QMetaProperty();
}

从这个函数的实现来看,一个QObject应该只会有一个打开USER flag的property。

meta call就是通过object的meta system的支持来动态调用object的方法,metacall也是signal&slot的机制的基石。

static bool invokeMethod(QObject *obj, const char *member,
Qt::ConnectionType,
QGenericReturnArgument ret,
QGenericArgument val0 = QGenericArgument(),
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()); static inline bool invokeMethod(QObject *obj, const char *member,
QGenericReturnArgument ret,
QGenericArgument val0 = QGenericArgument(),
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())
{
return invokeMethod(obj, member, Qt::AutoConnection, ret, val0, val1, val2, val3,
val4, val5, val6, val7, val8, val9);
} static inline bool invokeMethod(QObject *obj, const char *member,
Qt::ConnectionType type,
QGenericArgument val0 = QGenericArgument(),
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())
{
return invokeMethod(obj, member, type, QGenericReturnArgument(), val0, val1, val2,
val3, val4, val5, val6, val7, val8, val9);
} static inline bool invokeMethod(QObject *obj, const char *member,
QGenericArgument val0 = QGenericArgument(),
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())
{
return invokeMethod(obj, member, Qt::AutoConnection, QGenericReturnArgument(), val0,
val1, val2, val3, val4, val5, val6, val7, val8, val9);
}

QMetaObject这个静态方法可以动态地调用obj对象名字为member的方法,type参数表明该调用时同步的还是异步的。ret是一个 通用的用来存储返回值的类型,后面的9个参数是用来传递调用参数的,QGenericArgument()是一种通用的存储参数值的类型。

所调用的方法必须是invocable的,也就是signal,slot或者是加了声明为Q_INVOCABLE的其他方法。

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 false; QVarLengthArray<char, > sig;
int len = qstrlen(member);
if (len <= )
return false;
sig.append(member, len);
sig.append('('); 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 paramCount;
for (paramCount = ; paramCount < MaximumParamCount; ++paramCount) {
len = qstrlen(typeNames[paramCount]);
if (len <= )
break;
sig.append(typeNames[paramCount], len);
sig.append(',');
}
if (paramCount == )
sig.append(')'); // no parameters
else
sig[sig.size() - ] = ')';
sig.append('\0'); int idx = obj->metaObject()->indexOfMethod(sig.constData());
if (idx < ) {
QByteArray norm = QMetaObject::normalizedSignature(sig.constData());
idx = obj->metaObject()->indexOfMethod(norm.constData());
} if (idx < || idx >= obj->metaObject()->methodCount()) {
qWarning("QMetaObject::invokeMethod: No such method %s::%s",
obj->metaObject()->className(), sig.constData());
return false;
}
QMetaMethod method = obj->metaObject()->method(idx);
return method.invoke(obj, type, ret,
val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
}

先依据传递的方法名称和参数,构造完整的函数签名(存储在局部变量sig)。参数的类型名就是调用时传递时的参数静态类型,这里可不会有什么类型转换,这是运行时的行为,参数类型转换是编译时的行为。

然后通过这个sig签名在obj中去查找该方法,查询的结果就是一个QMetaMethod值,再将调用委托给QMetaMethod::invoke方法。

bool QMetaMethod::invoke(QObject *object,
Qt::ConnectionType connectionType,
QGenericReturnArgument returnValue,
QGenericArgument val0,
QGenericArgument val1,
QGenericArgument val2,
QGenericArgument val3,
QGenericArgument val4,
QGenericArgument val5,
QGenericArgument val6,
QGenericArgument val7,
QGenericArgument val8,
QGenericArgument val9) const
{
if (!object || !mobj)
return false; Q_ASSERT(mobj->cast(object)); // check return type
if (returnValue.data()) {
const char *retType = typeName();
if (qstrcmp(returnValue.name(), retType) != ) {
// 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 unnormalized;
int len = qstrlen(returnValue.name()); unnormalized.reserve(len + );
unnormalized = "_("; // the function is called "_"
unnormalized.append(returnValue.name());
unnormalized.append(')'); QByteArray normalized = QMetaObject::normalizedSignature(unnormalized.constData());
normalized.truncate(normalized.length() - ); // drop the ending ')' if (qstrcmp(normalized.constData() + , retType) != )
return false;
}
} // check argument count (we don't allow invoking a method if given too few arguments)
const char *typeNames[] = {
returnValue.name(),
val0.name(),
val1.name(),
val2.name(),
val3.name(),
val4.name(),
val5.name(),
val6.name(),
val7.name(),
val8.name(),
val9.name()
};
int paramCount;
for (paramCount = ; paramCount < MaximumParamCount; ++paramCount) {
if (qstrlen(typeNames[paramCount]) <= )
break;
}
int metaMethodArgumentCount = ;
{
// based on QMetaObject::parameterNames()
const char *names = mobj->d.stringdata + mobj->d.data[handle + ];
if (*names == ) {
// do we have one or zero arguments?
const char *signature = mobj->d.stringdata + mobj->d.data[handle];
while (*signature && *signature != '(')
++signature;
if (*++signature != ')')
++metaMethodArgumentCount;
} else {
--names;
do {
++names;
while (*names && *names != ',')
++names;
++metaMethodArgumentCount;
} while (*names);
}
}
if (paramCount <= metaMethodArgumentCount)
return false; // check connection type
QThread *currentThread = QThread::currentThread();
QThread *objectThread = object->thread();
if (connectionType == Qt::AutoConnection) {
connectionType = currentThread == objectThread
? Qt::DirectConnection
: Qt::QueuedConnection;
} #ifdef QT_NO_THREAD
if (connectionType == Qt::BlockingQueuedConnection) {
connectionType = Qt::DirectConnection;
}
#endif // invoke!
void *param[] = {
returnValue.data(),
val0.data(),
val1.data(),
val2.data(),
val3.data(),
val4.data(),
val5.data(),
val6.data(),
val7.data(),
val8.data(),
val9.data()
};
// recompute the methodIndex by reversing the arithmetic in QMetaObject::property()
int idx_relative = ((handle - priv(mobj->d.data)->methodData) / );
int idx_offset = mobj->methodOffset();
QObjectPrivate::StaticMetaCallFunction callFunction =
(QMetaObjectPrivate::get(mobj)->revision >= && mobj->d.extradata)
? reinterpret_cast<const QMetaObjectExtraData *>(mobj->d.extradata)->static_metacall : ; if (connectionType == Qt::DirectConnection) {
if (callFunction) {
callFunction(object, QMetaObject::InvokeMetaMethod, idx_relative, param);
return true;
} else {
return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, idx_relative + idx_offset, param) < ;
}
} else if (connectionType == Qt::QueuedConnection) {
if (returnValue.data()) {
qWarning("QMetaMethod::invoke: Unable to invoke methods with return values in "
"queued connections");
return false;
} int nargs = ; // include return type
void **args = (void **) qMalloc(paramCount * sizeof(void *));
Q_CHECK_PTR(args);
int *types = (int *) qMalloc(paramCount * sizeof(int));
Q_CHECK_PTR(types);
types[] = ; // return type
args[] = ; for (int i = ; i < paramCount; ++i) {
types[i] = QMetaType::type(typeNames[i]);
if (types[i]) {
args[i] = QMetaType::construct(types[i], param[i]);
++nargs;
} else if (param[i]) {
qWarning("QMetaMethod::invoke: Unable to handle unregistered datatype '%s'",
typeNames[i]);
for (int x = ; x < i; ++x) {
if (types[x] && args[x])
QMetaType::destroy(types[x], args[x]);
}
qFree(types);
qFree(args);
return false;
}
} QCoreApplication::postEvent(object, new QMetaCallEvent(idx_offset, idx_relative, callFunction,
, -, nargs, types, args));
} else { // blocking queued connection
#ifndef QT_NO_THREAD
if (currentThread == objectThread) {
qWarning("QMetaMethod::invoke: Dead lock detected in "
"BlockingQueuedConnection: Receiver is %s(%p)",
mobj->className(), object);
} QSemaphore semaphore;
QCoreApplication::postEvent(object, new QMetaCallEvent(idx_offset, idx_relative, callFunction,
, -, , , param, &semaphore));
semaphore.acquire();
#endif // QT_NO_THREAD
}
return true;
}

代码首先检查返回值的类型是否正确;再检查参数的个数是否匹配,看懂这段代码需要参考该系列之二对moc文件的解析;再依据当前线程和被调对象所属 线程来调整connnection type;如果是directconnection,直接调用 QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param),param是将所有参数值指针排列组成的指针数组。如果不是directconnection,也即异步调用,就通过一个post一个 QMetaCallEvent到obj,此时须将所有的参数复制一份存入event对象。

QMetaObject::metacall的实现如下:

int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)
{
if (QMetaObject *mo = object->d_ptr->metaObject)
return static_cast<QAbstractDynamicMetaObject*>(mo)->metaCall(cl, idx, argv);
else
return object->qt_metacall(cl, idx, argv);
}

如果object->d_ptr->metaObject(QMetaObjectPrivate)存在,通过该metaobject 来调用,这里要参考该系列之三对QMetaObjectPrivate的介绍,这个条件实际上就是object就是QObject类型,而不是派生类型。 否则调用object::qt_metacall。

对于异步调用,QObject的event函数里有如下代码:

class Q_CORE_EXPORT QMetaCallEvent : public QEvent
{
public:
QMetaCallEvent(ushort method_offset, ushort method_relative, QObjectPrivate::StaticMetaCallFunction callFunction , const QObject *sender, int signalId,
int nargs = , int *types = , void **args = , QSemaphore *semaphore = );
~QMetaCallEvent(); inline int id() const { return method_offset_ + method_relative_; }
inline const QObject *sender() const { return sender_; }
inline int signalId() const { return signalId_; }
inline void **args() const { return args_; } virtual void placeMetaCall(QObject *object); private:
const QObject *sender_;
int signalId_;
int nargs_;
int *types_;
void **args_;
QSemaphore *semaphore_;
QObjectPrivate::StaticMetaCallFunction callFunction_;
ushort method_offset_;
ushort method_relative_;
};
class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
Q_DECLARE_PUBLIC(QObject) public:
struct ExtraData
{
ExtraData() {}
#ifndef QT_NO_USERDATA
QVector<QObjectUserData *> userData;
#endif
QList<QByteArray> propertyNames;
QList<QVariant> propertyValues;
}; typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **);
struct Connection
{
QObject *sender;
QObject *receiver;
StaticMetaCallFunction callFunction;
// The next pointer for the singly-linked ConnectionList
Connection *nextConnectionList;
//senders linked list
Connection *next;
Connection **prev;
QBasicAtomicPointer<int> argumentTypes;
ushort method_offset;
ushort method_relative;
ushort connectionType : ; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
~Connection();
int method() const { return method_offset + method_relative; }
};
// ConnectionList is a singly-linked list
struct ConnectionList {
ConnectionList() : first(), last() {}
Connection *first;
Connection *last;
}; struct Sender
{
QObject *sender;
int signal;
int ref;
};
case QEvent::MetaCall:
{
#ifdef QT_JAMBI_BUILD
d_func()->inEventHandler = false;
#endif
QMetaCallEvent *mce = static_cast<QMetaCallEvent*>(e);
QObjectPrivate::Sender currentSender;
currentSender.sender = const_cast<QObject*>(mce->sender());
currentSender.signal = mce->signalId();
currentSender.ref = ;
QObjectPrivate::Sender * const previousSender =
QObjectPrivate::setCurrentSender(this, &currentSender);
#if defined(QT_NO_EXCEPTIONS)
mce->placeMetaCall(this);
#else
QT_TRY {
mce->placeMetaCall(this);
} QT_CATCH(...) {
QObjectPrivate::resetCurrentSender(this, &currentSender, previousSender);
QT_RETHROW;
}
#endif
QObjectPrivate::resetCurrentSender(this, &currentSender, previousSender);
break;
}

QMetaCallEvent的代码很简单:

void QMetaCallEvent::placeMetaCall(QObject *object)
{
if (callFunction_) {
callFunction_(object, QMetaObject::InvokeMetaMethod, method_relative_, args_);
} else {
QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, method_offset_ + method_relative_, args_);
}
}

最后来看一下object->qt_metacall是如何实现的,这又回到了上面所提供的示例moc文件中去了。该文件提供了该方法的实现:

int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
# {
# _id = QObject::qt_metacall(_c, _id, _a);
# if (_id < )
# return _id;
# if (_c == QMetaObject::InvokeMetaMethod) {
# switch (_id) {
# case : clicked(); break;
# case : pressed(); break;
# case : onEventA((*reinterpret_cast< const QString(*)>(_a[]))); break;
# case : onEventB((*reinterpret_cast< int(*)>(_a[]))); break;
# default: ;
# }
# _id -= ;
# }
# #ifndef QT_NO_PROPERTIES
# else if (_c == QMetaObject::ReadProperty) {
# void *_v = _a[];
# switch (_id) {
# case : *reinterpret_cast< QString*>(_v) = getPropertyA(); break;
# case : *reinterpret_cast< QString*>(_v) = getPropertyB(); break;
# }
# _id -= ;
# } else if (_c == QMetaObject::WriteProperty) {
# void *_v = _a[];
# switch (_id) {
# case : getPropertyA(*reinterpret_cast< QString*>(_v)); break;
# case : getPropertyB(*reinterpret_cast< QString*>(_v)); break;
# }
# _id -= ;
# } else if (_c == QMetaObject::ResetProperty) {
# switch (_id) {
# case : resetPropertyA(); break;
# case : resetPropertyB(); break;
# }
# _id -= ;
# } else if (_c == QMetaObject::QueryPropertyDesignable) {
# _id -= ;
# } else if (_c == QMetaObject::QueryPropertyScriptable) {
# _id -= ;
# } else if (_c == QMetaObject::QueryPropertyStored) {
# _id -= ;
# } else if (_c == QMetaObject::QueryPropertyEditable) {
# _id -= ;
# } else if (_c == QMetaObject::QueryPropertyUser) {
# _id -= ;
# }
# #endif // QT_NO_PROPERTIES
# return _id;
# }

这段代码将调用最终转到我们自己的实现的函数中来。这个函数不经提供了metamethod的动态调用,而且也提供了property的动态操作方法。可想而知,property的动态调用的实现方式一定和invocalbe method是一致的。

在qobjectdefs.h中有这样的定义:

 # define METHOD(a)   ""#a
# define SLOT(a) ""#a
# define SIGNAL(a) ""#a

不过是在方法签名之前加了一个数字标记。因为我们既可以将signal连接到slot,也可以将signal连接到signal,所有必须要有某种方法区分一下。

QObject::connect()

bool QObject::connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type)
{
{
const void *cbdata[] = { sender, signal, receiver, method, &type };
if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
return true;
} #ifndef QT_NO_DEBUG
bool warnCompat = true;
#endif
if (type == Qt::AutoCompatConnection) {
type = Qt::AutoConnection;
#ifndef QT_NO_DEBUG
warnCompat = false;
#endif
} if (sender == || receiver == || signal == || method == ) {
qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
sender ? sender->metaObject()->className() : "(null)",
(signal && *signal) ? signal+ : "(null)",
receiver ? receiver->metaObject()->className() : "(null)",
(method && *method) ? method+ : "(null)");
return false;
}
QByteArray tmp_signal_name; if (!check_signal_macro(sender, signal, "connect", "bind"))
return false;
const QMetaObject *smeta = sender->metaObject();
const char *signal_arg = signal;
++signal; //skip code
int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, false);
if (signal_index < ) {
// check for normalized signatures
tmp_signal_name = QMetaObject::normalizedSignature(signal - );
signal = tmp_signal_name.constData() + ; smeta = sender->metaObject();
signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, false);
}
if (signal_index < ) {
// re-use tmp_signal_name and signal from above smeta = sender->metaObject();
signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, true);
}
if (signal_index < ) {
err_method_notfound(sender, signal_arg, "connect");
err_info_about_objects("connect", sender, receiver);
return false;
}
signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);
int signalOffset, methodOffset;
computeOffsets(smeta, &signalOffset, &methodOffset);
int signal_absolute_index = signal_index + methodOffset;
signal_index += signalOffset; QByteArray tmp_method_name;
int membcode = extract_code(method); if (!check_method_code(membcode, receiver, method, "connect"))
return false;
const char *method_arg = method;
++method; // skip code const QMetaObject *rmeta = receiver->metaObject();
int method_index_relative = -;
switch (membcode) {
case QSLOT_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, false);
break;
case QSIGNAL_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, false);
break;
} if (method_index_relative < ) {
// check for normalized methods
tmp_method_name = QMetaObject::normalizedSignature(method);
method = tmp_method_name.constData(); // rmeta may have been modified above
rmeta = receiver->metaObject();
switch (membcode) {
case QSLOT_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, false);
if (method_index_relative < )
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, true);
break;
case QSIGNAL_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, false);
if (method_index_relative < )
method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, true);
break;
}
} if (method_index_relative < ) {
err_method_notfound(receiver, method_arg, "connect");
err_info_about_objects("connect", sender, receiver);
return false;
} if (!QMetaObject::checkConnectArgs(signal, method)) {
qWarning("QObject::connect: Incompatible sender/receiver arguments"
"\n %s::%s --> %s::%s",
sender->metaObject()->className(), signal,
receiver->metaObject()->className(), method);
return false;
} int *types = ;
if ((type == Qt::QueuedConnection)
&& !(types = queuedConnectionTypes(smeta->method(signal_absolute_index).parameterTypes())))
return false; #ifndef QT_NO_DEBUG
if (warnCompat) {
QMetaMethod smethod = smeta->method(signal_absolute_index);
QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset());
check_and_warn_compat(smeta, smethod, rmeta, rmethod);
}
#endif
if (!QMetaObjectPrivate::connect(sender, signal_index, receiver, method_index_relative, rmeta ,type, types))
return false;
const_cast<QObject*>(sender)->connectNotify(signal - );
return true;
}

忽略细节,只关注主要的流程,这段代码的做的事情就是将signal在sender的meta system中的signal索引找出,以及接受者方法(signal或slot)在receiver的meta system中的索引找出来。在委托QMetaObjectPrivate::connect()执行连接。

bool QMetaObjectPrivate::connect(const QObject *sender, int signal_index,
const QObject *receiver, int method_index,
const QMetaObject *rmeta, int type, int *types)
{
QObject *s = const_cast<QObject *>(sender);
QObject *r = const_cast<QObject *>(receiver); int method_offset = rmeta ? rmeta->methodOffset() : ;
QObjectPrivate::StaticMetaCallFunction callFunction =
(rmeta && QMetaObjectPrivate::get(rmeta)->revision >= && rmeta->d.extradata)
? reinterpret_cast<const QMetaObjectExtraData *>(rmeta->d.extradata)->static_metacall : ; QOrderedMutexLocker locker(signalSlotLock(sender),
signalSlotLock(receiver)); if (type & Qt::UniqueConnection) {
QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;
if (connectionLists && connectionLists->count() > signal_index) {
const QObjectPrivate::Connection *c2 =
(*connectionLists)[signal_index].first; int method_index_absolute = method_index + method_offset; while (c2) {
if (c2->receiver == receiver && c2->method() == method_index_absolute)
return false;
c2 = c2->nextConnectionList;
}
}
type &= Qt::UniqueConnection - ;
} QObjectPrivate::Connection *c = new QObjectPrivate::Connection;
c->sender = s;
c->receiver = r;
c->method_relative = method_index;
c->method_offset = method_offset;
c->connectionType = type;
c->argumentTypes = types;
c->nextConnectionList = ;
c->callFunction = callFunction; QT_TRY {
QObjectPrivate::get(s)->addConnection(signal_index, c);
} QT_CATCH(...) {
delete c;
QT_RETHROW;
} c->prev = &(QObjectPrivate::get(r)->senders);
c->next = *c->prev;
*c->prev = c;
if (c->next)
c->next->prev = &c->next; QObjectPrivate *const sender_d = QObjectPrivate::get(s);
if (signal_index < ) {
sender_d->connectedSignals[] = sender_d->connectedSignals[] = ~;
} else if (signal_index < (int)sizeof(sender_d->connectedSignals) * ) {
sender_d->connectedSignals[signal_index >> ] |= ( << (signal_index & 0x1f));
} return true;
}

同样忽略细节,这段代码首先在connecttype要求UniqueConnection的时候检查一下是不是有重复的连接。然后创建一个
QObjectPrivate::Connection结构,这个结构包含了sender,receiver,接受方法的method_index,然后加入到某个连接存储表中;连接存储表可能是一种hash结构,signal_index就是key。可以推测,当signal方法被调用时,一定会到这个结构中查找所有连接到该signal的connection,connection保存了receiver和method_index,由上一篇可知,可以很容易调用到receiver的对应method。

Qt Meta Object System-元对象系统的更多相关文章

  1. 6、Qt Meta Object system 学习

    原文地址:http://blog.csdn.net/ilvu999/article/details/8049908 使用 meta object system 继承自 QOject 类定义中添加 Q_ ...

  2. Qt Meta Object system 学习

    原文地址:http://blog.csdn.net/ilvu999/article/details/8049908 使用 meta object system 继承自 QOject 类定义中添加 Q_ ...

  3. Qt笔记-const-虚函数-元对象系统

    const与指针 摘自C++ Primer Plus (第五版) 中文版 const(常量): const变量的地址可以给指向const的指针,但不能指向常规类型的指针: const float a= ...

  4. 深入了解Qt(二)之元对象系统(Meta-Object System)

    深入了解Qt主要内容来源于Inside Qt系列,本文做了部分删改,以便于理解.在此向原作者表示感谢! 在Qt Meta Object System-元对象系统这篇文章中,从底层实现的源码剖析了元对象 ...

  5. Qt 元对象系统(Meta-Object System)

    (转自:http://blog.csdn.net/aladdina/article/details/5496891) Qt的元对象系统基于如下三件事情: 类:QObject,为所有需要利用原对象系统的 ...

  6. Qt 元对象系统(Meta-Object System)(不管是否使用信号槽,都推荐使用)

    Qt 元对象系统(Meta-Object System) Qt的元对象系统基于如下三件事情: 类:QObject,为所有需要利用原对象系统的对象提供了一个基类. 宏:Q_OBJECT,通常可以声明在类 ...

  7. Qt对象模型之二:对象树与元对象系统

    一.对象树的概念 Qt中使用对象树(object tree)来组织和管理所有的QObject类及其子类的对象.当创建一个QObject时,如果使用了其他的对象作为其父对象(parent),那么这个 Q ...

  8. QtCore是Qt的精髓(包括五大模块:元对象系统,属性系统,对象模型,对象树,信号槽)

    作者:小豆君的干货铺链接:https://www.zhihu.com/question/27040542/answer/218384474来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业 ...

  9. 解析Qt元对象系统(五) Q_INVOKABLE与invokeMethod(automatic connection从Qt4.8开始的解释已经与之前不同,发送对象驻足于哪一个线程并不重要,起到决定作用的是接收者对象所驻足的线程以及发射信号(该信号与接受者连接)的线程是不是在同一个线程)good

    概述查看Qt源码可知,Q_INVOKABLE是个空宏,目的在于让moc识别. 使用Q_INVOKABLE来修饰成员函数,目的在于被修饰的成员函数能够被元对象系统所唤起. Q_INVOKABLE与QMe ...

随机推荐

  1. [实变函数]5.3 非负可测函数的 Lebesgue 积分

    本节中, 设 $f,g,f_i$ 是可测集 $E$ 上的非负可测函数, $A,B$ 是 $E$ 的可测子集.       1 定义: (1) $f$ 在 $E$ 上的 Lebesgue 积分      ...

  2. C++模板元编程 - 2 模仿haskell的列表以及相关操作

    这是昨天和今天写的东西,利用C++的可变模板参数包以及包展开,模式匹配的一些东西做的,感觉用typename...比轮子叔那个List<A,List<B, List<C, D> ...

  3. 更改EGit的user settings中默认的location

    在系统的环境变量中添加变量HOME,值为C:\Users\Kane.Sun\ 记得要讲users改为首字母大写,不然可能会有问题.

  4. Redis各种数据结构内存占用测试

    启动时:(redis为空) 插入数据量都为100W(100W个key或者list中100W个值,或者1000个key,每个key中1000个值) String Key value # Memory u ...

  5. CSS如何实现数字分页效果

    代码实例如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www ...

  6. Yii 框架中带有区间的搜索

  7. (转)关于rdlc报表的数据源

      rdlc 报表字符类数据分为文本数据和表数据,区别就在于文本数据只有一个,表数据可以有多行,然而有很多数据只需要一个传入就可以比如打印某个用户的基本信息,很多信息都是唯一的,如果此时报表传入的数据 ...

  8. JMeter中3种参数值的传递

    小伙伴们在使用JMeter的过程中,肯定会遇到参数值传递的问题,下面来和大家总结下,在使用JMeter做压力测试的时候,常见的3种参数值的传递是怎样的. (一)从CSV文件读取要批量输入的变量 假如我 ...

  9. expdp impdp终极教学

    源地址:http://blog.csdn.net/giianhui/article/details/7788550

  10. 织梦后台更新,报错DedeCMS Error:Tag disabled:"php" more...

    网站采用织梦v5.7版本,在做过一次后台补丁更新后,再对网站“生成”操作的时候,无厘头出现报错“ 网站后台--系统--系统基本参数---其他选项 ---模板引擎禁用标签:php  ,把php删掉 保存 ...