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