基于Value类型实现原型对象【不是new出来的】
在为基于值的类型(例如,QPointF)实现原型对象时,应用相同的一般技术;您用应该在实例之间共享的功能填充原型对象。然后通过调用QScriptEngine::setDefaultPrototype()将原型对象与类型相关联。这确保了当例如相关类型的值从槽返回到脚本时,脚本值的原型链接将被正确初始化。
当自定义类型的值存储到QVariants( 默认情况下qt脚本确实如此)中时,qscriptvalue_cast()使您能够安全地将脚本值转换为指向C++类型的指针。这使得很容易进行类型检查,而且,对于应该修改底层C++值的原型函数,可以修改脚本值中包含的实际值(而不是它的副本)。
Q_DECLARE_METATYPE(QPointF)
Q_DECLARE_METATYPE(QPointF*)
QScriptValue QPointF_prototype_x(QScriptContext *context, QScriptEngine *engine)
{
// Since the point is not to be modified, it's OK to cast to a value here
QPointF point = qscriptvalue_cast<QPointF>(context->thisObject());
return point.x();
}
QScriptValue QPointF_prototype_setX(QScriptContext *context, QScriptEngine *engine)
{
// Cast to a pointer to be able to modify the underlying C++ value
QPointF *point = qscriptvalue_cast<QPointF*>(context->thisObject());
if (!point)
return context->throwError(QScriptContext::TypeError, "QPointF.prototype.setX: this object is not a QPointF");
point->setX(context->argument(0).toNumber());
return engine->undefinedValue();
}
实现基于值的类型的构造函数
通过包装本地工厂函数,可以为基于值的类型实现构造函数。例如,以下函数实现QPoint的简单构造函数:
QScriptValue QPoint_ctor(QScriptContext *context, QScriptEngine *engine)
{
int x = context->argument(0).toInt32();
int y = context->argument(1).toInt32();
return engine->toScriptValue(QPoint(x, y));
}
...
engine.globalObject().setProperty("QPoint", engine.newFunction(QPoint_ctor));
在上面的代码中,我们简化了一些事情,例如,我们没有检查参数计数来决定使用哪个QPoint C++构造函数。在您自己的构造函数中,您必须自己完成这种类型的解析,即,通过检查传递给本机函数的参数数量,和/或通过检查参数的类型并将参数转换为所需的类型。如果检测到参数有问题,则可能希望通过抛出脚本异常来发出信号;参见QScriptContext::throwError()。
管理基于非QObject的对象
对于基于值的类型(例如QPoint),当Qt脚本对象被垃圾回收时,C++对象将被销毁,因此管理C++对象的内存不是问题。对于QObject,Qt脚本为管理底层C++对象的生存期提供了几种备选方案;请参阅控件QObject所有权部分。但是,对于不从QObject继承的多态类型,当您不能(或不)将该类型包装在QObject中时,您必须自己管理C++对象的生存期。
当Qt脚本对象包装C++对象时,通常是合理的,即当Qt脚本对象是垃圾收集时,C++对象被删除;这通常是对象可以由脚本构建的情况,相反,应用程序为脚本提供了预先生成的“environment”对象。一种使C++对象的生命周期遵循Qt脚本对象的生存期的方法是使用共享指针类(比如QSharedPointer)来保存指向对象的指针;当包含QSharedPointer的Qt脚本对象被垃圾收集时,如果没有其他引用对象,则将删除基础C++对象。
下面的代码片段显示了构造函数,该函数构造使用QSharedPointer存储的QXmlStreamReader对象:
typedef QSharedPointer<QXmlStreamReader> XmlStreamReaderPointer;
Q_DECLARE_METATYPE(XmlStreamReaderPointer)
QScriptValue constructXmlStreamReader(QScriptContext *context, QScriptEngine *engine)
{
if (!context->isCalledAsConstructor())
return context->throwError(QScriptContext::SyntaxError, "please use the 'new' operator");
QIODevice *device = qobject_cast<QIODevice*>(context->argument(0).toQObject());
if (!device)
return context->throwError(QScriptContext::TypeError, "please supply a QIODevice as first argument");
// Create the C++ object
QXmlStreamReader *reader = new QXmlStreamReader(device);
XmlStreamReaderPointer pointer(reader);
// store the shared pointer in the script object that we are constructing
return engine->newVariant(context->thisObject(), QVariant::fromValue(pointer));
}
原型函数可以使用qscriptvalue_cast() 来将这个对象转换成适当的类型:
QScriptValue xmlStreamReader_atEnd(QScriptContext *context, QScriptEngine *)
{
XmlStreamReaderPointer reader = qscriptvalue_cast<XmlStreamReaderPointer>(context->thisObject());
if (!reader)
return context->throwError(QScriptContext::TypeError, "this object is not an XmlStreamReader");
return reader->atEnd();
}
原型和构造函数对象以通常的方式设置:
QScriptEngine engine;
QScriptValue xmlStreamReaderProto = engine.newObject();
xmlStreamReaderProto.setProperty("atEnd", engine.newFunction(xmlStreamReader_atEnd));
QScriptValue xmlStreamReaderCtor = engine.newFunction(constructXmlStreamReader, xmlStreamReaderProto);
engine.globalObject().setProperty("XmlStreamReader", xmlStreamReaderCtor);
脚本现在可以通过调用XmlStreamReader构造函数来构造QXmlStreamReader对象,并且当垃圾收集Qt Script对象(或者脚本引擎被销毁)时,QXmlStreamReader对象也被销毁。
用QScriptClass定义自定义脚本类
在某些情况下,QScriptEngine::newQObject()提供的动态QObject绑定或QScriptEngine::newFunction()提供的手动绑定都不够。例如,您可能想要对底层对象实现动态脚本代理;或者您可能希望实现一个类似于数组的类(即,对作为有效数组索引的属性和长度属性进行特殊处理)。在这种情况下,可以对QScriptClass进行子类实现所需的行为。
QScriptClass允许您通过虚拟get/set属性函数处理(类)脚本对象的所有属性访问。定制属性的迭代也通过QScriptClassPropertyIterator类得到支持;这意味着您可以通知要由for-in脚本语句和QScriptValueIterator报告的属性。
错误处理和调试设备
脚本中的语法错误将在评估脚本后立即报告;QScriptEngine::evaluate()将返回一个SyntaxError对象,您可以将该对象转换为字符串以获得错误的描述。
函数QScriptEngine::uncaughtExceptionBacktrace() 为您提供了上一个未捕获异常的人类可读回溯。为了在回溯中获得有用的文件名信息,在评估脚本时,应该将适当的文件名传递给QScriptEngine::evaluate()。
通常,在评估脚本时不会发生异常,但是在稍后实际执行脚本定义的函数时,会发生异常。对于C++信号处理程序来说,这是棘手的;考虑按钮的clicked()信号连接到脚本函数的情况,并且脚本函数在处理该信号时导致脚本异常。脚本异常传播到哪里?
解决方案是连接到QScriptEngine::signalHandlerException()信号;当信号处理器导致异常时,这将向您提供通知,以便您可以了解发生了什么和/或从中恢复。
在Qt 4.4中引入了QScriptEngineAgent类。QScriptEngineAgent提供了用于报告脚本引擎中的低级“事件”的接口,例如当一个新的函数调用或达到新的脚本语句时。通过子类化QScriptEngineAgent,可以通知您这些事件并执行一些操作(如果需要的话)。QScriptEngineAgent本身不提供任何特定于调试的功能(例如,设置断点),但它是这样做的工具的基础。
Qt脚本工具模块提供了一个可以嵌入到应用程序中的QT脚本调试器。