我正在将QScriptEngine
代码迁移到QJSEngine
,并且遇到了在评估脚本后无法调用函数的问题:
#include <QCoreApplication>
#include <QtQml>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QJSEngine engine;
QJSValue evaluationResult = engine.evaluate("function foo() { return \"foo\"; }");
if (evaluationResult.isError()) {
qWarning() << evaluationResult.toString();
return 1;
}
if (!evaluationResult.hasProperty("foo")) {
qWarning() << "Script has no \"foo\" function";
return 1;
}
if (!evaluationResult.property("foo").isCallable()) {
qWarning() << "\"foo\" property of script is not callable";
return 1;
}
QJSValue callResult = evaluationResult.property("foo").call();
if (callResult.isError()) {
qWarning() << "Error calling \"foo\" function:" << callResult.toString();
return 1;
}
qDebug() << "Result of call:" << callResult.toString();
return 0;
}
此脚本的输出为:
Script has no "activate" function
当我使用QScriptEngine
时,可以调用相同的函数:
scriptEngine->currentContext()->activationObject().property("foo").call(scriptEngine->globalObject());
为什么函数不作为评估结果的属性存在,我该如何调用它?
答案 0 :(得分:5)
该代码将导致foo()
被评估为全局范围内的函数声明。由于您未对其进行调用,因此生成的QJSValue
为undefined
。您可以通过在浏览器中打开JavaScript控制台并编写相同的行来查看相同的行为:
您无法调用foo()
的{{1}}函数,因为它不存在。你可以做的是通过全局对象调用它:
这与您的C ++代码看到的相同。因此,要访问和调用undefined
功能,您需要通过foo()
的{{3}}功能访问它:
QJSEngine
此代码的输出为:
#include <QCoreApplication>
#include <QtQml>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QJSEngine engine;
QJSValue evaluationResult = engine.evaluate("function foo() { return \"foo\"; }");
if (evaluationResult.isError()) {
qWarning() << evaluationResult.toString();
return 1;
}
if (!engine.globalObject().hasProperty("foo")) {
qWarning() << "Script has no \"foo\" function";
return 1;
}
if (!engine.globalObject().property("foo").isCallable()) {
qWarning() << "\"foo\" property of script is not callable";
return 1;
}
QJSValue callResult = engine.globalObject().property("foo").call();
if (callResult.isError()) {
qWarning() << "Error calling \"foo\" function:" << callResult.toString();
return 1;
}
qDebug() << "Result of call:" << callResult.toString();
return 0;
}
这与您发布的使用Result of call: "foo"
的行大致相同。
这种方法的好处是您无需触摸脚本即可使其工作。
缺点是,如果您计划重复使用相同的QScriptEngine
来调用多个脚本,以这种方式编写JavaScript代码会导致问题,特别是如果其中的函数具有相同的名称。具体来说,您评估的对象将永远留在全局命名空间中。
QJSEngine
以QScriptEngine
:QScriptContext
形式提供了解决此问题的新方法,之后又push()
。但是,globalObject()。
解决此问题的一种方法是为每个脚本创建一个新的pop()
。我没试过,我不确定它会有多贵。
no such API exists in QJSEngine
,但我不太明白它如何与每个脚本的多个函数一起使用。
在与同事交谈后,我了解了一种使用documentation looked like it might hint at another way around it解决问题的方法:
QJSEngine
此代码的输出为:
#include <QCoreApplication>
#include <QtQml>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QJSEngine engine;
QString code = QLatin1String("( function(exports) {"
"exports.foo = function() { return \"foo\"; };"
"exports.bar = function() { return \"bar\"; };"
"})(this.object = {})");
QJSValue evaluationResult = engine.evaluate(code);
if (evaluationResult.isError()) {
qWarning() << evaluationResult.toString();
return 1;
}
QJSValue object = engine.globalObject().property("object");
if (!object.hasProperty("foo")) {
qWarning() << "Script has no \"foo\" function";
return 1;
}
if (!object.property("foo").isCallable()) {
qWarning() << "\"foo\" property of script is not callable";
return 1;
}
QJSValue callResult = object.property("foo").call();
if (callResult.isError()) {
qWarning() << "Error calling \"foo\" function:" << callResult.toString();
return 1;
}
qDebug() << "Result of call:" << callResult.toString();
return 0;
}
您可以在我刚刚链接到的文章中详细了解此方法。以下是它的摘要:
Result of call: "foo"
),允许函数外部的代码创建它并将其存储在
变量(exports
)。但是,正如文章所述,这种方法仍然使用全局范围:
以前的模式通常由用于浏览器的JavaScript模块使用。该模块将声明一个全局变量并将其代码包装在一个函数中,以便拥有自己的私有命名空间。但是,如果多个模块碰巧声称具有相同名称,或者您希望将两个版本的模块并排加载,则此模式仍会导致问题。
如果您想进一步了解,请按照文章进行操作。但是,只要您使用唯一的对象名称,就可以了。
以下是“现实生活”脚本如何更改以适应此解决方案的示例:
(this.object = {})
function activate(thisEntity, withEntities, activatorEntity, gameController, activationTrigger, activationContext) {
gameController.systemAt("WeaponComponentType").addMuzzleFlashTo(thisEntity, "muzzle-flash");
}
function equipped(thisEntity, ownerEntity) {
var sceneItemComponent = thisEntity.componentOfType("SceneItemComponentType");
sceneItemComponent.spriteFileName = ":/sprites/pistol-equipped.png";
var physicsComponent = thisEntity.componentOfType("PhysicsComponentType");
physicsComponent.width = sceneItemComponent.sceneItem.width;
physicsComponent.height = sceneItemComponent.sceneItem.height;
}
function unequipped(thisEntity, ownerEntity) {
var sceneItemComponent = thisEntity.componentOfType("SceneItemComponentType");
sceneItemComponent.spriteFileName = ":/sprites/pistol.png";
var physicsComponent = thisEntity.componentOfType("PhysicsComponentType");
physicsComponent.width = sceneItemComponent.sceneItem.width;
physicsComponent.height = sceneItemComponent.sceneItem.height;
}
function destroy(thisEntity, gameController) {
}
( function(exports) {
exports.activate = function(thisEntity, withEntities, activatorEntity, gameController, activationTrigger, activationContext) {
gameController.systemAt("WeaponComponentType").addMuzzleFlashTo(thisEntity, "muzzle-flash");
}
exports.equipped = function(thisEntity, ownerEntity) {
var sceneItemComponent = thisEntity.componentOfType("SceneItemComponentType");
sceneItemComponent.spriteFileName = ":/sprites/pistol-equipped.png";
var physicsComponent = thisEntity.componentOfType("PhysicsComponentType");
physicsComponent.width = sceneItemComponent.sceneItem.width;
physicsComponent.height = sceneItemComponent.sceneItem.height;
}
exports.unequipped = function(thisEntity, ownerEntity) {
var sceneItemComponent = thisEntity.componentOfType("SceneItemComponentType");
sceneItemComponent.spriteFileName = ":/sprites/pistol.png";
var physicsComponent = thisEntity.componentOfType("PhysicsComponentType");
physicsComponent.width = sceneItemComponent.sceneItem.width;
physicsComponent.height = sceneItemComponent.sceneItem.height;
}
exports.destroy = function(thisEntity, gameController) {
}
})(this.Pistol = {});
脚本可以包含具有相同名称(Car
,activate
等)的函数,而不会影响destroy
的函数。
从Qt 5.12开始,Pistol
支持正确的JavaScript模块:
对于更大的功能,您可能希望将代码和数据封装到模块中。模块是包含脚本代码,变量等的文件,并使用导出语句来描述其与应用程序其余部分的接口。在import语句的帮助下,模块可以引用其他模块的功能。这允许以安全的方式从较小的连接构建块构建脚本化应用程序。相反,使用evaluate()的方法带来的风险是,一个evaluate()调用的内部变量或函数会意外地污染全局对象并影响后续评估。
所有需要做的就是将文件重命名为QJSEngine
扩展名,然后将代码转换为:
.mjs
调用其中一个函数的C ++看起来像这样:
export function activate(thisEntity, withEntities, activatorEntity, gameController, activationTrigger, activationContext) {
gameController.systemAt("WeaponComponentType").addMuzzleFlashTo(thisEntity, "muzzle-flash");
}
export function equipped(thisEntity, ownerEntity) {
var sceneItemComponent = thisEntity.componentOfType("SceneItemComponentType");
sceneItemComponent.spriteFileName = ":/sprites/pistol-equipped.png";
var physicsComponent = thisEntity.componentOfType("PhysicsComponentType");
physicsComponent.width = sceneItemComponent.sceneItem.width;
physicsComponent.height = sceneItemComponent.sceneItem.height;
}
export function unequipped(thisEntity, ownerEntity) {
var sceneItemComponent = thisEntity.componentOfType("SceneItemComponentType");
sceneItemComponent.spriteFileName = ":/sprites/pistol.png";
var physicsComponent = thisEntity.componentOfType("PhysicsComponentType");
physicsComponent.width = sceneItemComponent.sceneItem.width;
physicsComponent.height = sceneItemComponent.sceneItem.height;
}
export function destroy(thisEntity, gameController) {
}