3
votes

I want to call a C++ function from QML/JS, which takes a C++ class (data object) instantiated in QML as argument. The program compiles and runs, however when I try to call the C++ function from QML/JS I get the following error:

"Could not convert argument 0 at" "onClicked@qrc:/main.qml:26" "Passing incompatible arguments to C++ functions from JavaScript is dangerous and deprecated." "This will throw a JavaScript TypeError in future releases of Qt!"

I have tried all combination of call by reference/value/pointer, as well as multiple variations with Q_GADGET and Q_OBJECT but I simply cannot get it to work.

My minimum Example with QObject based C++ data type as well as all the call combinations is as follows:

C++ data type


#include <QObject>
#include <QDebug>

//Data type which should be instantiated in QML
class CustomDataObject : public QObject{

    Q_OBJECT
    Q_PROPERTY(int exampleValue MEMBER exampleValue)

public:

    int exampleValue;

    explicit CustomDataObject(QObject * parent = nullptr) : QObject(parent) {}
    ~CustomDataObject() = default;
    CustomDataObject(const CustomDataObject& blob)  {}

};

Q_DECLARE_METATYPE(CustomDataObject);

C++ Class with function to call


//Class with function called from QML
class UniqueDataBackend : public QObject {

    Q_OBJECT

public:

    //Call with native data type (works)
    Q_INVOKABLE void processNativeObject(int value) {
        qDebug() << "C++: Nativ Value: " << value;
    }

    //Call by value (does not work)
    Q_INVOKABLE void processDataObjectByValue(const CustomDataObject data) {
        qDebug() << "C++: Data Object Value: " << data.exampleValue;
    }

    //Call by reference (does not work)
    Q_INVOKABLE void processDataObjectByReference(const CustomDataObject & data) {
        qDebug() << "C++: Data Object Value: " << data.exampleValue;
    }

    //Call by pointer (does not work)
    Q_INVOKABLE void processDataObjectByPointer(const CustomDataObject * data) {
        qDebug() << "C++: Data Object Value: " << data->exampleValue;
    }


    UniqueDataBackend(QObject * parent = nullptr) : QObject(parent) {}
    ~UniqueDataBackend() = default;
    UniqueDataBackend(const UniqueDataBackend& blob)  {}
};

Q_DECLARE_METATYPE(UniqueDataBackend);

main.cpp


#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQmlEngine>

#include "CustomStruct.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    //Register types for use in QML
    qRegisterMetaType<CustomDataObject>();
    qmlRegisterType<CustomDataObject>("Custom.Types", 1, 0, "CustomDataObject");

    //Register intances for use in QML
engine.rootContext()->setContextProperty("UniqueDataBackend", new UniqueDataBackend());

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}


QML File (main.qml)


import QtQuick 2.12
import QtQuick.Controls 2.12
import Custom.Types 1.0

ApplicationWindow {
    id: window
    width: 400
    height: 300
    visible: true

    CustomDataObject {
        id: customData
        exampleValue: 84
    }

    Button {
        text: "Click me!"
        anchors.fill: parent
        onClicked: {
            console.log("QML: Calling c++ function with native data type!")
            UniqueDataBackend.processNativeObject(42)

            console.log("QML: Checking value of custom data type in javascript: " + customData.exampleValue)

            console.log("QML: Calling c++ function by value with custom data type!")
            UniqueDataBackend.processDataObjectByValue(customData)

            console.log("QML: Calling c++ function by reference with custom data type!")
            UniqueDataBackend.processDataObjectByReference(customData)

            console.log("QML: Calling c++ function by pointer with custom data type!")
            UniqueDataBackend.processDataObjectByPointer(customData)
        }
    }


}


Output


qml: QML: Calling c++ function with native data type!
C++: Nativ Value:  42

qml: QML: Checking value of custom data type in javascript: 84

qml: QML: Calling c++ function by value with custom data type!
"Could not convert argument 0 at"
     "onClicked@qrc:/main.qml:26"
"Passing incompatible arguments to C++ functions from JavaScript is dangerous and deprecated."
"This will throw a JavaScript TypeError in future releases of Qt!"
C++: Data Object Value:  0

qml: QML: Calling c++ function by reference with custom data type!
"Could not convert argument 0 at"
     "onClicked@qrc:/main.qml:29"
"Passing incompatible arguments to C++ functions from JavaScript is dangerous and deprecated."
"This will throw a JavaScript TypeError in future releases of Qt!"
C++: Data Object Value:  -842150451

qml: QML: Calling c++ function by pointer with custom data type!
qrc:/main.qml:32: Error: Unknown method parameter type: const CustomDataObject*

I assume that it is a problem with custom data type as I can call a function with native data types without a problem, but for the life of me I cannot figure it out.

Thank you very much for your help/comments!

1
What about non const pointer?GrecKo
It works! I have no idea how I have overlooked that. Thank you a lot! Want to write it as an answer so I can mark it solved?ghst

1 Answers

1
votes

The error reporting an unknown type (Error: Unknown method parameter type: const CustomDataObject*) is fixable by registering the const type as well:

qRegisterMetaType<CustomDataObject*>("const CustomDataObject*");

The error about not being able to convert ("Could not convert argument 0 at...") is trickier. Type conversion in QML uses QT's meta type system, which is based on QVariant. The built-in conversions are listed at the QVariant.canConvert() documentation. Also there you can read that conversion between pointers to subtypes of QObject are automatic. That's why your processDataObjectByPointer works, if you register the const type as mentioned above.

But I could not find a mention about passing objects as value or reference other than the error message "Passing incompatible arguments to C++ functions from JavaScript is dangerous and deprecated." Obviously the types are compatible, but the message hints me that the correct would be to pass values and references as QJSValue. The method below works as you would expect for a reference. Same applies for a "by value" equivalent:

void UniqueDataBackend::processDataObjectByQJSValue(const QJSValue& data) {
    qDebug() << "C++: Data Object Value: "  << qobject_cast<CustomDataObject*>(data.toQObject())->getExample();
}

Out of curiosity, I checked if it was possible to register a function converter with QMetaType.registerConverter<From,CustomDataObject> so you can invoke methods passing objects by value and reference. The hard part here was to know which is the From type that Qml used internally. After installing QT's debug symbols, source code, and putting breaking points at few places, I found that its ID was 39. By inspecting the table of built-in types in QMetaType, it is QObject*. The use of a converter requires to implement the assignment operator:

CustomDataObject &CustomDataObject::operator=(const CustomDataObject &other) {
   exampleValue = other.exampleValue;
   return *this;
}

The following code installs a converter that does the trick for invocation by value and const reference. But not by reference since the actual argument is the copy of the QML object created at the converter; thus changes are not reflected at the original. Keep in mind that this is not officially advised, and is totally dependent on internal representation (it worked for me with QT 5.12.8):

QMetaType::registerConverter<QObject*,CustomDataObject>( [] (QObject* qObjPtr) {
    CustomDataObject* dataPtr = qobject_cast<CustomDataObject*>( qObjPtr ); 
    return (dataPtr == nullptr) ? CustomDataObject() : CustomDataObject( *dataPtr ) ;
});