作为全局对象的QScriptValue的深层副本

时间:2011-02-16 18:20:30

标签: c++ qt qtscript

我有一个使用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 个答案:

答案 0 :(得分:2)

为了在QtScript中提供多线程,我需要一种方法将QScriptValue个对象深层复制到另一个QScriptEngine并偶然发现这个问题。不幸的是,Dave的代码不足以完成这项任务,即使仅在一个QScriptEngine内进行复制也存在一些问题。所以我需要一个更复杂的版本。这些是我在解决方案中必须解决的问题:

  1. 当对象包含对自身的引用时,Dave的代码会导致堆栈溢出。
  2. 我希望我的解决方案能够尊重对象的引用,以便对一个对象的多次引用不会导致引用的对象被多次复制。
  3. 由于深度复制的QScriptValue对象用于与源对象不同的QScriptEngine,我需要一种真正复制的方法,例如功能也是如此。
  4. 这可能对其他人有用,所以这里是我提出的代码:

    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内部方法QScriptValue::objectId()

答案 1 :(得分:1)

我得到了它的工作。这是解决方案,以防它对其他人有用:

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函数和一堆其他内置函数。