Глубокая копия QScriptValue как глобального объекта
У меня есть программа, использующая 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 принимает фактический указатель на функцию).
2 ответа
Я получил это работает. Вот решение на случай, если оно пригодится кому-то еще:
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
функция и куча других встроенных модулей.
Чтобы сделать многопоточность доступной в QtScript, мне нужен был способ глубокого копирования QScriptValue
возражает против другого QScriptEngine
и наткнулся на этот вопрос. К сожалению, кода Дейва было недостаточно для этой задачи, и у него есть несколько проблем, даже если копирование выполняется только в одном QScriptEngine
, Поэтому мне нужна была более сложная версия. Вот проблемы, которые мне пришлось решать в моем решении:
- Код Дейва приводит к переполнению стека, когда объект содержит ссылку на себя.
- Я хотел, чтобы мое решение учитывало ссылки на объекты, чтобы множественные ссылки на один объект не вызывали копирование ссылочного объекта более одного раза.
- Как глубоко скопированный
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-internal QScriptValue::objectId()
,