|
【在寫本文的時候,看到新聞說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ù)。
- 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中返回一個復雜的數(shù)據(jù)類型。
- 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);
- }
這是比較簡單的一種交互場景:用戶定義一個“封閉”的函數(shù)或類型,C++程序可以調用并返回結果。
2.Script環(huán)境中調用Qt環(huán)境中的函數(shù)或類
代碼如下,Qt中定義了一個類myclass,一個函數(shù)myAdd。在腳本中調用類方法和函數(shù),相當于: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方法,注意,腳本中所有的調用參數(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實例,可以采用如下的方法。
- 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中創(chuàng)建,但在腳本中,通過createObject函數(shù),似乎是在Script中創(chuàng)建的,這是一種簡單有效的方式。
3.Qt環(huán)境中調用Script環(huán)境中的數(shù)據(jù)或類
代碼如下。對于簡單變量,直接設置其值就可以了,如果該變量不存在,會自動添加,對于復雜變量,例如代碼中腳本環(huán)境中定義的類comx,當它作為調用參數(shù)調用Qt中的函數(shù)時,對它的解析不同樣簡單的變量。
- 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環(huán)境中定義,注意此例中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句法檢查
當允許用戶自定義腳本時,好的程序應檢查其正確性。正確性包括兩個方面:運行前靜態(tài)的語法檢查;運行中能捕獲運行中的錯誤并返回給用戶。
語法檢查可以調用
- QScriptSyntaxCheckResult QScriptEngine::checkSyntax ( const QString & program )
該方法是一個靜態(tài)方法。
運行時,如果有錯誤,QScriptEngine.evaluate方法將返回一個Error對象,但由于QScriptValue可以封裝任何數(shù)據(jù)類型,所以我們需要有其它的函數(shù)來便利地判斷是否有異常發(fā)生,比較健壯的代碼應該像下面的樣子:
- ...
- QScriptValue result = myEngine.evaluate(...);
- if(myEngine.hasUncaughtException)
- {
- //錯誤處理
- int errlinenumber =myEngine.uncaughtExceptionLineNumber();
- ...
- }
- else
- {
- ... //繼續(xù)處理
- }
為簡單起見,本文以上的代碼并未按上面的格式。
|