Programming
C++
Qt
March 2013 2

QML и C++. Гоняем данные, оцениваем скорость взаимодействия

From Sandbox
О том как отправлять данные из QML в C++ и после манипуляций с ними возвращать их (данные) обратно, было уже неоднократно рассказано. В большинстве статей приводятся одни и те же примеры или, в лучшем случае, слегка измененные авторами статей.

Давайте рассмотрим пару самых явных способов передавать данные из QML в C++ и обратно. Также оценим их эффективность.

Ну, пожалуй, начнем


Берем самое простое, что может прийти в голову: есть окно с текстом, при нажатии на текст, нам нужно сделать какие-нибудь манипуляции с этим текстом средствами c++ и после показать конечный вариант в этом же окне.

Первый вариант реализации столь сложной задачи: из QML вызываем слот, описанный в C++ классе и передаем ему наш текст. После изменения текста вызываем сигнал и вместе с этим сигналом передаем измененный текст в QML.

Файл main.cpp | Создаем экземпляр класса, в котором описаны наш сигнал и слот, декларируем контекстное свойство (как бы передаем ссылку на наш класс в qml)
#include <QApplication>
#include "qmlapplicationviewer.h"
#include <QDeclarativeContext>
#include "asd.h"

Q_DECL_EXPORT int main(int argc, char *argv[])
{
    QScopedPointer<QApplication> app(createApplication(argc, argv));

    QmlApplicationViewer viewer;

    asd ASD;
    viewer.rootContext()->setContextProperty("ASD", &ASD);

    viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
    viewer.setMainQmlFile(QLatin1String("qml/habr1/main.qml"));

    viewer.showExpanded();

    return app->exec();
}


Файл asd.h (заголовок нашего класса с сигналом и слотом)
#ifndef ASD_H
#define ASD_H

#include <QObject>

class asd : public QObject
{
    Q_OBJECT

public:
    explicit asd(QObject *parent = 0);
    
signals:
    void transmitNewText(QString text);
    
public slots:
    void getOldText(QString text);
};

#endif // ASD_H


Файл asd.cpp (описываем наш слот) | Как видно из кода, слот принимает текст, добавляет к нему слово и после вызывает сигнал
#include "asd.h"

asd::asd(QObject *parent) :
    QObject(parent)
{
}

void asd::getOldText(QString text){
    emit transmitNewText(text + " Hello!");
}


Ну и main.qml (наше окно с текстом) | По нажатию кнопки мыши (onClicked) вызываем слот и ждем сигнал (onTransmitNewText) при получении которого меняем текст сообщения в нашем окне
import QtQuick 1.1

Rectangle {
    width: 360
    height: 360

    Connections {
        target: ASD
        onTransmitNewText: text1.text = text
    }

    Text {
        id: text1
        text: qsTr("Hello World.")
        anchors.centerIn: parent
    }

    MouseArea {
        anchors.fill: parent
        onClicked: ASD.getOldText(text1.text)
    }
}


На этом скучная часть заканчивается. Давайте это включим и посмотрим, сколько времени требуется на вызов слота и отправку нашего измененного текста с сигналом.



Для чистоты эксперимента кликнем на тексте 20 раз:


Видим две строки, называющиеся «Сигналы». Открываем временную шкалу, убеждаемся, что для оценки скорости всего процесса нам нужна только верхняя строка с нашей картинки т.к. именно она длится с самого начала вызова слота до момента завершения отправки сигнала:


Второй вариант реализации: при нажатии на текст генерируем сигнал, соединяем этот сигнал со слотам уже знакомого нам класса, после чего все тем же путем после изменения текста вызываем все тот же сигнал и отвечаем на него слотом, описанным в QML.

На первый взгляд может показаться, что для этого потребуется больше времени, чем в первом варианте.

Изменяем наш main.cpp (тут все просто и понятно, соединяем слоты и сигналы)
#include <QApplication>
#include "qmlapplicationviewer.h"
#include <QGraphicsObject>
#include "asd.h"

Q_DECL_EXPORT int main(int argc, char *argv[])
{
    QScopedPointer<QApplication> app(createApplication(argc, argv));

    QmlApplicationViewer viewer;
    viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
    viewer.setMainQmlFile(QLatin1String("qml/habr2/main.qml"));

    asd ASD;
    QObject *qml = viewer.rootObject();

    QObject::connect(qml, SIGNAL(transmitOldText(QString)),
                     &ASD, SLOT(getOldText(QString)));

    QObject::connect(&ASD, SIGNAL(transmitNewText(QVariant)),
                     qml, SLOT(getNewText(QVariant)));

    viewer.showExpanded();

    return app->exec();
}


asd.h (немного изменяем заголовок нашего класса) | т.к. в qml определяя функцию нельзя явно указать тип данных, придется заменить QString на QVariant
#ifndef ASD_H
#define ASD_H

#include <QObject>
#include <QVariant>

class asd : public QObject
{
    Q_OBJECT

public:
    explicit asd(QObject *parent = 0);

signals:
    void transmitNewText(QVariant text);

public slots:
    void getOldText(QString text);
};

#endif // ASD_H


asd.cpp (добавлен все тот же QVariant)
#include "asd.h"

asd::asd(QObject *parent) :
    QObject(parent)
{
}

void asd::getOldText(QString text){
    emit transmitNewText(QVariant(text + " Hello!"));
}


Ну и наше новое окно main.qml
import QtQuick 1.1

Rectangle {
    id: rth
    width: 360
    height: 360

    function getNewText(text){
        text1.text = text
    }

    signal transmitOldText(string text)

    Text {
        id: text1
        text: qsTr("Hello World.")
        anchors.centerIn: parent
    }

    MouseArea {
        anchors.fill: parent
        onClicked: transmitOldText(text1.text)
    }
}


Проверим, сколько времени нам понадобится на этот раз:


По просьбам читателей, добавим в наше тестирование функцию, возвращающую значение


Итак, main.cpp остается точно таким, каким и был в первом варианте. Внем все также декларируется контекстное свойство (ссылка на экземпляр нашего класса)
asd ASD;
viewer.rootContext()->setContextProperty("ASD", &ASD);


Слегка изменяем наш C++ слот (теперь он возвращает значение)
QString asd::getOldTextTransmitNewText(QString text){
    return text + " Hello!";
}


После чего немного меняем main.qml
MouseArea {
        anchors.fill: parent
        onClicked: text1.text = ASD.getOldTextTransmitNewText(text1.text)
    }


Включаем, смотрим на затраченное время:


Этот же вариант, только с Q_INVOKABLE
Q_INVOKABLE
    QString getOldTextTransmitNewText(QString text);


Оказывается совсем немного медленнее слота:


Подводим итоги (напоминаю, что приведенные миллисекунды — это сумма для 20 вызовов)


Самым долгим оказался первый способ (вызов слота из qml с последующей ловлей сигнала): 14.885 мс.
Более быстрым оказывается второй вариант (где мы соединяли слоты и сигналы): 13.993 мс.
Самым быстрый вариант — вызов функции, возвращающей значение: 13.456 мс. для слота и 13.508 мс. для Q_INVOKABLE (в пределах погрешности).

Да, штука, конечно, не шибко заметная, но никто ведь не ожидал отличий миллисекунд эдак в 20. Можно сослаться на погрешность, но при многократном повторении сего, если можно так сказать, эксперимента, результат оказывался примерно равен приведенному в этой статье.

Зачем и для кого все это делалось? Мне в голову почему-то пришла именно эта мысль, ибо такой вот «ерундой» я никогда еще не занимался. Ну а после решил поделиться результатами с вами.
+12
18.1k 64
Comments 11
Top of the day