在C#中調(diào)用VBScript、JavaScript等腳本的實(shí)現(xiàn)
作者:秋楓
以前在做工作流(workflow)項(xiàng)目的時(shí)候,里面有一項(xiàng)就是在用戶制定流程定義時(shí)可以編寫腳本來控制活動(dòng)的跳轉(zhuǎn),而這些腳本定義后存在數(shù)據(jù)庫中,當(dāng)流程啟動(dòng)的時(shí)候,工作流引擎會(huì)控制活動(dòng)執(zhí)行順序,串型的兩個(gè)活動(dòng)比較簡單,但有的活動(dòng)到下一個(gè)活動(dòng)有條件判斷,或者存在多個(gè)分支,簡單的還好,只要在數(shù)據(jù)庫表中加個(gè)字段就可以實(shí)現(xiàn),復(fù)雜一點(diǎn)的就需要通過腳本實(shí)現(xiàn)了。當(dāng)時(shí)經(jīng)驗(yàn)不夠,幾天都沒找到快速的解決辦法,想自己寫一個(gè)自定義腳本引擎沒有把握,而且時(shí)間也不夠,還是在網(wǎng)上找找看吧,花了一些時(shí)間,還是找到了一個(gè)自認(rèn)為比較好的解決辦法,寫出來同大家分享。 下面通過兩部分來說明實(shí)現(xiàn)以及應(yīng)用。
一.使用MSScriptControl
到微軟的網(wǎng)站上下載Windows Script Control,它是一個(gè)ActiveX(R) 控件,所以在.NET中使用我Interop了一下。下載安裝完成后,新建一個(gè)C#的Windows應(yīng)用程序項(xiàng)目,在解決方案資源管理器中選中引用節(jié)點(diǎn),右鍵點(diǎn)擊選擇添加引用菜單,彈出添加引用對話框,單擊瀏覽找到安裝Windows Script Control的目錄,選取msscript.ocx文件確定。那么在引用節(jié)點(diǎn)下會(huì)增加一個(gè)MSScriptControl組件,下面是他Interop后的所有對象。
ScriptControl 對支持 ActiveX(TM) Script 的宿主 Script 引擎提供簡單接口。接下來我們對被轉(zhuǎn)化成ScriptControlClass類的ScriptControl的屬性和方法進(jìn)行一些說明。
屬性
AllowUI 屬性:應(yīng)用于 ScriptControl 本身或 Scirpt 引擎顯示的用戶界面元素,可讀寫。
CodeObject 屬性:返回對象,該對象用于調(diào)用指定模塊的公用成員。只讀。
Error 屬性:返回 Error 對象,其中包含所發(fā)生的最后一個(gè)錯(cuò)誤的相關(guān)詳細(xì)信息。只讀。
Language 屬性:設(shè)置或返回正在使用的 Script 語言名稱?勺x寫。
Modules 屬性:為 ScriptControl 對象返回模塊集合。只讀。
Procedures 屬性:返回在指定模塊中定義的過程集合。只讀。
SitehWnd 屬性:設(shè)置或返回窗口的 hWnd,通過執(zhí)行 Script 代碼,此窗口用于顯示對話框和其他用戶界面元素?勺x寫。
State 屬性:設(shè)置或返回 ScriptControl 對象的模式?勺x寫。
Timeout 屬性:設(shè)置或返回時(shí)間(毫秒),此時(shí)間后用戶可選擇中止 Script 代碼的執(zhí)行或允許代碼繼續(xù)執(zhí)行?勺x寫。
UseSafeSubset 屬性:設(shè)置或返回 Boolean 值,指明宿主應(yīng)用程序是否有保密性要求。如果宿主應(yīng)用程序需要安全控制,則 UseSafeSubset 為 True,否則為 False?勺x寫。
方法
AddCode 方法:向模塊添加指定代碼?啥啻握{(diào)用 AddCode 方法。
AddObject 方法:使主機(jī)對象模型對 Script 引擎可用。
Eval 方法:計(jì)算表達(dá)式并返回結(jié)果。
ExecuteStatement 方法:執(zhí)行指定的語句。
Reset 方法:放棄所有已經(jīng)添加到 ScriptControl 中的 Script 代碼和對象。
Run 方法:運(yùn)行指定過程。
事件
Error 事件:出現(xiàn)運(yùn)行時(shí)錯(cuò)誤時(shí),發(fā)生此事件。
Timeout 事件:當(dāng)超出了 Timeout 屬性指定的時(shí)間且用戶在結(jié)果對話框中選定了 End 時(shí),發(fā)生此事件。
補(bǔ)充幾點(diǎn)
AllowUI 屬性如果設(shè)置為false,則顯示對話框之類的語句不起作用,如在 VBScript 中MsgBox 語句,JavaScript中的alert等,并且如果執(zhí)行的腳本超出TimeOut設(shè)置的毫秒數(shù),也不會(huì)跳出超出時(shí)間提醒的對話框,反之則相反;重新設(shè)置 Language 屬性會(huì)清空AddCode加載的代碼;對于TimeOut屬性,發(fā)生超時(shí)時(shí),ScriptControl 檢查對象的 AllowUI 屬性,確定是否允許顯示用戶界面元素。
如果讀者需要更詳細(xì)的了解,可以查看MSDN文檔。
為了使控件更容易使用,我用一個(gè)ScriptEngine類包裝了一下,下面是完整代碼:
using System;
using MSScriptControl;
using System.Text;
namespace ZZ
{
/// <summary>
/// 腳本類型
/// </summary>
public enum ScriptLanguage
{
/// <summary>
/// JScript腳本語言
/// </summary>
JScript,
/// <summary>
/// VBscript腳本語言
/// </summary>
VBscript,
/// <summary>
/// JavaScript腳本語言
/// </summary>
JavaScript
}
/// <summary>
/// 腳本運(yùn)行錯(cuò)誤代理
/// </summary>
public delegate void RunErrorHandler();
/// <summary>
/// 腳本運(yùn)行超時(shí)代理
/// </summary>
public delegate void RunTimeoutHandler();
/// <summary>
/// ScriptEngine類
/// </summary>
public class ScriptEngine
{
private ScriptControl msc;
//定義腳本運(yùn)行錯(cuò)誤事件
public event RunErrorHandler RunError;
//定義腳本運(yùn)行超時(shí)事件
public event RunTimeoutHandler RunTimeout;
/// <summary>
///構(gòu)造函數(shù)
/// </summary>
public ScriptEngine():this(ScriptLanguage.VBscript)
{
}
/// <summary>
/// 構(gòu)造函數(shù)
/// </summary>
/// <param name="language">腳本類型</param>
public ScriptEngine(ScriptLanguage language)
{
this.msc = new ScriptControlClass();
this.msc.UseSafeSubset = true;
this.msc.Language = language.ToString();
((DScriptControlSource_Event)this.msc).Error += new DScriptControlSource_ErrorEventHandler(ScriptEngine_Error);
((DScriptControlSource_Event)this.msc).Timeout += new DScriptControlSource_TimeoutEventHandler(ScriptEngine_Timeout);
}
/// <summary>
/// 運(yùn)行Eval方法
/// </summary>
/// <param name="expression">表達(dá)式</param>
/// <param name="codeBody">函數(shù)體</param>
/// <returns>返回值object</returns>
public object Eval(string expression,string codeBody)
{
msc.AddCode(codeBody);
return msc.Eval(expression);
}
/// <summary>
/// 運(yùn)行Eval方法
/// </summary>
/// <param name="language">腳本語言</param>
/// <param name="expression">表達(dá)式</param>
/// <param name="codeBody">函數(shù)體</param>
/// <returns>返回值object</returns>
public object Eval(ScriptLanguage language,string expression,string codeBody)
{
if(this.Language != language)
this.Language = language;
return Eval(expression,codeBody);
}
/// <summary>
/// 運(yùn)行Run方法
/// </summary>
/// <param name="mainFunctionName">入口函數(shù)名稱</param>
/// <param name="parameters">參數(shù)</param>
/// <param name="codeBody">函數(shù)體</param>
/// <returns>返回值object</returns>
public object Run(string mainFunctionName,object[] parameters,string codeBody)
{
this.msc.AddCode(codeBody);
return msc.Run(mainFunctionName,ref parameters);
}
/// <summary>
/// 運(yùn)行Run方法
/// </summary>
/// <param name="language">腳本語言</param>
/// <param name="mainFunctionName">入口函數(shù)名稱</param>
/// <param name="parameters">參數(shù)</param>
/// <param name="codeBody">函數(shù)體</param>
/// <returns>返回值object</returns>
public object Run(ScriptLanguage language,string mainFunctionName,object[] parameters,string codeBody)
{
if(this.Language != language)
this.Language = language;
return Run(mainFunctionName,parameters,codeBody);
}
/// <summary>
/// 放棄所有已經(jīng)添加到 ScriptControl 中的 Script 代碼和對象
/// </summary>
public void Reset()
{
this.msc.Reset();
}
/// <summary>
/// 獲取或設(shè)置腳本語言
/// </summary>
public ScriptLanguage Language
{
get{return (ScriptLanguage)Enum.Parse(typeof(ScriptLanguage),this.msc.Language,false);}
set{this.msc.Language = value.ToString();}
}
/// <summary>
/// 獲取或設(shè)置腳本執(zhí)行時(shí)間,單位為毫秒
/// </summary>
public int Timeout
{
get{return this.msc.Timeout;}
set{this.msc.Timeout = value;}
}
/// <summary>
/// 設(shè)置是否顯示用戶界面元素
/// </summary>
public bool AllowUI
{
get{return this.msc.AllowUI;}
set{this.msc.AllowUI = value;}
}
/// <summary>
/// 宿主應(yīng)用程序是否有保密性要求
/// </summary>
public bool UseSafeSubset
{
get{return this.msc.UseSafeSubset;}
set{this.msc.UseSafeSubset = true;}
}
/// <summary>
/// RunError事件激發(fā)
/// </summary>
private void OnError()
{
if(RunError!=null)
RunError();
}
/// <summary>
/// OnTimeout事件激發(fā)
/// </summary>
private void OnTimeout()
{
if(RunTimeout!=null)
RunTimeout();
}
private void ScriptEngine_Error()
{
OnError();
}
private void ScriptEngine_Timeout()
{
OnTimeout();
}
}
}
上面的包裝定義了一個(gè)ScriptLanguage枚舉,這樣操作起來更方便一點(diǎn)。另外腳本引擎包括了Error事件和Timeout事件,根據(jù)實(shí)際使用情況可進(jìn)行注冊。
二.腳本引擎演示
我建了個(gè)窗體程序,測試包括腳本語言的選擇,是否開啟AllowUI屬性,超時(shí)時(shí)間的設(shè)置,以及腳本引擎調(diào)用方法的選擇。測試程序代碼比較長,下面列出了主要部分:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace ZZ
{
public class Form1 : System.Windows.Forms.Form
{
private ScriptEngine scriptEngine;
private System.Windows.Forms.CheckBox checkBoxAllowUI;
private System.Windows.Forms.TextBox textBoxResult;
private System.Windows.Forms.NumericUpDown numericUpDownTimeout;
private System.Windows.Forms.TextBox textBoxCodeBody;
private System.Windows.Forms.Button buttonRun;
private System.Windows.Forms.Button buttonCancel;
private System.Windows.Forms.ComboBox comboBoxScript;
private System.Windows.Forms.TextBox textBoxParams;
private System.Windows.Forms.RadioButton radioButtonEval;
private System.Windows.Forms.RadioButton radioButtonRun;
private System.Windows.Forms.TextBox textBoxMethodName;
private System.ComponentModel.Container components = null;
public Form1()
{
InitializeComponent();
this.comboBoxScript.SelectedIndex = 0;
this.scriptEngine = new ScriptEngine();
this.scriptEngine.UseSafeSubset = true;
this.scriptEngine.RunError += new RunErrorHandler(scriptEngine_RunError);
this.scriptEngine.RunTimeout += new RunTimeoutHandler(scriptEngine_RunTimeout);
}
protected override void Dispose( bool disposing )
{
if( disposing )
if (components != null)
components.Dispose();
base.Dispose( disposing );
}
#region Windows 窗體設(shè)計(jì)器生成的代碼
private void InitializeComponent()
{
//省略
}
#endregion
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
//運(yùn)行腳本
private void buttonRun_Click(object sender, System.EventArgs e)
{
this.scriptEngine.Reset();
this.scriptEngine.Language = (ScriptLanguage)Enum.Parse(typeof(ScriptLanguage),this.comboBoxScript.SelectedItem.ToString());
this.scriptEngine.Timeout = (int)this.numericUpDownTimeout.Value;
this.scriptEngine.AllowUI = this.checkBoxAllowUI.Checked;
if(this.radioButtonEval.Checked)//執(zhí)行Eval方法
{
this.textBoxResult.Text = this.scriptEngine.Eval(this.textBoxMethodName.Text+"("+this.textBoxParams.Text+")",this.textBoxCodeBody.Text).ToString();
}
else//執(zhí)行Run方法
{
string[] parameters = (string[])this.textBoxParams.Text.Split(',');
object [] paramArray = new object[parameters.Length];
for(int i = 0;i<parameters.Length;i++)
paramArray[i] = Int32.Parse(parameters[i]);
this.textBoxResult.Text = this.scriptEngine.Run(this.textBoxMethodName.Text,paramArray,this.textBoxCodeBody.Text).ToString();
}
}
//退出程序
private void buttonCancel_Click(object sender, System.EventArgs e)
{
this.Close();
}
//錯(cuò)誤函數(shù)
private void scriptEngine_RunError()
{
MessageBox.Show("RunError執(zhí)行腳本錯(cuò)誤!");
}
private void scriptEngine_RunTimeout()
{
MessageBox.Show("RunTimeout執(zhí)行腳本超時(shí),引發(fā)錯(cuò)誤!");
}
}
}
下面是測試程序運(yùn)行界面:
在文本框中寫了一個(gè)JavaScript的函數(shù)。輸入12,輸出12000012。
如果把超時(shí)時(shí)間調(diào)整為1毫秒,那么執(zhí)行該腳本就會(huì)跳出下面的超時(shí)提醒框,同時(shí)
|