Qt MetaObject System详解之四:meta call
2018-03-30 14:58
615 查看
所谓meta call就是通过object的meta system的支持来动态调用object的方法,metacall也是signal&slot的机制的基石。本篇通过参考源代码来探究meta call的实现方法。
return false;
QVarLengthArray<char, 512> sig;
int len = qstrlen(member);
if (len <= 0)
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 = 1; paramCount < MaximumParamCount; ++paramCount) {
len = qstrlen(typeNames[paramCount]);
if (len <= 0)
break;
sig.append(typeNames[paramCount], len);
sig.append(',');
}
if (paramCount == 1)
sig.append(')'); // no parameters
else
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 || idx >= obj->metaObject()->methodCount())
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方法。view plaincopy to clipboardprint?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;
// check return type
if (returnValue.data()) {
const char *retType = typeName();
if (qstrcmp(returnValue.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 unnormalized;
int len = qstrlen(returnValue.name());
unnormalized.reserve(len + 3);
unnormalized = "_("; // the function is called "_"
unnormalized.append(returnValue.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 false;
}
}
// check argument count (we don't allow invoking a method if given too few arguments)
const char *typeNames[] = {
returnValue.
150b1
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 = 1; paramCount < MaximumParamCount; ++paramCount) {
if (qstrlen(typeNames[paramCount]) <= 0)
break;
}
int metaMethodArgumentCount = 0;
{
// based on QMetaObject::parameterNames()
const char *names = mobj->d.stringdata + mobj->d.data[handle + 1];
if (*names == 0) {
// 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;
}
// 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 methodIndex = ((handle - priv(mobj->d.data)->methodData) / 5) + mobj->methodOffset();
if (connectionType == Qt::DirectConnection) {
return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param) < 0;
} else {
if (returnValue.data()) {
qWarning("QMetaMethod::invoke: Unable to invoke methods with return values in "
"queued connections");
return false;
}
int nargs = 1; // 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[0] = 0; // return type
args[0] = 0;
for (int i = 1; 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 = 1; x < i; ++x) {
if (types[x] && args[x])
QMetaType::destroy(types[x], args[x]);
}
qFree(types);
qFree(args);
return false;
}
}
if (connectionType == Qt::QueuedConnection) {
QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,
0,
-1,
nargs,
types,
args));
} else {
if (currentThread == objectThread) {
qWarning("QMetaMethod::invoke: Dead lock detected in "
"BlockingQueuedConnection: Receiver is %s(%p)",
mobj->className(), object);
}
// blocking queued connection
#ifdef QT_NO_THREAD
QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,
0,
-1,
nargs,
types,
args));
#else
QSemaphore semaphore;
QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,
0,
-1,
nargs,
types,
args,
&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的实现如下:view plaincopy to clipboardprint?/*!
\internal
*/
int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)
{
if (QMetaObject *mo = object->d_ptr->metaObject)
return static_cast(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函数里有如下代码:view plaincopy to clipboardprint? case QEvent::MetaCall:
{
d_func()->inEventHandler = false;
QMetaCallEvent *mce = static_cast(e);
QObjectPrivate::Sender currentSender;
currentSender.sender = const_cast(mce->sender());
currentSender.signal = mce->signalId();
currentSender.ref = 1;
QObjectPrivate::Sender * const previousSender =
QObjectPrivate::setCurrentSender(this, ¤tSender);
#if defined(QT_NO_EXCEPTIONS)
mce->placeMetaCall(this);
#else
QT_TRY {
mce->placeMetaCall(this);
} QT_CATCH(...) {
QObjectPrivate::resetCurrentSender(this, ¤tSender, previousSender);
QT_RETHROW;
}
#endif
QObjectPrivate::resetCurrentSender(this, ¤tSender, previousSender);
break;
}
QMetaCallEvent的代码很简单:int QMetaCallEvent::placeMetaCall(QObject *object)
{ return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, id_, args_);}殊途同归。最后来看一下object->qt_metacall是如何实现的,这又回到了该系统之二所提供的示例moc文件中去了。该文件提供了该方法的实现:view plaincopy to clipboardprint?# int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
# {
# _id = QObject::qt_metacall(_c, _id, _a);
# if (_id < 0)
# return _id;
# if (_c == QMetaObject::InvokeMetaMethod) {
# switch (_id) {
# case 0: clicked(); break;
# case 1: pressed(); break;
# case 2: onEventA((*reinterpret_cast< const QString(*)>(_a[1]))); break;
# case 3: onEventB((*reinterpret_cast< int(*)>(_a[1]))); break;
# default: ;
# }
# _id -= 4;
# }
# #ifndef QT_NO_PROPERTIES
# else if (_c == QMetaObject::ReadProperty) {
# void *_v = _a[0];
# switch (_id) {
# case 0: *reinterpret_cast< QString*>(_v) = getPropertyA(); break;
# case 1: *reinterpret_cast< QString*>(_v) = getPropertyB(); break;
# }
# _id -= 2;
# } else if (_c == QMetaObject::WriteProperty) {
# void *_v = _a[0];
# switch (_id) {
# case 0: getPropertyA(*reinterpret_cast< QString*>(_v)); break;
# case 1: getPropertyB(*reinterpret_cast< QString*>(_v)); break;
# }
# _id -= 2;
# } else if (_c == QMetaObject::ResetProperty) {
# switch (_id) {
# case 0: resetPropertyA(); break;
# case 1: resetPropertyB(); break;
# }
# _id -= 2;
# } else if (_c == QMetaObject::QueryPropertyDesignable) {
# _id -= 2;
# } else if (_c == QMetaObject::QueryPropertyScriptable) {
# _id -= 2;
# } else if (_c == QMetaObject::QueryPropertyStored) {
# _id -= 2;
# } else if (_c == QMetaObject::QueryPropertyEditable) {
# _id -= 2;
# } else if (_c == QMetaObject::QueryPropertyUser) {
# _id -= 2;
# }
# #endif // QT_NO_PROPERTIES
# return _id;
# }
这段代码将调用最终转到我们自己的实现的函数中来。这个函数不经提供了metamethod的动态调用,而且也提供了property的动态操作方法。可想而知,property的动态调用的实现方式一定和invocalbe method是一致的。
QMetaObject::invokeMethod():
bool invokeMethod ( QObject * obj , const char * member , Qt::ConnectionType type , 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() )QMetaObject这个静态方法可以动态地调用obj对象名字为member的方法,type参数表明该调用时同步的还是异步的。ret是一个 通用的用来存储返回值的类型,后面的9个参数是用来传递调用参数的,QGenericArgument()是一种通用的存储参数值的类型。(这里让人感觉 比较奇怪的是Qt为什么不将这个参数列表弄成某种动态的形式,而是最多九个)所调用的方法必须是invocable的,也就是signal,slot或者是加了声明为Q_INVOCABLE的其他方法。这个方法的实现如下:view plaincopy to clipboardprint?if (!obj)return false;
QVarLengthArray<char, 512> sig;
int len = qstrlen(member);
if (len <= 0)
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 = 1; paramCount < MaximumParamCount; ++paramCount) {
len = qstrlen(typeNames[paramCount]);
if (len <= 0)
break;
sig.append(typeNames[paramCount], len);
sig.append(',');
}
if (paramCount == 1)
sig.append(')'); // no parameters
else
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 || idx >= obj->metaObject()->methodCount())
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方法。view plaincopy to clipboardprint?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;
// check return type
if (returnValue.data()) {
const char *retType = typeName();
if (qstrcmp(returnValue.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 unnormalized;
int len = qstrlen(returnValue.name());
unnormalized.reserve(len + 3);
unnormalized = "_("; // the function is called "_"
unnormalized.append(returnValue.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 false;
}
}
// check argument count (we don't allow invoking a method if given too few arguments)
const char *typeNames[] = {
returnValue.
150b1
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 = 1; paramCount < MaximumParamCount; ++paramCount) {
if (qstrlen(typeNames[paramCount]) <= 0)
break;
}
int metaMethodArgumentCount = 0;
{
// based on QMetaObject::parameterNames()
const char *names = mobj->d.stringdata + mobj->d.data[handle + 1];
if (*names == 0) {
// 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;
}
// 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 methodIndex = ((handle - priv(mobj->d.data)->methodData) / 5) + mobj->methodOffset();
if (connectionType == Qt::DirectConnection) {
return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param) < 0;
} else {
if (returnValue.data()) {
qWarning("QMetaMethod::invoke: Unable to invoke methods with return values in "
"queued connections");
return false;
}
int nargs = 1; // 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[0] = 0; // return type
args[0] = 0;
for (int i = 1; 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 = 1; x < i; ++x) {
if (types[x] && args[x])
QMetaType::destroy(types[x], args[x]);
}
qFree(types);
qFree(args);
return false;
}
}
if (connectionType == Qt::QueuedConnection) {
QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,
0,
-1,
nargs,
types,
args));
} else {
if (currentThread == objectThread) {
qWarning("QMetaMethod::invoke: Dead lock detected in "
"BlockingQueuedConnection: Receiver is %s(%p)",
mobj->className(), object);
}
// blocking queued connection
#ifdef QT_NO_THREAD
QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,
0,
-1,
nargs,
types,
args));
#else
QSemaphore semaphore;
QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,
0,
-1,
nargs,
types,
args,
&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的实现如下:view plaincopy to clipboardprint?/*!
\internal
*/
int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)
{
if (QMetaObject *mo = object->d_ptr->metaObject)
return static_cast(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函数里有如下代码:view plaincopy to clipboardprint? case QEvent::MetaCall:
{
d_func()->inEventHandler = false;
QMetaCallEvent *mce = static_cast(e);
QObjectPrivate::Sender currentSender;
currentSender.sender = const_cast(mce->sender());
currentSender.signal = mce->signalId();
currentSender.ref = 1;
QObjectPrivate::Sender * const previousSender =
QObjectPrivate::setCurrentSender(this, ¤tSender);
#if defined(QT_NO_EXCEPTIONS)
mce->placeMetaCall(this);
#else
QT_TRY {
mce->placeMetaCall(this);
} QT_CATCH(...) {
QObjectPrivate::resetCurrentSender(this, ¤tSender, previousSender);
QT_RETHROW;
}
#endif
QObjectPrivate::resetCurrentSender(this, ¤tSender, previousSender);
break;
}
QMetaCallEvent的代码很简单:int QMetaCallEvent::placeMetaCall(QObject *object)
{ return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, id_, args_);}殊途同归。最后来看一下object->qt_metacall是如何实现的,这又回到了该系统之二所提供的示例moc文件中去了。该文件提供了该方法的实现:view plaincopy to clipboardprint?# int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
# {
# _id = QObject::qt_metacall(_c, _id, _a);
# if (_id < 0)
# return _id;
# if (_c == QMetaObject::InvokeMetaMethod) {
# switch (_id) {
# case 0: clicked(); break;
# case 1: pressed(); break;
# case 2: onEventA((*reinterpret_cast< const QString(*)>(_a[1]))); break;
# case 3: onEventB((*reinterpret_cast< int(*)>(_a[1]))); break;
# default: ;
# }
# _id -= 4;
# }
# #ifndef QT_NO_PROPERTIES
# else if (_c == QMetaObject::ReadProperty) {
# void *_v = _a[0];
# switch (_id) {
# case 0: *reinterpret_cast< QString*>(_v) = getPropertyA(); break;
# case 1: *reinterpret_cast< QString*>(_v) = getPropertyB(); break;
# }
# _id -= 2;
# } else if (_c == QMetaObject::WriteProperty) {
# void *_v = _a[0];
# switch (_id) {
# case 0: getPropertyA(*reinterpret_cast< QString*>(_v)); break;
# case 1: getPropertyB(*reinterpret_cast< QString*>(_v)); break;
# }
# _id -= 2;
# } else if (_c == QMetaObject::ResetProperty) {
# switch (_id) {
# case 0: resetPropertyA(); break;
# case 1: resetPropertyB(); break;
# }
# _id -= 2;
# } else if (_c == QMetaObject::QueryPropertyDesignable) {
# _id -= 2;
# } else if (_c == QMetaObject::QueryPropertyScriptable) {
# _id -= 2;
# } else if (_c == QMetaObject::QueryPropertyStored) {
# _id -= 2;
# } else if (_c == QMetaObject::QueryPropertyEditable) {
# _id -= 2;
# } else if (_c == QMetaObject::QueryPropertyUser) {
# _id -= 2;
# }
# #endif // QT_NO_PROPERTIES
# return _id;
# }
这段代码将调用最终转到我们自己的实现的函数中来。这个函数不经提供了metamethod的动态调用,而且也提供了property的动态操作方法。可想而知,property的动态调用的实现方式一定和invocalbe method是一致的。
相关文章推荐
- Qt MetaObject System详解
- Qt MetaObject System详解之二:meta数据和数据结构
- Qt MetaObject System详解之五:signal&slot
- Qt Meta Object system 学习(一)
- Qt MetaObject 详解之一:简单介绍
- Qt Meta Object system 学习(二)
- Qt Meta Object system 学习
- Qt Meta Object system 学习(三)
- Changes to the Meta-Object System in Qt 5
- Inside QT Series (三):元对象系统(Meta-Object System)
- Qt MetaObject 详解之二:QMeta数据以及数据结构信息
- Qt MetaObject System-- 元对象系统
- QtMetaObjectsysmtem详解之三:QMetaObject接口实现
- Qt Meta Object system 学习(二)
- Qt Meta Object System-元对象系统
- Qt Meta Object system 学习(一)
- Qt Meta Object system 学习(一)
- Qt Meta Object System-元对象系统
- Qt 源码之元对象(QMetaObject)
- qt QMetaObject::connectSlotsByName()自动关联失效问题解决