Qt应用程序脚本技术QScriptEngine详解
一、QScriptEngine 概述
QScriptEngine 是 Qt 框架中用于执行 JavaScript 代码的核心类,属于 QtScript 模块(需注意 Qt 5 后部分功能被 QJSEngine 替代
)。它提供了 JavaScript 解释、对象绑定、信号槽机制与 C++ 交互的能力,适用于脚本扩展、动态逻辑实现等场景。
二、核心功能
脚本执行
通过 evaluate()
方法执行 JavaScript 代码,返回 QScriptValue
对象存储结果:
#include <QScriptEngine>
#include <QDebug>
void runSimpleScript()
{
QScriptEngine engine;
// 执行简单脚本
QScriptValue result = engine.evaluate("var x = 10; var y = 20; x + y;");
if (engine.hasUncaughtException()) {
qDebug() << "Error at line" << engine.uncaughtExceptionLineNumber()
<< ":" << engine.uncaughtException().toString();
} else {
qDebug() << "Result:" << result.toNumber(); // 输出30
}
}
对象绑定
将 C++ 对象暴露给脚本环境,使用 newQObject()
方法:
// 向脚本环境暴露C++对象
QScriptEngine engine;
QPushButton button;
QScriptValue scriptButton = engine.newQObject(&button);
engine.globalObject().setProperty("button", scriptButton);
// 脚本中可以操作button对象
engine.evaluate("button.text = 'Click Me'; button.show();");
信号槽连接
支持 JavaScript 函数作为槽:
QScriptValue func = engine.evaluate("(function(msg) { print(msg); })");
QObject::connect(myObject, SIGNAL(signal(QString)), func);
三、关键方法
globalObject()
:获取全局对象,用于注册变量或函数。newFunction()
:封装 C++ 函数为脚本可调用的函数。pushContext()
/popContext()
:管理执行上下文。
示例:注册全局函数
QScriptValue add(QScriptContext* context, QScriptEngine* engine) {
return context->argument(0).toNumber() + context->argument(1).toNumber();
}
engine->globalObject().setProperty("add", engine->newFunction(add));
// 脚本中调用:add(2, 3)
四、错误处理
通过 hasUncaughtException()
和 uncaughtException()
检查执行错误:
QScriptValue result = engine.evaluate("invalidCode");
if (engine.hasUncaughtException()) {
qDebug() << "Error:" << engine.uncaughtException().toString();
}
五、注意事项
- 内存管理:QScriptEngine 不自动管理 C++ 对象生命周期,需手动处理。
- 模块支持:Qt 5 推荐使用
QJSEngine
(QtQml 模块)作为替代,但QScriptEngine
仍可用于复杂脚本需求。 - 性能:频繁调用脚本可能影响性能,建议预编译或优化逻辑。
六、典型应用场景
- 动态配置规则(如公式计算)。
- 插件系统扩展(用户编写脚本逻辑)。
- 自动化测试(模拟交互行为)。
如需进一步优化或扩展功能,可结合 QScriptClass
实现自定义对象原型。
七、实例展示
1、实现脚本控制台
// scriptconsole.h
#include <QWidget>
#include <QScriptEngine>
class ScriptConsole : public QWidget
{
Q_OBJECT
public:
ScriptConsole(QWidget *parent = nullptr);
public slots:
void execute(const QString &script);
private:
QScriptEngine *engine;
QTextEdit *outputWidget;
QLineEdit *inputLine;
void setupUi();
void print(const QString &message);
};
// scriptconsole.cpp
ScriptConsole::ScriptConsole(QWidget *parent)
: QWidget(parent), engine(new QScriptEngine(this))
{
setupUi();
// 将print函数暴露给脚本
QScriptValue printFunc = engine->newFunction([](QScriptContext *context, QScriptEngine *engine) {
QString message;
for (int i = 0; i < context->argumentCount(); ++i) {
if (i > 0) message.append(" ");
message.append(context->argument(i).toString());
}
qobject_cast<ScriptConsole*>(engine->parent())->print(message);
return engine->undefinedValue();
});
engine->globalObject().setProperty("print", printFunc);
}
void ScriptConsole::execute(const QString &script)
{
print("> " + script);
QScriptValue result = engine->evaluate(script);
if (engine->hasUncaughtException()) {
print(QString("Exception at line %1: %2")
.arg(engine->uncaughtExceptionLineNumber())
.arg(engine->uncaughtException().toString()));
} else if (!result.isUndefined()) {
print(result.toString());
}
}
2、动态加载UI并绑定脚本
// scriptedwindow.h
#include <QMainWindow>
#include <QScriptEngine>
class ScriptedWindow : public QMainWindow
{
Q_OBJECT
public:
ScriptedWindow(const QString &uiFile, const QString &scriptFile, QWidget *parent = nullptr);
private:
QScriptEngine *engine;
void loadUi(const QString &uiFile);
void loadScript(const QString &scriptFile);
};
// scriptedwindow.cpp
ScriptedWindow::ScriptedWindow(const QString &uiFile, const QString &scriptFile, QWidget *parent)
: QMainWindow(parent), engine(new QScriptEngine(this))
{
loadUi(uiFile);
loadScript(scriptFile);
}
void ScriptedWindow::loadUi(const QString &uiFile)
{
QUiLoader loader;
QFile file(uiFile);
if (!file.open(QFile::ReadOnly)) {
qWarning() << "Cannot open UI file:" << uiFile;
return;
}
QWidget *ui = loader.load(&file, this);
setCentralWidget(ui);
// 将UI对象暴露给脚本
foreach (QObject *obj, ui->findChildren<QObject*>()) {
if (!obj->objectName().isEmpty()) {
engine->globalObject().setProperty(obj->objectName(),
engine->newQObject(obj));
}
}
}
void ScriptedWindow::loadScript(const QString &scriptFile)
{
QFile file(scriptFile);
if (!file.open(QFile::ReadOnly)) {
qWarning() << "Cannot open script file:" << scriptFile;
return;
}
QTextStream stream(&file);
QString script = stream.readAll();
QScriptValue result = engine->evaluate(script);
if (engine->hasUncaughtException()) {
qWarning() << "Script error at line" << engine->uncaughtExceptionLineNumber()
<< ":" << engine->uncaughtException().toString();
}
}
3、创建可脚本化的C++类
// calculator.h
#include <QObject>
class Calculator : public QObject
{
Q_OBJECT
public:
explicit Calculator(QObject *parent = nullptr);
public slots:
double add(double a, double b) { return a + b; }
double subtract(double a, double b) { return a - b; }
double multiply(double a, double b) { return a * b; }
double divide(double a, double b) { return b != 0 ? a / b : 0; }
signals:
void calculationPerformed(const QString &op, double result);
};
// 注册到脚本引擎
QScriptValue calculatorConstructor(QScriptContext *context, QScriptEngine *engine)
{
return engine->newQObject(new Calculator(engine));
}
void registerCalculator(QScriptEngine *engine)
{
QScriptValue ctor = engine->newFunction(calculatorConstructor);
QScriptValue metaObject = engine->newQMetaObject(&Calculator::staticMetaObject, ctor);
engine->globalObject().setProperty("Calculator", metaObject);
}
在脚本中使用自定义类
// 使用示例
QScriptEngine engine;
registerCalculator(&engine);
engine.evaluate(R"(
var calc = new Calculator;
print(calc.add(5, 3)); // 输出8
calc.calculationPerformed.connect(function(op, result) {
print("Operation " + op + " produced " + result);
});
calc.multiply(4, 5); // 会触发信号
)");
4、脚本扩展插件系统
// scriptextension.h
#include <QObject>
#include <QScriptable>
#include <QScriptValue>
#include <QScriptEngine>
class ScriptExtension : public QObject, protected QScriptable
{
Q_OBJECT
public:
ScriptExtension(QObject *parent = nullptr);
public slots:
QScriptValue systemInfo();
QScriptValue fileExists(const QString &path);
void delay(int milliseconds);
};
// 注册函数
void registerExtensions(QScriptEngine *engine)
{
QScriptValue extensionObject = engine->newQObject(new ScriptExtension(engine));
engine->globalObject().setProperty("ext", extensionObject);
}
// 脚本中使用
engine.evaluate(R"(
print(ext.systemInfo().os);
if (ext.fileExists('/path/to/file')) {
print('File exists');
}
ext.delay(1000); // 延迟1秒
)");