Skip to content

Instantly share code, notes, and snippets.

@andref
Created May 30, 2012 19:47
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save andref/2838534 to your computer and use it in GitHub Desktop.
Save andref/2838534 to your computer and use it in GitHub Desktop.
Calling QMetaMethods with QVariant arguments and best-effort type conversion
#include <QtCore>
#include <QtDebug>
QVariant call(QObject* object, QMetaMethod metaMethod, QVariantList args)
{
// Convert the arguments
QVariantList converted;
// We need enough arguments to perform the conversion.
QList<QByteArray> methodTypes = metaMethod.parameterTypes();
if (methodTypes.size() < args.size()) {
qWarning() << "Insufficient arguments to call" << metaMethod.signature();
return QVariant();
}
for (int i = 0; i < methodTypes.size(); i++) {
const QVariant& arg = args.at(i);
QByteArray methodTypeName = methodTypes.at(i);
QByteArray argTypeName = arg.typeName();
QVariant::Type methodType = QVariant::nameToType(methodTypeName);
QVariant::Type argType = arg.type();
QVariant copy = QVariant(arg);
// If the types are not the same, attempt a conversion. If it
// fails, we cannot proceed.
if (copy.type() != methodType) {
if (copy.canConvert(methodType)) {
if (!copy.convert(methodType)) {
qWarning() << "Cannot convert" << argTypeName
<< "to" << methodTypeName;
return QVariant();
}
}
}
converted << copy;
}
QList<QGenericArgument> arguments;
for (int i = 0; i < converted.size(); i++) {
// Notice that we have to take a reference to the argument, else
// we'd be pointing to a copy that will be destroyed when this
// loop exits.
QVariant& argument = converted[i];
// A const_cast is needed because calling data() would detach
// the QVariant.
QGenericArgument genericArgument(
QMetaType::typeName(argument.userType()),
const_cast<void*>(argument.constData())
);
arguments << genericArgument;
}
QVariant returnValue(QMetaType::type(metaMethod.typeName()),
static_cast<void*>(NULL));
QGenericReturnArgument returnArgument(
metaMethod.typeName(),
const_cast<void*>(returnValue.constData())
);
// Perform the call
bool ok = metaMethod.invoke(
object,
Qt::DirectConnection,
returnArgument,
arguments.value(0),
arguments.value(1),
arguments.value(2),
arguments.value(3),
arguments.value(4),
arguments.value(5),
arguments.value(6),
arguments.value(7),
arguments.value(8),
arguments.value(9)
);
if (!ok) {
qWarning() << "Calling" << metaMethod.signature() << "failed.";
return QVariant();
} else {
return returnValue;
}
}
class Target : public QObject
{
Q_OBJECT;
public:
explicit Target(QObject* parent = 0) : QObject(parent) {
}
Q_INVOKABLE virtual void foo(qulonglong val) {
qDebug() << "Value:" << val;
}
};
int main(int argc, char** argv) {
QCoreApplication a(argc, argv);
Target* t = new Target();
int index = t->metaObject()->indexOfMethod("foo(qulonglong)");
QMetaMethod metaMethod = t->metaObject()->method(index);
QVariantList list;
list << QVariant("10");
call(t, metaMethod, list);
a.exec();
}
#include <main.moc>
TEMPLATE = app
TARGET =
DEPENDPATH += .
INCLUDEPATH += .
SOURCES += main.cpp
@joncol
Copy link

joncol commented Oct 9, 2015

Nice!

@wxmaper
Copy link

wxmaper commented Mar 23, 2016

Good stuff! I'm used this to create implementation of objectFactory PHPQt5ObjectFactory::createObject
thanks a lot :)

@Sharm
Copy link

Sharm commented Oct 26, 2016

Amazing, you save my day.

The most important things, that makes me confused is

        // Notice that we have to take a reference to the argument, else 
        // we'd be pointing to a copy that will be destroyed when this
        // loop exits. 

        QVariant& argument = converted[i];

        // A const_cast is needed because calling data() would detach
        // the QVariant.

        QGenericArgument genericArgument(
            QMetaType::typeName(argument.userType()),
            const_cast<void*>(argument.constData())
        );

Thanks a lot for comments.

@ncorgan
Copy link

ncorgan commented Feb 22, 2021

This is exactly what I need, thanks!

@elbakramer
Copy link

I think preventing detach here is meaningless for both arguments and return value.
I mean, at least for Qt 6.6.0 currently I'm looking around as of now.

From what I understand, detach becomes meaningful when different QVariants share a same resource.
But here,

  • QGenericArgument accepts const void* for data.
    So just directly passing const void* from constData() would suffice. (constData() won't detach any data)
    Casting to void* using const_cast<void*> to give const void* type parameter is meaningless here.
  • QGenericReturnArgument accepts void* for data.
    The QVarant that we are newly creating definitely shares nothing, no worries about detach.
    So just directly passing void* from data() would suffice.

So just call constData() for argument and data() for return value when creating generic arguments.

// argument
QGenericArgument genericArgument(
    argument.typeName(),
    argument.constData()
);

// return value
QGenericReturnArgument returnArgument(
    metaMethod.typeName(),
    returnValue.data()
);

Note: the original implementation would return QVarant that would give isNull() == false always, even when internal data is written with some meaningful data.

@elbakramer
Copy link

Also it seems that the code around line 13 conflicts with the comment (when checking whether we have enough arguments):

    // We need enough arguments to perform the conversion.

    QList<QByteArray> methodTypes = metaMethod.parameterTypes();
    if (methodTypes.size() < args.size()) { // Note: this checks whether we have more args than we need, not whether it's insufficient.
        qWarning() << "Insufficient arguments to call" << metaMethod.signature();
        return QVariant();
    }

https://gist.github.com/andref/2838534#file-main-cpp-L13

And I think a possible attempt for that would be like: args.size() < metaMethod.parameterCount()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment