為Delphi應用增加腳本支持


上次說到我想為DirectUI增加腳本支持,今天我們就來研究下腳本的實現問題。

雖然現在有了諸如AntLR、GOLD Parser、TP Lex and Yacc等等CC工具,大大方便了腳本引擎的開發,但我仍然覺得在這個框架里自己實現一套腳本引擎是件毫無意義的事。所以我決定使用現有的腳本引擎。

事實上Delphi可用的第三方腳本引擎很多,這里列幾個常見的:

FastScript

號稱最快的Delphi腳本引擎,支持 VB、JS、Cpp、Pascal 語法,可以在腳本中使用自定義的類型和對象,內置了對Delphi VCL的支持。

ifps/RemObjects PascalScript

使用Pascal語法的腳本引擎,可以在腳本中使用自定義的類型和對象。

TMS Scripter Studio

TMS出品的腳本引擎,除了腳本引擎,還附帶有一堆可視控件,可以用來開發IDE。

支持在腳本中使用自定義的類型和對象,也內置了對Delphi VCL的支持。

PaxScript / PaxCompiler

這是我用過的Delphi里最強大的腳本引擎了。同樣,她支持 VB、JS、Cpp、Pascal 語法,也支持在腳本中使用自定義的類型和對象,內置了對Delphi VCL的支持。

另外,她還支持將腳本代碼編譯成中間字節碼,以后運行是可以直接載入中間字節碼執行,節約了代碼解析的時間。如果使用PaxCompiler,你甚至可以將腳本代碼編譯成機器碼直接執行!

但是最終我並沒有在其中進行選擇,因為上述這些腳本引擎都有一個問題,就是對自定義的類型和對象的支持方式。

在這些引擎中,如果你要增加自定義類型的支持,就必須為每一個類的每一個方法和屬性生成一個代理函數,然后通過引擎通過這些代理函數對對象進行操作。

想想我的DirectUI引擎,其中大大小小有上百個類,光生成這些代理函數都能讓我瘋了。雖然它們也有提供工具,可以根據源代碼直接生成代理函數,但問題是我以后如果修改了源代碼,豈不是又得重新生成一遍?想想都煩啊。

最后我把目光定在了MS身上。其實MS早就為我們提供了一套好用的腳本引擎:MS ScriptControl。見過HTML里的VBS和JS嗎?那里用的腳本引擎跟MS ScriptControl是同一套核心。

MS ScriptControl系統默認支持VBS和JS,但實際上他的語法是可以擴展的。例如你機器上如果裝有Python,你就能在腳本中使用Python。

MS ScriptControl可以訪問自動化對象(對應到Delphi里就是TAutoObject),將對象用AddObject方法注冊到引擎里,就可以在腳本中訪問了。在運行時,引擎會取得對象的IDispatch接口實現,並通過IDispatch.GetIDsOfNames取得要訪問的方法或屬性的ID,然后在調用IDispatch.Invoke執行此方法或取得此屬性的值。

聰明的你一定想到了,我只要實現一個TAutoObject代理對象,然后用這個對象通過RTTI,就可以訪問到我們的自定義對象了!借助RTTI,我只需要實現一個通用的代理對象,就能讓腳本訪問所有類型的對象,即使類型有更改,代理對象都不需要做任何改動!真正的一勞永逸啊,哇咔咔~~~

 


 

 

好了,飯要一口一口吃,現在我們先把MS ScriptControl封裝一下,后面再去做代理對象。

我們先來定義一套接口,這套接口定義了我們的腳本引擎所需要實現的功能,然后再封裝MS ScriptControl來實現。這樣做的好處是,如果我以后想換其他的腳本引擎,只需要把這套接口重新實現一遍就好了,外部調用腳本引擎的地方基本無需改動。

代碼不貼上來了,后面有下載。

 

這里還要說說MS ScriptControl的一些東西。其實網上相關的文章不少,不過大多都是泛泛而談,有用的東西不多,我只好自己研究了。

簡單的東西就不寫了,去google吧,這里講幾個網上找不到相關說明的細節。

MS ScriptControl中有模塊的概念,就像我們IDE中同一工程下不同的單元文件一樣。默認有一個主模塊,名字叫Global,MS ScriptControl中的AddCode、Eval、ExecuteStatetment等方法都是對這個模塊訪問的封裝。其他模塊是需要用Modules.Add自己添加的。

這里有個細節,Modules.Add方法有2個參數:Name,模塊名稱,必填;Obj,OleVariant類型,是個可選參數。這個參數做什么用的我到現在也沒明白,本來我也不想關心他的,可惜繞不過去。在VB中,DispInterface的可選參數可以不寫,直接Modules.Add("Module1")就OK,可是Delphi下這個參數是var的,必須填。於是我

var
  Obj: OleVariant; 
begin
  Obj := Unassigned;
  Modules.Add( ' Module1 ', Obj);
end;

運行報錯。。。於是我再Obj := Null,錯。。。再Obj := 0,又錯。。。數次失敗后,我都快絕望了。。。后來我想,這個參數我也不用,不是有個全局的EmptyParam么,省了我聲明個變量,於是Modules.Add('Module1', EmptyParam),結果。。。通過了。。。神那,你能告訴我這參數到底干蝦米用的啊。。。淚啊。。。

 

繼續。。。與編譯器對待單元文件的方式不同,這些模塊都是封閉的,除了其他的模塊可以直接調用主模塊中方法外,其他模塊之間無法直接互相調用,主模塊也無法調用其他模塊。挺奇怪的。

后來我想了個轍。每個模塊都有個屬性CodeObject,即該模塊的代碼對象,通過他是可以調用到模塊中的代碼的。這個對象可是IDispatch哦,那么我將他注冊到腳本引擎里,不就可以在其他模塊中用 模塊名.對象 來訪問了嗎?我真是太聰明了,咔咔。。。於是乎,就有了下面的代碼

var
  I: Integer;
  Module: IScriptModule;
begin
   for I :=  1  to Modules.Count  do
   begin
    Module := Modules[I];
    FScript.AddObject(Module.Name, Module, False);
   end;
end;

 F5,嗯,出錯?對象名已存在??NN個熊,要存在我怎么訪問不到!算了,只好出昏招,我注冊名跟模塊名不一樣,總可以了吧。。。

 

唉,折騰了一天,總算是可以用了~

圖上是調用第一個模塊的Foo方法的執行結果

 

好了,羅羅嗦嗦了這么多,就到這里吧,下次再來實現代理對象。

 

代碼下載  測試程序下載 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM