Evgenii Legotckoi
Evgenii Legotckoi2 февраля 2016 г. 11:10

QML - Урок 023. Охота за багами при передаче указателя на QObject в QML

Одними из самых мерзких и мало предсказуемых багов являются те, которые возникают в неопределённый момент времени. К числу таких можно отнести баг, который проявляется при передаче указателя на QObject в QML слой. Проблема заключается в том, что если у QObject отсутствует родитель, то при передаче в слой QML происходит смена владельца объекта, то есть ему устанавливается JavaScriptOwnership. В итоге, когда в QML слое пропадут все ссылки на данный объект, то он будет удалён сборщиком мусора QML. Соответственно, все ссылки в C++ слое окажутся невалидными. А приложение при попытке обращения по этим ссылкам молча схлопнется, ничего не сообщив о причине краха.

Говоря о неопределённости срабатывания бага, необходимо отметить, что эта неопределённость произрастает из момента сборки мусора. В обычном варианте этот момент может наступить тогда, когда программа откушает пару гигабайт памяти или когда памяти будет не хватать и её можно будет получить, произведя сборку мусора. То есть приложение может без проблем работать достаточно долго без проявления бага, и даже возможно пережить несколько версий, прежде чем баг проявится у какого-нибудь пользователя, который решит сообщить об этом разработчикам.

Стоит отметить, что данное правило не будет применяться к объектам объявленным в качестве Q_PROPERTY.

Но для демонстрации проблемы можно воспользоваться QML функцией gc() , которая ускорит сборку мусора.


Охота за багами

А теперь посмотрим, как это реализовано в программном коде.

Структура проекта

В данном проекте нас интересуют следующие файлы:

  • main.cpp - без его редактирования не получится передать испытуемый объект в QML слой;
  • MyClass.h - заголовочный файл класса для производства проблем;
  • MyClass.cpp - файл исходных кодов класса для производства проблем;
  • main.qml - файл QML слоя, в котором будем уничтожать указатель на объект.

MyClass.h

В тестируемом классе будет создаваться объект с помощью метода createObject(), а использоваться с помощью метода useObject(), соответственно. Как только в слое QML будет обнулён указатель и будет произведена сборка мусора, то метод useObject() положит программу.

#ifndef MYCLASS_H
#define MYCLASS_H

#include <QObject>

class MyClass : public QObject
{
    Q_OBJECT

public:
    Q_INVOKABLE QObject* createObject();    // Создание объекта для испытаний
    Q_INVOKABLE void useObject();           // Использование испытуемого объекта

private:
    QObject* m_object;      // Подопытный кролик
};

#endif // MYCLASS_H

MyClass.cpp

В метода createObject() имеется два вариант инициализации объекта m_object. Первый не закомментированый является вариантом с багом, второй будет работать стабильно. А в методе useObject() будем получать имя объекта, естественно, что в данном случае будем получать пустую строку.

#include "MyClass.h"
#include <QObject>
#include <QDebug>
#include <QString>

QObject *MyClass::createObject()
{
    m_object = new QObject;         // Создаём объект без родителя
//    m_object = new QObject(this);   // Создаём объект с родителем
    return m_object;                // Возвращаем объект
}

void MyClass::useObject()
{
    qDebug() << m_object->objectName(); // Отображаем имя объекта
}

main.cpp

Стандартная регистрация объекта для доступа в контексте QML.

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "MyClass.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MyClass mClass;

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    // Регистрируем тестовый класс в QML слое, демонстрирующий проблему
    engine.rootContext()->setContextProperty("myClass", &mClass);

    return app.exec();
}

main.qml

В проект создано три кнопки:

  1. Создаст объект и передаст его указатель свойству objectFromCppWorld;
  2. Обнулит указатель, создав баг;
  3. Попытается использовать объект C++ слоя. И метод будет работать, пока не произойдёт сборка мусора.
import QtQuick 2.5
import QtQuick.Controls 1.3

ApplicationWindow {
    width: 640
    height: 480
    visible: true

    property QtObject objectFromCppWorld: null

    /* Для демонстрации проблемы со сменой родителя объекта
     * сделаем три кнопки, которые будем активировать последовательно.
     * Первая создаст объект в C++ слое и передаст указатель на него в QML слой
     * Вторая обнулит указатель и запустит сборку мусора
     * Третья попробует использовать объект
     */
    Column {
        anchors.centerIn: parent
        Button {
            text: "create object in C++, save the pointer to it in C++ world and pass it to QML"
            onClicked: objectFromCppWorld = myClass.createObject()
        }
        Button {
            text: "null the reference in QML"
            onClicked: {
                objectFromCppWorld = null
                gc()
            }
        }
        Button {
            // Когда произойдет сборка мусора, использовать объект без родителя не получится
            text: "use created object in C++"
            onClicked: myClass.useObject()
        }
    }
}

Соавтор статьи: Владимир Курман

Видеоурок

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
SH
  • Sak Hax
  • 25 апреля 2024 г. 23:00

C++ - Тест 001. Первая программа и типы данных

  • Результат:33баллов,
  • Очки рейтинга-10
г
  • ги
  • 24 апреля 2024 г. 0:51

C++ - Тест 005. Структуры и Классы

  • Результат:41баллов,
  • Очки рейтинга-8
l
  • laei
  • 23 апреля 2024 г. 18:19

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:10баллов,
  • Очки рейтинга-10
Последние комментарии
k
kmssr9 февраля 2024 г. 4:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 11:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 20:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 18:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 декабря 2023 г. 7:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
G
Gar22 апреля 2024 г. 14:46
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil Academics20 апреля 2024 г. 16:45
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasov14 апреля 2024 г. 15:41
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел Дорофеев14 апреля 2024 г. 11:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrex4 апреля 2024 г. 13:47
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

Следите за нами в социальных сетях