QScriptValue 作为全局对象的深层复制

发布于 2024-10-17 14:47:40 字数 1676 浏览 1 评论 0原文

我有一个使用 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 采用实际的函数指针)。

I have a program using QtScript for some automation. I have added a bunch of C++ functions and classes to the global scope of the script engine so that scripts can access them, like so:

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

I would like to be able to run multiple scripts in succession, each with a fresh global state. So if one script sets a global variable, like

myGlobalVar = "stuff";

I want that variable to be erased before the next script runs. My method for doing this is to make a deep copy of the script engine's Global Object, and then restore it when a script finishes running. But the deep copies aren't working, since my system function suddenly breaks with the error:

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

Here is my deep copy function, adapted from:
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;
}

(the SkipInEnumeration was put in to avoid an infinite loop)

EDIT: Part of the problem, I think, is that in the debugger (QScriptEngineDebugger), the functions and constructors I've added are supposed to appear as type Function, but after copying, they appear as type Object. I haven't yet found a good way of creating a new Function that duplicates an existing one (QScriptEngine::newFunction takes an actual function pointer).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

秋叶绚丽 2024-10-24 14:47:40

为了在 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()

For the purpose of making multi-threading available within QtScript, I needed a way to deep-copy QScriptValue objects to another QScriptEngine and stumbled upon this question. Unfortunately, Dave's code was not sufficient for this task, and has a few problems even when copying within only one QScriptEngine. So I needed a more sophisticated version. These are the problems I had to address in my solution:

  1. Dave's code results in a stack overflow when an object contains a reference to itself.
  2. I wanted my solution to respect references to objects so that multiple references to one object would not cause the referenced object to be copied more than once.
  3. As the deep-copied QScriptValue objects are used in a different QScriptEngine than their source objects, I needed a way to truly copy e.g. functions as well.

It might be useful for someone else, so here's the code I came up with:

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;
}

Note: This code uses the Qt-internal method QScriptValue::objectId().

无声无音无过去 2024-10-24 14:47:40

我成功了。这是解决方案,以防对其他人有用:

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

I got it working. Here's the solution in case it's useful for anyone else:

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;
}

The important part is the addition of the !obj.isFunction() check, which will just copy Functions as they are, and not do a deep copy. The subtlety here is that isObject() will return true if the item is a Function, which we don't want. This is documented in the Qt docs and I stumbled upon it a few moments ago.

Also, this check removed the need to avoid copying items marked SkipInEnumeration. The infinite loop is fixed by checking for functions and copying them as-is. Leaving in the SkipInEnumeration actually broke some other stuff, like the eval function and a bunch of other built-ins.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文