Глубокая копия QScriptValue как Глобальный Объект - PullRequest
5 голосов
/ 16 февраля 2011

У меня есть программа, использующая QtScript для некоторой автоматизации. Я добавил несколько функций и классов C ++ в глобальную область действия механизма сценариев, чтобы сценарии могли получить к ним доступ, например:

QScriptValue fun = engine->newFunction( systemFunc );
engine->globalObject().setProperty( "system", fun );

Я хотел бы иметь возможность запускать несколько сценариев подряд, каждый со свежим глобальным состоянием. Так что, если один скрипт устанавливает глобальную переменную, например

myGlobalVar = "stuff";

Я хочу, чтобы эта переменная была удалена до запуска следующего скрипта. Мой способ сделать это состоит в том, чтобы сделать глубокую копию глобального объекта обработчика сценариев, а затем восстановить его, когда сценарий завершит работу. Но глубокие копии не работают, так как моя функция system неожиданно перестает работать с ошибкой:

TypeError: Result of expression 'system' [[object Object]] is not a function.

Вот моя функция глубокого копирования, адаптированная с:
http://qt.gitorious.org/qt-labs/scxml/blobs/master/src/qscxml.cpp

QScriptValue copyObject( const QScriptValue& obj, QString level = "" )
{
    if( obj.isObject() || obj.isArray() ) {
        QScriptValue copy = obj.isArray() ? obj.engine()->newArray() : obj.engine()->newObject();
        copy.setData( obj.data() );
        QScriptValueIterator it(obj);
        while(it.hasNext()) {
            it.next();
            qDebug() << "copying" + level + "." + it.name();
            if( it.flags() & QScriptValue::SkipInEnumeration )
                 continue;
            copy.setProperty( it.name(), copyObject(it.value(), level + "." + it.name()) );
        }
        return copy;
    }

    return obj;
}

(SkipInEnumeration был вставлен, чтобы избежать бесконечного цикла)

РЕДАКТИРОВАТЬ: Часть проблемы, я думаю, заключается в том, что в отладчике (QScriptEngineDebugger) добавленные мной функции и конструкторы должны отображаться как тип Function, но после копирования они отображаются как тип Object. Я еще не нашел хорошего способа создания новой функции, которая дублирует существующую функцию (QScriptEngine :: newFunction принимает фактический указатель на функцию).

Ответы [ 2 ]

2 голосов
/ 27 февраля 2016

Чтобы сделать многопоточность доступной в QtScript, мне нужен был способ глубокого копирования QScriptValue объектов в другой QScriptEngine и я наткнулся на этот вопрос. К сожалению, кода Дейва было недостаточно для этой задачи, и у него есть несколько проблем, даже при копировании в пределах одного QScriptEngine. Поэтому мне нужна была более сложная версия. Вот проблемы, которые мне пришлось решать в моем решении:

  1. Код Дейва приводит к переполнению стека, когда объект содержит ссылку на себя.
  2. Я хотел, чтобы мое решение учитывало ссылки на объекты, чтобы множественные ссылки на один объект не вызывали копирование ссылочного объекта более одного раза.
  3. Поскольку глубоко * скопированные QScriptValue объекты используются в QScriptEngine, отличном от их исходных объектов, мне нужен был способ истинного копирования, например. функции также.

Это может быть полезно для кого-то другого, поэтому вот код, который я придумал:

class ScriptCopier
{
public:
    ScriptCopier(QScriptEngine& toEngine)
        : m_toEngine(toEngine) {}

    QScriptValue copy(const QScriptValue& obj);

    QScriptEngine& m_toEngine;
    QMap<quint64, QScriptValue> copiedObjs;
};


QScriptValue ScriptCopier::copy(const QScriptValue& obj)
{
    QScriptEngine& engine = m_toEngine;

    if (obj.isUndefined()) {
        return QScriptValue(QScriptValue::UndefinedValue);
    }
    if (obj.isNull()) {
        return QScriptValue(QScriptValue::NullValue);
    }

    // If we've already copied this object, don't copy it again.
    QScriptValue copy;
    if (obj.isObject())
    {
        if (copiedObjs.contains(obj.objectId()))
        {
            return copiedObjs.value(obj.objectId());
        }
        copiedObjs.insert(obj.objectId(), copy);
    }

    if (obj.isQObject())
    {
        copy = engine.newQObject(copy, obj.toQObject());
        copy.setPrototype(this->copy(obj.prototype()));
    }
    else if (obj.isQMetaObject())
    {
        copy = engine.newQMetaObject(obj.toQMetaObject());
    }
    else if (obj.isFunction())
    {
        // Calling .toString() on a pure JS function returns
        // the function's source code.
        // On a native function however toString() returns
        // something like "function() { [native code] }".
        // That's why we do a syntax check on the code.

        QString code = obj.toString();
        auto syntaxCheck = engine.checkSyntax(code);

        if (syntaxCheck.state() == syntaxCheck.Valid)
        {
            copy = engine.evaluate(QString() + "(" + code + ")");
        }
        else if (code.contains("[native code]"))
        {
            copy.setData(obj.data());
        }
        else
        {
            // Do error handling…
        }

    }
    else if (obj.isVariant())
    {
        QVariant var = obj.toVariant();
        copy = engine.newVariant(copy, obj.toVariant());
    }
    else if (obj.isObject() || obj.isArray())
    {
        if (obj.isObject()) {
            if (obj.scriptClass()) {
                copy = engine.newObject(obj.scriptClass(), this->copy(obj.data()));
            } else {
                copy = engine.newObject();
            }
        } else {
            copy = engine.newArray();
        }
        copy.setPrototype(this->copy(obj.prototype()));

        QScriptValueIterator it(obj);
        while ( it.hasNext())
        {
            it.next();

            const QString& name = it.name();
            const QScriptValue& property = it.value();

            copy.setProperty(name, this->copy(property));
        }
    }
    else
    {
        // Error handling…
    }

    return copy;
}

Примечание. В этом коде используется метод Qt-internal QScriptValue::objectId().

.
1 голос
/ 17 февраля 2011

Я получил это работает. Вот решение на случай, если оно пригодится кому-либо еще:

QScriptValue copyObject( const QScriptValue& obj)
{
    if( (obj.isObject() || obj.isArray()) && !obj.isFunction() ) {
        QScriptValue copy = obj.isArray() ? obj.engine()->newArray() : obj.engine()->newObject();
        copy.setData( obj.data() );
        QScriptValueIterator it(obj);
        while(it.hasNext()) {
            it.next();
            copy.setProperty( it.name(), copyObject(it.value()) );
        }
        return copy;
    }

    return obj;
}

Важной частью является добавление проверки !obj.isFunction(), которая будет просто копировать функции как они есть, а не делать глубокое копирование. Тонкость здесь в том, что isObject() вернет истину, если элемент является функцией, которую мы не хотим. Это задокументировано в документации по Qt, и я наткнулся на это несколько минут назад.

Кроме того, эта проверка убрала необходимость избегать копирования пунктов, помеченных SkipInEnumeration. Бесконечный цикл исправляется путем проверки функций и копирования их как есть. Уход в SkipInEnumeration фактически сломал некоторые другие вещи, такие как eval функция и куча других встроенных модулей.

...