电竞比分网-中国电竞赛事及体育赛事平台

分享

跨平臺編程的利器

 torony 2016-04-14

【在寫本文的時候,看到新聞說Nokia準備放棄Qt,看來Qt要成為昨日黃花了,如果沒有大公司支持,任何語言都不會有強的生命力?!?br>

(本文接上篇“一個MDI圖形應用框架)

因為業(yè)務需要,作者比較關心一種語言或技術的用戶自定義能力,用戶自定義的“最高境界”就是能將腳本嵌入到程序中,從而改變或控制程序的運行。

從Qt4.3起,就支持腳本了,Qt中的腳本被稱為Qt Script,它是基于ECMAScript的,因此與我們通常用的javascript相同,據(jù)文章中介紹,Qt中的腳本引擎器采用的是Google的產品,也就是Chrome的JavaScript engine。

一、相關類的介紹

下表是Qt Script技術中有關的類及其簡介。

QScriptClass 是對script中類的封裝,可以在C++代碼中對script類進行調用。
QScriptClassPropertyIterator 該類似乎用途不大,只有在你基礎QScriptClass時,并且需要遍歷類的屬性時需要。
QScriptContext 這個類比較有用,通常作為參數(shù)傳遞到你的C++程序,可以得到調用的上下文環(huán)境:用來封裝script函數(shù)調用(指調用C++的函數(shù))的,基本上功能有兩個:一是提供this指針,二是提供調用的arguments
QScriptContextInfo QScriptContext的輔助信息
QScriptEngine 腳本解析引擎,創(chuàng)建一個腳本運行環(huán)境時必須使用。
QScriptEngineAgent 該類是調試Script的基礎工具類,可以通過設置Engine的Agent,捕獲特定的事件進行處理
QScriptEngineDebugger Script的調試類,一般情況下不用。
QScriptProgram 對Script運行程序的封裝,如果我們有一段寫好的腳本,并且它多次運行(QScriptProgram會將腳本編譯),則可以使用該類。
QScriptString 它用作一個對QScriptEngine中字符串的句柄,是對字符串的一個封裝。
QScriptSyntaxCheckResult 在用戶可以編寫自己的腳本時,該類比較有用,可以得到Script語法的檢查結果。
QScriptValue 有點類似于windows中的Variant類型,用于對腳本語言所有數(shù)據(jù)類型的封裝。
QScriptValueIterator 為QScriptValue提供了一種類似Java-style的迭代器(當QScriptValue是集合類型時)。
QScriptable 提供了一種在Qt C++環(huán)境中對腳本的控制方式,似乎與Qt中特有的消息觸發(fā)機制connect, slot等有關。

大致上,主要用到的類有(按使用頻度):QScriptEngine、QScriptValue、QScriptClass、QScriptContext,QScriptSyntaxCheckResult(如果需要檢查語法)。

二、與腳本的交互

作者曾經寫過一篇C#與Python交互的文章“在C#環(huán)境中動態(tài)調用IronPython腳本”,本文也按照該篇的情況,介紹Qt與腳本的交互。

1.從Scrip環(huán)境中返回數(shù)據(jù)

代碼如下。Script中定義了兩個函數(shù)cube和mysqrt,在C++環(huán)境中調用這兩個函數(shù)。

  1. void testScript()  
  2. {  
  3.     QScriptEngine myEngine;  
  4.     myEngine.evaluate("function cube(x) { return x * x * x; }    function mysqrt(x) { return Math.sqrt(x);} ");  
  5.     qDebug()<< myEngine.evaluate("mysqrt(cube(3))").toNumber();  
  6. }  

以下的代碼顯示了從Script中返回一個復雜的數(shù)據(jù)類型。

  1.  Q_DECLARE_METATYPE(QList<int>)  
  2. void testInvokeScript()  
  3. {  
  4.     QScriptEngine myEngine;  
  5.   
  6.     qScriptRegisterSequenceMetaType<QList<int> >(&myEngine);  
  7.      QScriptValue global = myEngine.globalObject();  
  8.   
  9.     myEngine.evaluate("var pack= new Array();  pack[0]=1; pack[1]='I am here.'; pack[2]=3;");  
  10.     myEngine.evaluate("var intarr= new Array(); intarr[0]=1; intarr[1]=2; intarr[2]=3;");  
  11.     QScriptValue vv = global.property("pack");  
  12.     QScriptValue vi = global.property("intarr");  
  13.   
  14.      for (int i = 0; i < vlist.size(); ++i)  qDebug()<<vlist.at(i);  
  15.   
  16.      QList<int> ilist = qscriptvalue_cast<QList<int> >(vi);  
  17.      for (int i = 0; i < ilist.size(); ++i)  qDebug()<<ilist.at(i);  
  18. }  

這是比較簡單的一種交互場景:用戶定義一個“封閉”的函數(shù)或類型,C++程序可以調用并返回結果。

2.Script環(huán)境中調用Qt環(huán)境中的函數(shù)或類

代碼如下,Qt中定義了一個類myclass,一個函數(shù)myAdd。在腳本中調用類方法和函數(shù),相當于:myclass.GetId()和myAdd()。

  1. class myclass : public QObject  
  2. {  
  3.     Q_OBJECT  
  4.     Q_PROPERTY(QString focus READ hasFocus)  
  5. public:  
  6.     explicit myclass(QObject *parent = 0)  
  7.     {  
  8.        m_id =10;  
  9.     }  
  10.   
  11.     Q_INVOKABLE int Test()  { return m_id;}  
  12.     Q_INVOKABLE QString GetName(QString prefix)  
  13.     {  
  14.        if(name == "a")  return m_id+10;  
  15.        if(name =="b" )  return m_id+20;  
  16.        return m_id;   
  17.     }  
  18.     QString hasFocus() const { return "abcde";}  
  19. signals:  
  20.   
  21. public slots:  
  22.   int GetId(QString name)  
  23.   {  
  24.     return "Hello " + prefix;    
  25.   }  
  26. private:  
  27.   int m_id;  
  28. };  
  29.   
  30.   
  31. QScriptValue MainWindow::myAdd(QScriptContext *context, QScriptEngine *engine, void *pargs)  
  32. {  
  33.    QScriptValue a = context->argument(0);  
  34.    QScriptValue b = context->argument(1);  
  35.    myclass *pmc =(myclass *)pargs;  
  36.    int i = pmc->GetId("c");  
  37.    return a.toNumber() + b.toNumber() + i;  
  38. }  
  39.   
  40. void MainWindow::testInvokeFunction()  
  41. {  
  42.     QScriptEngine myEngine;  
  43.     //修改button上的文字  
  44.      QScriptValue scriptButton = myEngine.newQObject(ui->btnOK);  
  45.      myEngine.globalObject().setProperty("button", scriptButton);  
  46.      myEngine.evaluate("button.text = \"true\"");  
  47.     //調用myAdd方法  
  48.      myclass *mc = new myclass(this);  
  49.      QScriptValue fun = myEngine.newFunction(myAdd,mc);  
  50.      myEngine.globalObject().setProperty("myAdd", fun);  
  51.      QScriptValue vv = myEngine.evaluate("myAdd(2,3)");  
  52.   
  53.     //調用類myclass的GetId方法       
  54.      myclass *mc2 = new myclass(this);  
  55.      QScriptValue qso = myEngine.newQObject(mc2);  
  56.      myEngine.globalObject().setProperty("qso",qso);  
  57.      QScriptValue ii = myEngine.evaluate("qso.GetId('aaa')");  
  58.   
  59.      qDebug()<<"vv="<<vv.toNumber();  
  60.      qDebug()<<"ii="<<ii.toString();  
  61. }  

測試代碼中,包含3部分內容:1.將button上的文字在腳本中修改了。2.腳本中調用myAdd方法,注意,腳本中所有的調用參數(shù),都封裝到了QScriptContext類中,我們只能從該類中得到,QScripEngine.newFunction方法可以給傳到腳本環(huán)境中的方法額外的參數(shù),即方法中第2個參數(shù),應用該參數(shù),有時會使代碼更清晰。本文中,給myAdd方法的額外參數(shù)是一個myclass類。3.腳本中定義了一個全局變量qso,它的類型就是Qt中的myclass,接著調用它的GetId方法。

上面代碼中,可以看出,如果需要將Qt中的類(本文是從QObject繼承的類,如不是QObject的子類,則比較麻煩)或方法傳遞到Script環(huán)境中,需要用到QScriptEngine的newQObject和newFunction兩個方法對類型實例進行封裝,然后再腳本環(huán)境中設置一個變量“指向”該類型實例,腳本就可以用了。

以上代碼中,myclass的實例是在Qt環(huán)境中創(chuàng)建的,如果需要在Script環(huán)境中創(chuàng)建一個myclass實例,可以采用如下的方法。

  1. QScriptValue MainWindow::createObject(QScriptContext *context, QScriptEngine *engine)  
  2. {  
  3.    QScriptValue a = context->argument(0);  
  4.    if(a.isString())  
  5.    {  
  6.        QString stringname=a.toString();  
  7.        if(stringname == "myclass")  
  8.        {  
  9.            myclass *mc2 = new myclass(0);  
  10.            return engine->newQObject(mc2);  
  11.        }  
  12.    }  
  13.    return NULL;  
  14. }  
  15.   
  16. void MainWindow::testCreateObject()  
  17. {  
  18.     QScriptEngine myEngine;  
  19.   
  20.    QScriptValue fun = myEngine.newFunction(createObject);  
  21.     myEngine.globalObject().setProperty("createObject", fun);  
  22.   
  23.      myEngine.evaluate("var obj=createObject('myclass');");  
  24.   
  25.      QScriptValue ii = myEngine.globalObject().property("obj");  
  26.   
  27.      qDebug()<<myEngine.evaluate("obj.GetName('me')").toString();  
  28.   
  29.   
  30. }  
本質上,myclass實例依然是在Qt中創(chuàng)建,但在腳本中,通過createObject函數(shù),似乎是在Script中創(chuàng)建的,這是一種簡單有效的方式。

3.Qt環(huán)境中調用Script環(huán)境中的數(shù)據(jù)或類

代碼如下。對于簡單變量,直接設置其值就可以了,如果該變量不存在,會自動添加,對于復雜變量,例如代碼中腳本環(huán)境中定義的類comx,當它作為調用參數(shù)調用Qt中的函數(shù)時,對它的解析不同樣簡單的變量。

  1. QScriptValue MainWindow::complexAdd(QScriptContext *context, QScriptEngine *engine)  
  2.   
  3.    QScriptValue a = context->argument(0);  
  4.    if(a.isObject())  
  5.    {  
  6.       QScriptClass *pc = a.scriptClass();  
  7.       if(pc == NULL)   qDebug()<<"pc is null";  
  8.       else  
  9.       {  
  10.         qDebug()<<pc->name();  
  11.       }  
  12.       QScriptValue val(engine, 123);  
  13.       a.setProperty("x", val);  
  14.    }  
  15.    return NULL;  
  16. }  
  17.   
  18. void MainWindow::testQSCriptClass()  
  19. {  
  20.     QScriptEngine myEngine;  
  21.   
  22.    myEngine.evaluate("var ii=3;  function cube(x) { return x * x * x; }");  
  23.    myEngine.globalObject().setProperty("myNumber", 12);  
  24.    myEngine.globalObject().setProperty("ii", 5);      
  25.    qDebug()<<myEngine.evaluate("cube(myNumber + 1)").toNumber();  
  26.    qDebug()<<myEngine.evaluate("cube(ii)").toNumber();  
  27.   
  28.     myEngine.evaluate("var comx={x:1, y:2};  var aobj= new Object();  aobj.x=1; aobj.y=2;");  
  29.     QScriptValue fun = myEngine.newFunction(complexAdd);  
  30.     myEngine.globalObject().setProperty("complexAdd", fun);  
  31.   
  32.     myEngine.evaluate("complexAdd(aobj);");  
  33.   
  34.    qDebug()<<  myEngine.evaluate("aobj.x").toString();  
  35.  }  

4.一個為Script中類添加方法的例子

代碼如下,該例子為Script中的類person,添加了一個方法,fullName,實際上該方法在Qt環(huán)境中定義,注意此例中QScriptContext的用法。

  1. QScriptValue MainWindow::Person_prototype_fullName(QScriptContext *context, QScriptEngine *engine)  
  2.  {  
  3.      QScriptValue self = context->thisObject();  
  4.      QString result;  
  5.      result += self.property("firstName").toString();  
  6.      result += QLatin1String(" ");  
  7.      result += self.property("lastName").toString();  
  8.      return result;  
  9.  }  
  10.    
  11.   void MainWindow::testQSCriptContext()  
  12.  {  
  13.   
  14.      QScriptEngine myEngine;  
  15.      QScriptValue fun = myEngine.newFunction(Person_prototype_fullName);  
  16.      myEngine.globalObject().setProperty("Person_prototype_fullName", fun);  
  17.   
  18.      myEngine.evaluate("var person={firstName:'Wang', lastName:'Yi',fullName:Person_prototype_fullName};");  
  19.      //one way to use  
  20.      QScriptValue who = myEngine.globalObject().property("person");  
  21.      qDebug()<< fun.call(who).toString();  
  22.      //another way to use  
  23.       qDebug()<<  myEngine.evaluate("person.fullName()").toString();  
  24.   }  


5.Script句法檢查

當允許用戶自定義腳本時,好的程序應檢查其正確性。正確性包括兩個方面:運行前靜態(tài)的語法檢查;運行中能捕獲運行中的錯誤并返回給用戶。
語法檢查可以調用

  1. QScriptSyntaxCheckResult QScriptEngine::checkSyntax ( const QString & program )  
該方法是一個靜態(tài)方法。

運行時,如果有錯誤,QScriptEngine.evaluate方法將返回一個Error對象,但由于QScriptValue可以封裝任何數(shù)據(jù)類型,所以我們需要有其它的函數(shù)來便利地判斷是否有異常發(fā)生,比較健壯的代碼應該像下面的樣子:

  1. ...  
  2. QScriptValue result = myEngine.evaluate(...);  
  3. if(myEngine.hasUncaughtException)  
  4. {  
  5.   //錯誤處理  
  6.   int errlinenumber =myEngine.uncaughtExceptionLineNumber();  
  7.   ...  
  8. }  
  9. else  
  10. {  
  11.    ...  //繼續(xù)處理  
  12. }  

為簡單起見,本文以上的代碼并未按上面的格式。



    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多