基于Qt5.14.2和mingw的Qt源码学习(四) — 元对象系统之invoke原理及反射实践
- 一、invoke函数
- 二、反射及调用实践
- 三、TODO
上次我们看了Qt是如何把类信息生成到moc文件中。那么这次我们来看看Qt是如何根据对象和函数名称动态调用函数的。
一、invoke函数
QMetaMethod 中重载了几种 invoke 函数,我们挑其中一个作为例子分析其实现原理。
// qmetaobject.cpp
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
{
...
}
1、步骤一 — 对象关系检查
Q_ASSERT(mobj->cast(object));
QObject *QMetaObject::cast(QObject *obj) const
{
// ### Qt 6: inline
return const_cast<QObject*>(cast(const_cast<const QObject*>(obj)));
}
const QObject *QMetaObject::cast(const QObject *obj) const
{
return (obj && obj->metaObject()->inherits(this)) ? obj : nullptr;
}
bool QMetaObject::inherits(const QMetaObject *metaObject) const noexcept
{
const QMetaObject *m = this;
do {
if (metaObject == m)
return true;
} while ((m = m->d.superdata));
return false;
}
inherits 方法也是我们常用的方法,是确定传入的元对象是否为this的超类。那么这里从下往上看可以看出,第一步的作用是判断 object 是否继承于 mobj(拥有此 QMetaMethod 的元对象)。这是由于当我们通过元对象调用其超类方法时,所返回的 QMetaMethod 中存的元对象实际是超类对象。
2、步骤二 — 返回值类型匹配
// check return type
if (returnValue.data()) {
const char *retType = typeName();
if (qstrcmp(returnValue.name(), retType) != 0) {
// normalize the return value as well
QByteArray normalized = QMetaObject::normalizedType(returnValue.name());
if (qstrcmp(normalized.constData(), retType) != 0) {
// String comparison failed, try compare the metatype.
int t = returnType();
if (t == QMetaType::UnknownType || t != QMetaType::type(normalized))
return false;
}
}
}
如果有返回值类型这里需要对返回值类型进行匹配。
(1)QByteArray QMetaObject::normalizedType(const char *type)
这个函数的作用是把类型标准化。参考Qt手册上的解释,我们知道它是把类型上的const和空格去掉。同时文档上也说了,在检查信号和槽是否匹配时也是采取相同的方式对类型进行了标准化处理。
看到这,我想这个部分还挺神奇的,函数类型名称不匹配还要继续比较。关键在于下面这个函数。
(2)int QMetaType::type(const char *typeName)
// qmetatype.cpp
int QMetaType::type(const char *typeName)
{
return qMetaTypeTypeImpl</*tryNormalizedType=*/true>(typeName, qstrlen(typeName));
}
// qmetatype.cpp
template <bool tryNormalizedType>
static inline int qMetaTypeTypeImpl(const char *typeName, int length)
{
if (!length)
return QMetaType::UnknownType;
int type = qMetaTypeStaticType(typeName, length);
if (type == QMetaType::UnknownType) {
QReadLocker locker(customTypesLock());
type = qMetaTypeCustomType_unlocked(typeName, length);
#ifndef QT_NO_QOBJECT
if ((type == QMetaType::UnknownType) && tryNormalizedType) {
const NS(QByteArray) normalizedTypeName = QMetaObject::normalizedType(typeName);
type = qMetaTypeStaticType(normalizedTypeName.constData(),
normalizedTypeName.size());
if (type == QMetaType::UnknownType) {
type = qMetaTypeCustomType_unlocked(normalizedTypeName.constData(),
normalizedTypeName.size());
}
}
#endif
}
return type;
}
static inline int qMetaTypeStaticType(const char *typeName, int length)
{
int i = 0;
while (types[i].typeName && ((length != types[i].typeNameLength)
|| memcmp(typeName, types[i].typeName, length))) {
++i;
}
return types[i].type;
}
这里的 types 是一个 static 变量,储存了Qt支持的原生类型名、类型名长度、类型ID。查看其初始化可以看到qt支持的原生类型包括哪些(太多了这里就不粘贴了)。那么这个函数最后返回的就是类型的唯一标识了。看完这个函数我们就能理解前面为什么判断完类型名是否匹配还要判断ID了。参考该数组的一部分
// qmetatype.h
#define QT_FOR_EACH_STATIC_ALIAS_TYPE(F)\
F(ULong, -1, ulong, "unsigned long")
所以我们可以知道不同的类型名也可以对应相同的类型。但是类型ID是唯一的。
types 数组最后初始化了 QMetaTypeId2::MetaType 和 qreal,这两个是单独声明的,我也不知道干什么的,有可能是用在 qtdbus 中的。
这里的 qMetaTypeCustomType_unlocked 方法实际就是在搜索用户自定义类型。下面 ifndef 部分实际就是把类型名标准化之后重复了上述的搜索步骤。
那我们现在单独讨论如何设置自定义类型及变量。
3、补充 — 自定义类型
customTypes 储存了所有的用户自定义类型。
(1)(QVector, customTypes)
这个宏的作用可以直接上Qt的手册上查询,下面来看看实现。
// qglobalstatic.h
#define Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ARGS) \
namespace { namespace Q_QGS_ ## NAME { \
typedef TYPE Type; \
QBasicAtomicInt guard = Q_BASIC_ATOMIC_INITIALIZER(QtGlobalStatic::Uninitialized); \
Q_GLOBAL_STATIC_INTERNAL(ARGS) \
} } \
static QGlobalStatic<TYPE, \
Q_QGS_ ## NAME::innerFunction, \
Q_QGS_ ## NAME::guard> NAME;
#define Q_GLOBAL_STATIC(TYPE, NAME) \
Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ())
说实话,这个匿名空间嵌套命名空间,我看了很久没看出来作用。
a. QtGlobalStatic::Uninitialized
namespace QtGlobalStatic {
enum GuardValues {
Destroyed = -2,
Initialized = -1,
Uninitialized = 0,
Initializing = 1
};
}
这里的 QtGlobalStatic::Uninitialized 是Qt定义的全局变量值的枚举。看着名称有点神奇,莫不是靠状态控制Qt初始化进程?
b. QBasicAtomicInt
这个是Qt的一个原子操作类。以前我一直以为直接对 int、char 等基本类型的操作都是原子类型。这次上网看了看原子操作浅谈。基本类型是否为原子类型操作取决于编译系统生成的汇编指令个数。
那么什么是原子操作呢?原子操作是指不会被线程调度机制打断的操作。所以这种声明为原子类型的数据都是线程安全的。那么这里就不过多展开了,因为原子类型的实现多跟处理器有关。
那么这里把 guard 声明为原子类型变量就是保证该变量只会被初始化一次,即使这个宏在多处被引用。
c. innerFunction
这个方法的提供是为了保证手册里说的宏 Q_GLOBAL_STATIC 可以被当做指针使用。查看结构体 QGlobalStatic 的 * 、_->__ 就会发现他们实际上都是通过这个函数进行调用。这个方法实际就是类似于单例模式。保证只有一个静态变量被创建。
// qglobalstatic.h
#define Q_GLOBAL_STATIC_INTERNAL(ARGS) \
Q_DECL_HIDDEN inline Type *innerFunction() \
{ \
static Type *d; \
static QBasicMutex mutex; \
int x = guard.loadAcquire(); \
if (Q_UNLIKELY(x >= QtGlobalStatic::Uninitialized)) { \
const std::lock_guard<QBasicMutex> locker(mutex); \
if (guard.loadRelaxed() == QtGlobalStatic::Uninitialized) { \
d = new Type ARGS; \
static struct Cleanup { \
~Cleanup() { \
delete d; \
guard.storeRelaxed(QtGlobalStatic::Destroyed); \
} \
} cleanup; \
guard.storeRelease(QtGlobalStatic::Initialized); \
} \
} \
return d; \
}
那么上述代码就是创建了 QGlobalStatic 的变量 NAME。在调用此变量时实际是调用了更内部的静态变量 d。注意,这里的 _guard 变量正如我们上面推测的,是用于控制变量状态的。由于其被声明为原子类型,因此我们可以使用它来控制全局变量只被初始化一次。
现在我们知道用户自定义类型是个vector,接下来我们就要寻找怎样能向这个vector插值和获取。
(2)qMetaTypeCustomType_unlocked
// qmetatype.cpp
static int qMetaTypeCustomType_unlocked(const char *typeName, int length, int *firstInvalidIndex = nullptr)
{
const QVector<QCustomTypeInfo> * const ct = customTypes();
if (!ct)
return QMetaType::UnknownType;
if (firstInvalidIndex)
*firstInvalidIndex = -1;
for (int v = 0; v < ct->count(); ++v) {
const QCustomTypeInfo &customInfo = ct->at(v);
if ((length == customInfo.typeName.size())