2011-02-16 108 views
5

我有一个使用QtScript进行某些自动化的程序。我加了一堆C++函数和类的脚本引擎的全球范围,使脚本可以访问它们,就像这样:作为全局对象的QScriptValue的深层副本

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接受一个实际的函数指针)。

回答

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

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()

+0

在我的例子中`isVariant()`至少为数字返回false。我不得不添加一个`isNumber()`的情况(以及所有其他原语来预防)。 – Olivetree 2017-01-24 18:42:02