跨平台编程的利器—Qt:与Javascript的交互[为程序添加动态脚本]

原文链接:https://blog.csdn.net/guxch/article/details/7656846

因为业务需要,作者比较关心一种语言或技术的用户自定义能力,用户自定义的“最高境界”就是能将脚本嵌入到程序中,从而改变或控制程序的运行。
从Qt4.3起,就支持脚本了,Qt中的脚本被称为Qt Script,它是基于ECMAScript的,因此与我们通常用的javascript相同,据文章中介绍,Qt中的脚本引擎器采用的是Google的产品,也就是Chrome的JavaScript engine。

一、相关类的介绍

下表是Qt Script技术中有关的类及其简介。
在这里插入图片描述
大致上,主要用到的类有(按使用频度):QScriptEngine、QScriptValue、QScriptClass、QScriptContext,QScriptSyntaxCheckResult(如果需要检查语法)。

二、与脚本的交互

作者曾经写过一篇C#与Python交互的文章“在C#环境中动态调用IronPython脚本”,本文也按照该篇的情况,介绍Qt与脚本的交互。

1.从Scrip环境中返回数据

代码如下。Script中定义了两个函数cube和mysqrt,在C++环境中调用这两个函数。

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中返回一个复杂的数据类型。

 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()。

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实例,可以采用如下的方法。

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中的函数时,对它的解析不同样简单的变量。

 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的用法。

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句法检查
当允许用户自定义脚本时,好的程序应检查其正确性。正确性包括两个方面:运行前静态的语法检查;运行中能捕获运行中的错误并返回给用户。
语法检查可以调用

QScriptSyntaxCheckResult QScriptEngine::checkSyntax ( const QString & program )

该方法是一个静态方法。
运行时,如果有错误,QScriptEngine.evaluate方法将返回一个Error对象,但由于QScriptValue可以封装任何数据类型,所以我们需要有其它的函数来便利地判断是否有异常发生,比较健壮的代码应该像下面的样子:

 ...
 QScriptValue result = myEngine.evaluate(...);
 if(myEngine.hasUncaughtException)
 {
   //错误处理
   int errlinenumber =myEngine.uncaughtExceptionLineNumber();
   ...
 }
 else
 {
    ...  //继续处理
 }
  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值