打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
跨平台编程的利器
【在写本文的时候,看到新闻说Nokia准备放弃Qt,看来Qt要成为昨日黄花了,如果没有大公司支持,任何语言都不会有强的生命力。】
(本文接上篇“一个MDI图形应用框架)
因为业务需要,作者比较关心一种语言或技术的用户自定义能力,用户自定义的“最高境界”就是能将脚本嵌入到程序中,从而改变或控制程序的运行。
从Qt4.3起,就支持脚本了,Qt中的脚本被称为Qt Script,它是基于ECMAScript的,因此与我们通常用的javascript相同,据文章中介绍,Qt中的脚本引擎器采用的是Google的产品,也就是Chrome的JavaScript engine。
一、相关类的介绍
下表是Qt Script技术中有关的类及其简介。
QScriptClass是对script中类的封装,可以在C++代码中对script类进行调用。
QScriptClassPropertyIterator该类似乎用途不大,只有在你基础QScriptClass时,并且需要遍历类的属性时需要。
QScriptContext这个类比较有用,通常作为参数传递到你的C++程序,可以得到调用的上下文环境:用来封装script函数调用(指调用C++的函数)的,基本上功能有两个:一是提供this指针,二是提供调用的arguments
QScriptContextInfoQScriptContext的辅助信息
QScriptEngine脚本解析引擎,创建一个脚本运行环境时必须使用。
QScriptEngineAgent该类是调试Script的基础工具类,可以通过设置Engine的Agent,捕获特定的事件进行处理
QScriptEngineDebuggerScript的调试类,一般情况下不用。
QScriptProgram对Script运行程序的封装,如果我们有一段写好的脚本,并且它多次运行(QScriptProgram会将脚本编译),则可以使用该类。
QScriptString它用作一个对QScriptEngine中字符串的句柄,是对字符串的一个封装。
QScriptSyntaxCheckResult在用户可以编写自己的脚本时,该类比较有用,可以得到Script语法的检查结果。
QScriptValue有点类似于windows中的Variant类型,用于对脚本语言所有数据类型的封装。
QScriptValueIterator为QScriptValue提供了一种类似Java-style的迭代器(当QScriptValue是集合类型时)。
QScriptable提供了一种在Qt C++环境中对脚本的控制方式,似乎与Qt中特有的消息触发机制connect, slot等有关。
大致上,主要用到的类有(按使用频度):QScriptEngine、QScriptValue、QScriptClass、QScriptContext,QScriptSyntaxCheckResult(如果需要检查语法)。
二、与脚本的交互
作者曾经写过一篇C#与Python交互的文章“在C#环境中动态调用IronPython脚本”,本文也按照该篇的情况,介绍Qt与脚本的交互。
1.从Scrip环境中返回数据
代码如下。Script中定义了两个函数cube和mysqrt,在C++环境中调用这两个函数。
[cpp]
void testScript()
{
QScriptEngine myEngine;
myEngine.evaluate("function cube(x) { return x * x * x; }    function mysqrt(x) { return Math.sqrt(x);} ");
qDebug()<< myEngine.evaluate("mysqrt(cube(3))").toNumber();
}
以下的代码显示了从Script中返回一个复杂的数据类型。
[cpp]
Q_DECLARE_METATYPE(QList<int>)
void testInvokeScript()
{
QScriptEngine myEngine;
qScriptRegisterSequenceMetaType<QList<int> >(&myEngine);
QScriptValue global = myEngine.globalObject();
myEngine.evaluate("var pack= new Array();  pack[0]=1; pack[1]='I am here.'; pack[2]=3;");
myEngine.evaluate("var intarr= new Array(); intarr[0]=1; intarr[1]=2; intarr[2]=3;");
QScriptValue vv = global.property("pack");
QScriptValue vi = global.property("intarr");
for (int i = 0; i < vlist.size(); ++i)  qDebug()<<vlist.at(i);
QList<int> ilist = qscriptvalue_cast<QList<int> >(vi);
for (int i = 0; i < ilist.size(); ++i)  qDebug()<<ilist.at(i);
}
这是比较简单的一种交互场景:用户定义一个“封闭”的函数或类型,C++程序可以调用并返回结果。
2.Script环境中调用Qt环境中的函数或类
代码如下,Qt中定义了一个类myclass,一个函数myAdd。在脚本中调用类方法和函数,相当于:myclass.GetId()和myAdd()。
[cpp]
class myclass : public QObject
{
Q_OBJECT
Q_PROPERTY(QString focus READ hasFocus)
public:
explicit myclass(QObject *parent = 0)
{
m_id =10;
}
Q_INVOKABLE int Test()  { return m_id;}
Q_INVOKABLE QString GetName(QString prefix)
{
if(name == "a")  return m_id+10;
if(name =="b" )  return m_id+20;
return m_id;
}
QString hasFocus() const { return "abcde";}
signals:
public slots:
int GetId(QString name)
{
return "Hello " + prefix;
}
private:
int m_id;
};
QScriptValue MainWindow::myAdd(QScriptContext *context, QScriptEngine *engine, void *pargs)
{
QScriptValue a = context->argument(0);
QScriptValue b = context->argument(1);
myclass *pmc =(myclass *)pargs;
int i = pmc->GetId("c");
return a.toNumber() + b.toNumber() + i;
}
void MainWindow::testInvokeFunction()
{
QScriptEngine myEngine;
//修改button上的文字
QScriptValue scriptButton = myEngine.newQObject(ui->btnOK);
myEngine.globalObject().setProperty("button", scriptButton);
myEngine.evaluate("button.text = \"true\"");
//调用myAdd方法
myclass *mc = new myclass(this);
QScriptValue fun = myEngine.newFunction(myAdd,mc);
myEngine.globalObject().setProperty("myAdd", fun);
QScriptValue vv = myEngine.evaluate("myAdd(2,3)");
//调用类myclass的GetId方法
myclass *mc2 = new myclass(this);
QScriptValue qso = myEngine.newQObject(mc2);
myEngine.globalObject().setProperty("qso",qso);
QScriptValue ii = myEngine.evaluate("qso.GetId('aaa')");
qDebug()<<"vv="<<vv.toNumber();
qDebug()<<"ii="<<ii.toString();
}
测试代码中,包含3部分内容:1.将button上的文字在脚本中修改了。2.脚本中调用myAdd方法,注意,脚本中所有的调用参数,都封装到了QScriptContext类中,我们只能从该类中得到,QScripEngine.newFunction方法可以给传到脚本环境中的方法额外的参数,即方法中第2个参数,应用该参数,有时会使代码更清晰。本文中,给myAdd方法的额外参数是一个myclass类。3.脚本中定义了一个全局变量qso,它的类型就是Qt中的myclass,接着调用它的GetId方法。
上面代码中,可以看出,如果需要将Qt中的类(本文是从QObject继承的类,如不是QObject的子类,则比较麻烦)或方法传递到Script环境中,需要用到QScriptEngine的newQObject和newFunction两个方法对类型实例进行封装,然后再脚本环境中设置一个变量“指向”该类型实例,脚本就可以用了。
以上代码中,myclass的实例是在Qt环境中创建的,如果需要在Script环境中创建一个myclass实例,可以采用如下的方法。
[cpp]
QScriptValue MainWindow::createObject(QScriptContext *context, QScriptEngine *engine)
{
QScriptValue a = context->argument(0);
if(a.isString())
{
QString stringname=a.toString();
if(stringname == "myclass")
{
myclass *mc2 = new myclass(0);
return engine->newQObject(mc2);
}
}
return NULL;
}
void MainWindow::testCreateObject()
{
QScriptEngine myEngine;
QScriptValue fun = myEngine.newFunction(createObject);
myEngine.globalObject().setProperty("createObject", fun);
myEngine.evaluate("var obj=createObject('myclass');");
QScriptValue ii = myEngine.globalObject().property("obj");
qDebug()<<myEngine.evaluate("obj.GetName('me')").toString();
}
本质上,myclass实例依然是在Qt中创建,但在脚本中,通过createObject函数,似乎是在Script中创建的,这是一种简单有效的方式。
3.Qt环境中调用Script环境中的数据或类
代码如下。对于简单变量,直接设置其值就可以了,如果该变量不存在,会自动添加,对于复杂变量,例如代码中脚本环境中定义的类comx,当它作为调用参数调用Qt中的函数时,对它的解析不同样简单的变量。
[cpp]
QScriptValue MainWindow::complexAdd(QScriptContext *context, QScriptEngine *engine)
QScriptValue a = context->argument(0);
if(a.isObject())
{
QScriptClass *pc = a.scriptClass();
if(pc == NULL)   qDebug()<<"pc is null";
else
{
qDebug()<<pc->name();
}
QScriptValue val(engine, 123);
a.setProperty("x", val);
}
return NULL;
}
void MainWindow::testQSCriptClass()
{
QScriptEngine myEngine;
myEngine.evaluate("var ii=3;  function cube(x) { return x * x * x; }");
myEngine.globalObject().setProperty("myNumber", 12);
myEngine.globalObject().setProperty("ii", 5);
qDebug()<<myEngine.evaluate("cube(myNumber + 1)").toNumber();
qDebug()<<myEngine.evaluate("cube(ii)").toNumber();
myEngine.evaluate("var comx={x:1, y:2};  var aobj= new Object();  aobj.x=1; aobj.y=2;");
QScriptValue fun = myEngine.newFunction(complexAdd);
myEngine.globalObject().setProperty("complexAdd", fun);
myEngine.evaluate("complexAdd(aobj);");
qDebug()<<  myEngine.evaluate("aobj.x").toString();
}
4.一个为Script中类添加方法的例子
代码如下,该例子为Script中的类person,添加了一个方法,fullName,实际上该方法在Qt环境中定义,注意此例中QScriptContext的用法。
[cpp]
QScriptValue MainWindow::Person_prototype_fullName(QScriptContext *context, QScriptEngine *engine)
{
QScriptValue self = context->thisObject();
QString result;
result += self.property("firstName").toString();
result += QLatin1String(" ");
result += self.property("lastName").toString();
return result;
}
void MainWindow::testQSCriptContext()
{
QScriptEngine myEngine;
QScriptValue fun = myEngine.newFunction(Person_prototype_fullName);
myEngine.globalObject().setProperty("Person_prototype_fullName", fun);
myEngine.evaluate("var person={firstName:'Wang', lastName:'Yi',fullName:Person_prototype_fullName};");
//one way to use
QScriptValue who = myEngine.globalObject().property("person");
qDebug()<< fun.call(who).toString();
//another way to use
qDebug()<<  myEngine.evaluate("person.fullName()").toString();
}
5.Script句法检查
当允许用户自定义脚本时,好的程序应检查其正确性。正确性包括两个方面:运行前静态的语法检查;运行中能捕获运行中的错误并返回给用户。
语法检查可以调用
[cpp]
QScriptSyntaxCheckResult QScriptEngine::checkSyntax ( const QString & program )
该方法是一个静态方法。
运行时,如果有错误,QScriptEngine.evaluate方法将返回一个Error对象,但由于QScriptValue可以封装任何数据类型,所以我们需要有其它的函数来便利地判断是否有异常发生,比较健壮的代码应该像下面的样子:
[cpp]
...
QScriptValue result = myEngine.evaluate(...);
if(myEngine.hasUncaughtException)
{
//错误处理
int errlinenumber =myEngine.uncaughtExceptionLineNumber();
...
}
else
{
...  //继续处理
}
为简单起见,本文以上的代码并未按上面的格式。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Qt如何进行MD5加密
初识Python与Qt
Qt调用JavaScript函数
Qt 字符串设置固定宽度,前位补0
Qt获取计算机MAC地址
Qt qtableview 多行选中,获取行号
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服