2019/09/03

Script Engine in Delphi


圖片來源:網路

系統維護一段時間之後,總會遇到各式各樣的需求,哪怕是改字改公式這樣的小需要,都必須把專案修改後重新編譯以及發佈這樣的改版模式,才能讓使用者順利使用。

『如果有一套語言引擎嵌入到系統中,我只需要改腳本就能夠無痛更新。』

這樣的念頭在我腦海中出現,有想法就來試試看。

在開始之前,自造輪子不如使用輪子,來看看 3rd party 有沒有好用的函式庫可以使用,如果有就不需要再重新寫起。


Devart 的  VirtualDAC (建議售價:79.95 鎂起)

VirtualDAC 的 VirtualQuery 概念圖 (圖片來源:Devart)
資料庫應用裡最常使用到的就是 TDataSet,VirtualQuery 特色就是【利用 SQL 語言把多個 TDataSet 串起來,成為新的資料集】,支援 SQL92,經常需要做各種資料集統計時,這組元件可以省下很多寫 SQL 的時間。

JSEngine (建議售價:80 鎂起)

JSEngine 展示畫面 (圖片來源:WinSoft)
使用 JavaScript Engine 作為基底,可以在應用程式中寫簡單的 JavaScript 語言並且能和 Delphi 程式交互使用,使用 JavaScript 能寫出更為複雜的商業程式內容,而且還不用重新編譯就能執行,使用價值相當高的工具。

ScriptGate (Open source for FireMonkey)

ScriptGate 官網

包裝 TWebBrowser,讓 Delphi 利用它和 JavaScript 進行交互作業,從 Delphi 10.2 開始支援,使用 FireMonkey 框架,也能夠順利跨平台,有多平台開發的需求者一定要來試用看看。


其它對我而言學習曲線蠻高的,以上就我可以理解使用的項目分享給大家參考。


因為現有的專案仍舊是 VCL,所以 ScriptGate就無法應用在專案裡面,不過,既然可以透過 TWebBrowser 來和 JavaScript 互動,想必 VCL 應該也能如法泡製才是。

ScriptGate for VCL DIY

本著踩在巨人的肩膀上看世界為原則,谷哥哥給了一個令人驚奇的答案:


果然已經有人做好了!


在 http://delphidabbler.com 裡的 Article 14 和 21 兩篇文章,就已經有 ScriptGate 的內容了。
感謝 Mauricio Julio 貢獻,讓我們可以用到這麼好的作品
有了工具,再來就是實作,最重要的就是腳本了:

HTML & JavaScript

在這邊重點有兩個:
  • JavaScript DoEval function
輸入運算式並將運算結果傳到【result】Input 標籤裡。
  • Input tag
不顯示,只用來記錄 DoEval 運算結果。

Delphi

在Delphi中主要還是對TWebBrowser進行存取,文章的範例不難,在這邊複製貼上:

function ExecuteJavaScript(WebBrowser:TWebBrowser; Code: string):Variant;
var
  Document:IHTMLDocument2;
  Window:IHTMLWindow2;
begin
  // execute javascript in webbrowser
  Document:=WebBrowser.Document as IHTMLDocument2;
  if not Assigned(Document) then Exit;
  Window:=Document.parentWindow;
  if not Assigned(Window) then Exit;
  try
    Result:=Window.execScript(Code,'JavaScript');
  except
    on E:Exception do raise Exception.Create('Javascript error '+E.Message+' in: '#13#10+Code);
  end;
end;
 
function GetElementIdValue(WebBrowser:TWebBrowser; TagName,TagId,TagAttrib:String):String;
var
  Document: IHTMLDocument2;
  Body: IHTMLElement2;
  Tags: IHTMLElementCollection;
  Tag: IHTMLElement;
  I: Integer;
begin
  Result:='';
  if not Supports(WebBrowser.Document, IHTMLDocument2, Document) then
    raise Exception.Create('Invalid HTML document');
  if not Supports(Document.body, IHTMLElement2, Body) then
    raise Exception.Create('Can''t find );
  Tags := Body.getElementsByTagName(UpperCase(TagName));
  for I := 0 to Pred(Tags.length) do begin
    Tag:=Tags.item(I,EmptyParam) as IHTMLElement;
    if Tag.id=TagId then Result:=Tag.getAttribute(TagAttrib,0);
  end;
end;

TWebBrowser 在存取上其實還有些細節,不一定要用 UWebBrowserWrapper 單元,但用它不吃虧,於是最終測試碼如下:

procedure TForm2.Button2Click(Sender: TObject);
var
  LWbWra: TWebBrowserWrapper;
begin
  LWbWra := TWebBrowserWrapper.Create(WebBrowser1);
  try
    LWbWra.NavigateToLocalFile(ExtractFilePath(Application.ExeName)+'test1.htm');
    ExecuteJavaScript(LWbWra.WebBrowser,'DoEval("'+Edit1.Text+'");');
    Caption := GetElementIdValue(LWbWra.WebBrowser,'input','result','value');
  except
    on E:Exception do
      raise E
  end;
  LWbWra.Free;
end;


每一次的呼叫都會重新載入 HTML 檔案,所以修改完就可即時測試程式內容。


由於 Delphi 所附的 TWebBrowser 的核心是系統內的 Internet Explorer (IE),因此在 JavaScript 的支援上要留意 OS 各版的相容性,這點請開發者務必要留意。


以上各家的 Script Engine 提供給大家參考。 下次見 ^_^

===2019/09/25更新===
因為 VCL TWebBrowser 使用的就是 IE 核心,抓這個元件來用其實有點繞遠路了。

按照【Delphi中ScriptControl的高级应用】這篇所言,可以呼叫 MSScriptControl.ScriptControl ActiveX 元件,如此一來就可以把 TWebBrowser 這麼大的元件放下。

唯二的缺點大概就是 Debug 比較困難這件事。

另一方面,萬惡的 IE 本身也有很多漏洞,諸如:

但遇到被系統管理員限制等情形,軟體功能會被很大的限縮,不管是採行 TWebBrowser 或 ScriptControl 解決方案都比須要留意。

再者,被限縮事小,如果又因為漏洞而被當駭客的攻擊跳板,進而造成嚴重資安問題,就得不償失了!

====== 2022/01/14 ======

ScriptEngine 除了有個難以 Debug 的小缺點外,它其實還蠻方便的,Eden 做了兩個練習:

查詢 JScript Engine 版本

程式碼

uses
  ComObj;

procedure TForm2.btnscriptVersionClick(Sender: TObject);
var
  sc : OleVariant;
begin
  sc := CreateOleObject('MSScriptControl.ScriptControl.1');
  sc.Language :='JavaScript'; // Eden 2022/01/14
  sc.AddCode(mmoJscriptVersion.Text);
  lbJscriptVersion.Caption := sc.Run('GetScriptEngineInfo');
  sc := Unassigned;
end;

JS 程式碼參考微軟說明手冊,可以得到 Windows 10 的 JScript 版本為 5.8,相當於 JavaScript 1.5 (2000年) 版本。


JSON 字串化

程式碼

uses
  ComObj;

procedure TForm2.btnscriptVersionClick(Sender: TObject);
var
  sc : OleVariant;
begin
  sc := CreateOleObject('MSScriptControl.ScriptControl.1');
  sc.Language :='JavaScript'; // Eden 2022/01/14
  sc.AddCode(mmoJson2.Text);
  sc.AddCode(mmoJSON.Text);
  lbJSON.Caption := sc.Run('getJSONDemo');
  sc := Unassigned;
end;

因為 JScript 要向下相容 IE6, 7,所以並未將 JSON 物件啟用 (ECMA-262 3rd edition + ECMA-327(ES-CP) non JSON),在這裡我引入外部 JSON2 檔案,再取用微軟文件的範例程式,可以順利使用 JSON!

See also :


沒有留言:

張貼留言