Глубокая копия QScriptValue как глобального объекта

У меня есть программа, использующая 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 ответа

Решение

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

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() вернет true, если элемент является функцией, которую мы не хотим. Это задокументировано в документации по Qt, и я наткнулся на это несколько минут назад.

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

Чтобы сделать многопоточность доступной в 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(),

Другие вопросы по тегам