[ad_1]
We are developing a test tool for Qt/QML apps based on the Petri’s Nets and looking for a legal hack to reimplement Connections
a bit differently, but without any private API.
Consider a Petri’s transition declared in a QML component like:
Transition {
act: {
obj1.callAsyncOperation1();
obj2.callAsyncOperation2();
//...
}
TimeWaiter {
timeout: 100
}
ObjectWaiter {
target: obj1
function onAsyncOperation1Done(arg1: int, arg2: CustomAnonymousType)
{
console.log("...");
}
function onAsyncOperation1Failed(message: string, fatal: bool)
{
console.error("...");
}
}
}
act
is called from c++ logic- wait for any specified signal if some *Waiters are declared in a
Transition
- go to the next step (according to the PN idea after an
Action
phase in aTransition
we are switching to anAssertion
stage declared in somePlace
but this out of scope and just FYI)
Just problem we have in the current implementation is accessing custom types within signals due to nature of binding with dynamically declared slots. What we need are connections between signals from a target
and all declared slots to test& debug purposes + a bypass signal from a target
to a Transition
as a permit to move on.
a) values are undefined if types are not declared in slots at all
b) some kind of UB happens and app is crashing time to time if types are declared as variants
c) works as expected just if types are fully and properly(according to a signal) defined
The corner case of this story are custom types which don’t have typenames in the QML scope which are registered via qmlRegisterAnonymousType() or qRegisterMetaType().
Is there any method to make a connection between signals and slots with such arguments?
Now we are using QMetaObject::connect
and finally, in qqmlvmemetaobject.cpp
it boils down to:
for (uint ii = 0; ii < parameterCount; ++ii) {
jsCallData->args[ii] = scope.engine->metaTypeToJS(arguments->arguments[ii + 1], a[ii + 1]);
}
const int returnType = methodData->propType();
QV4::ScopedValue result(scope, function->call(jsCallData));
metaTypeToJS(...)
are converting all undefined arguments to QV4::Value(Null) through intermediate QV4::Encode::undefined
:
QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant)
{
// ...
if (type < QMetaType::User) {
switch (QMetaType::Type(type)) {
case QMetaType::UnknownType:
case QMetaType::Void:
return QV4::Encode::undefined();
Real code where we are remembering declared slots:
void ObjectWaiter::componentComplete()
{
const QMetaObject* meta_object(metaObject());
if (!meta_object) {
return ;
}
for (int m = meta_object->methodOffset(), count = meta_object->methodOffset() + meta_object->methodCount(); m < count; ++m) {
const QMetaMethod meta_method(meta_object->method(m));
if (meta_method.methodType() == QMetaMethod::MethodType::Slot) {
QByteArray slot_name(meta_method.name().remove(0, 2));
if (!slot_name.isEmpty()) {
slot_name.front() = QChar(slot_name.front()).toLower().toLatin1();
_slot_map.insert(slot_name, meta_method);
}
}
}
bind();
}
… and connecting signals to slots when target
is changed:
void ObjectWaiter::bind()
{
if (!_target || _slot_map.isEmpty()) {
return ;
}
const QMetaObject* meta_object(_target->metaObject());
if (!meta_object) {
return ;
}
for (int m = meta_object->methodOffset(), count = meta_object->methodOffset() + meta_object->methodCount(); m < count; ++m) {
const QMetaMethod meta_method(meta_object->method(m));
if (meta_method.methodType() == QMetaMethod::MethodType::Signal) {
QMap<QByteArray, QMetaMethod>::ConstIterator m(_slot_map.find(meta_method.name()));
if (_slot_map.constEnd() != m) {
std::unique_ptr<int[]> argv(new int[meta_method.parameterCount()]{});
for (int p = 0; p < meta_method.parameterCount(); ++p) {
int parameter_type(meta_method.parameterType(p));
if (QMetaType::UnknownType != parameter_type) {
argv[p] = parameter_type;
} else {
void* arg[] = { ¶meter_type, &p };
QMetaObject::metacall(_target, QMetaObject::RegisterMethodArgumentMetaType, meta_method.methodIndex(), arg);
if (parameter_type == -1) {
argv[p] = QMetaType::UnknownType;
qWarning(
"ObjectWaiter: Unable to handle parameter '%s' of type '%s' of method '%s', use qRegisterMetaType to register it.",
meta_method.parameterNames().at(p).constData(), meta_method.parameterTypes().at(p).constData(), meta_method.name().constData()
);
} else {
argv[p] = parameter_type;
}
}
}
if (!QMetaObject::connect(_target, meta_method.methodIndex(), this, m.value().methodIndex(), Qt::DirectConnection, argv.release())) {
qWarning("ObjectWaiter: Unable to make a dynamic connect");
}
if (!QObject::connect(_target, meta_method, this, QMetaMethod::fromSignal(&AbstractWaiter::done))) {
qWarning("ObjectWaiter: Unable to make the connection and won't be able to emit a stop signal at the end");
}
}
}
}
}
I was digging around Connections
and QSpySingal
implementations but could not find any other public API to reimplement ObjectWaiter
, but Qt is huge and hopefully there are something I have missed.
[ad_2]