5

我有一个使用 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)中,我添加的函数和构造函数应该显示为 type Function,但在复制后,它们显示为 type Object。我还没有找到一种创建复制现有函数的新函数的好方法(QScriptEngine::newFunction 需要一个实际的函数指针)。

4

2 回答 2

2

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

  1. 当对象包含对自身的引用时,Dave 的代码会导致堆栈溢出。
  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 内部方法QScriptValue::objectId()

于 2016-02-27T04:22:31.430 回答
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函数和一堆其他内置函数。

于 2011-02-16T21:34:55.333 回答