C++ QScript值作为全局对象的深度副本

C++ QScript值作为全局对象的深度副本,c++,qt,qtscript,C++,Qt,Qtscript,我有一个程序使用QtScript进行一些自动化。我在脚本引擎的全局范围中添加了一组C++函数和类,以便脚本可以访问它们,比如: QScriptValue fun = engine->newFunction( systemFunc ); engine->globalObject().setProperty( "system", fun ); 我希望能够连续运行多个脚本,每个脚本都有一个新的全局状态。所以如果一个脚本设置了一个全局变量,比如 myGlobalVar = "stuff";

我有一个程序使用QtScript进行一些自动化。我在脚本引擎的全局范围中添加了一组C++函数和类,以便脚本可以访问它们,比如:

QScriptValue fun = engine->newFunction( systemFunc );
engine->globalObject().setProperty( "system", fun );
我希望能够连续运行多个脚本,每个脚本都有一个新的全局状态。所以如果一个脚本设置了一个全局变量,比如

myGlobalVar = "stuff";
我希望在下一个脚本运行之前删除该变量。我这样做的方法是创建脚本引擎全局对象的深度副本,然后在脚本完成运行后恢复它。但是深层拷贝不起作用,因为我的
系统
功能突然因错误而中断:

TypeError: Result of expression 'system' [[object Object]] is not a function.
这是我的深度复制功能,改编自:

QScriptValue copyObject(常量QScriptValue&obj,QString level=”“)
{
if(obj.isObject()| | obj.isArray()){
QScript值copy=obj.isArray()?obj.engine()->newArray():obj.engine()->newObject();
copy.setData(obj.data());
QScript值迭代器it(obj);
while(it.hasNext()){
it.next();

qDebug()我让它工作起来了。下面是一个解决方案,以防它对其他人有用:

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

为了实现m由于QtScript中提供了多线程,我需要一种方法将
QScriptValue
对象深入复制到另一个
QScriptEngine
中,并偶然发现了这个问题。不幸的是,Dave的代码不足以完成这项任务,即使只在一个
QScriptEngine
中进行复制,也会出现一些问题。因此我需要一个更简单的方法网络钓鱼验证版本。以下是我必须在解决方案中解决的问题:

  • Dave的代码在对象包含对自身的引用时导致堆栈溢出
  • 我希望我的解决方案尊重对对象的引用,以便对一个对象的多个引用不会导致被引用对象被复制多次
  • 由于深度复制的
    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;
    }
    
    类脚本复制器
    {
    公众:
    脚本复制器(QScript引擎和toEngine)
    :m_toEngine(toEngine){}
    QScript值副本(常量QScript值和obj);
    QScriptEngine&m_-toEngine;
    QMap复制对象;
    };
    QScript值脚本复制器::复制(常量QScript值和对象)
    {
    QScriptEngine&engine=m_-toEngine;
    if(obj.isUndefined()){
    返回QScript值(QScript值::未定义的值);
    }
    if(obj.isNull()){
    返回QScript值(QScript值::NullValue);
    }
    //如果我们已经复制了此对象,请不要再复制它。
    QScript值拷贝;
    if(obj.isObject())
    {
    if(copiedObjs.contains(obj.objectId()))
    {
    返回copiedObjs.value(obj.objectId());
    }
    copiedObjs.insert(obj.objectId(),copy);
    }
    if(obj.isQObject())
    {
    copy=engine.newQObject(copy,obj.toQObject());
    copy.setPrototype(此->复制(obj.prototype());
    }
    else if(obj.isQMetaObject())
    {
    copy=engine.newQMetaObject(obj.toQMetaObject());
    }
    else if(obj.isFunction())
    {
    //对纯JS函数调用.toString()返回
    //函数的源代码。
    //但是,在本机函数上,toString()返回
    //类似于“function(){[native code]}”。
    //这就是我们对代码进行语法检查的原因。
    QString代码=obj.toString();
    自动语法检查=引擎。检查语法(代码);
    if(syntaxCheck.state()==syntaxCheck.Valid)
    {
    copy=engine.evaluate(QString()+“(“+code+”));
    }
    else if(code.contains(“[本机代码]”))
    {
    copy.setData(obj.data());
    }
    其他的
    {
    //做错误处理…
    }
    }
    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());
    }否则{
    copy=engine.newObject();
    }
    }否则{
    copy=engine.newArray();
    }
    copy.setPrototype(此->复制(obj.prototype());
    QScript值迭代器it(obj);
    while(it.hasNext())
    {
    it.next();
    const QString&name=it.name();
    常量QScriptValue&property=it.value();
    copy.setProperty(name,this->copy(property));
    }
    }
    其他的
    {
    //错误处理…
    }
    返回副本;
    }
    
    注意:这段代码在我的例子中使用了Qt内部方法
    QScriptValue::objectId()

    isVariant()
    至少对数字返回false。我还必须添加一个
    isNumber()
    case(以及所有其他原语,以防万一)。
    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;
    }