以前在做工作流程(workflow)項目的時候,裡面有一項就是在使用者制定流程定義時可以編寫指令碼來控制活動的跳轉,而這些指令碼定義後存在資料庫中,當流程啟動的時候,工作流程引擎會控制活動執行順序,串型的兩個活動比較簡單,但有的活動到下一個活動有條件判斷,或者存在多個分支,簡單的還好,只要在資料庫表中加個欄位就可以實現,複雜一點的就需要通過指令碼實現了。當時經驗不夠,幾天都沒找到快速的解決辦法,想自己寫一個自訂指令碼引擎沒有把握,而且時間也不夠,還是在網上找找看吧,花了一些時間,還是找到了一個自認為比較好的解決辦法,寫出來同大家分享。 下面通過兩部分來說明實現以及應用。 一.使用MSScriptControl 到微軟的網站上下載Windows Script Control,它是一個ActiveX(R) 控制項,所以在.NET中使用我Interop了一下。下載安裝完成後,建立一個C#的Windows應用程式項目,在方案總管中選中引用節點,右鍵點擊選擇添加引用菜單,彈出添加引用對話方塊,單擊瀏覽找到安裝Windows Script Control的目錄,選取msscript.ocx檔案確定。那麼在引用節點下會增加一個MSScriptControl組件,下面是他Interop後的所有對象。 ScriptControl 對支援 ActiveX(TM) Script 的宿主 Script 引擎提供簡單介面。接下來我們對被轉化成ScriptControlClass類的ScriptControl的屬性和方法進行一些說明。 屬性 AllowUI 屬性:應用於 ScriptControl 本身或 Scirpt 引擎顯示的使用者介面元素,可讀寫。 CodeObject 屬性:返回對象,該對象用於調用指定模組的公用成員。唯讀。 Error 屬性:返回 Error 對象,其中包含所發生的最後一個錯誤的相關詳細資料。唯讀。 Language 屬性:設定或返回正在使用的 Script 語言名稱。可讀寫。 Modules 屬性:為 ScriptControl 對象返回模組集合。唯讀。 Procedures 屬性:返回在指定模組中已定義流程集合。唯讀。 SitehWnd 屬性:設定或返回視窗的 hWnd,通過執行 Script 代碼,此視窗用於顯示對話方塊和其他使用者介面元素。可讀寫。 State 屬性:設定或返回 ScriptControl 對象的模式。可讀寫。 Timeout 屬性:設定或返回時間(毫秒),此時間後使用者可選擇中止 Script 代碼的執行或允許代碼繼續執行。可讀寫。 UseSafeSubset 屬性:設定或返回 Boolean 值,指明宿主應用程式是否有保密性要求。如果宿主應用程式需要安全控制,則 UseSafeSubset 為 True,否則為 False。可讀寫。 方法 AddCode 方法:向模組添加指定代碼。可多次調用 AddCode 方法。 AddObject 方法:使主機物件模型對 Script 引擎可用。 Eval 方法:計算運算式並返回結果。 ExecuteStatement 方法:執行指定的語句。 Reset 方法:放棄所有已經添加到 ScriptControl 中的 Script 代碼和對象。 Run 方法:運行指定過程。 事件 Error 事件:出現執行階段錯誤時,發生此事件。 Timeout 事件:當超出了 Timeout 屬性指定的時間且使用者在結果對話方塊中選定了 End 時,發生此事件。 補充幾點 AllowUI 屬性如果設定為false,則顯示對話方塊之類的語句不起作用,如在 VBScript 中MsgBox 語句,JavaScript中的alert等,並且如果執行的指令碼超出TimeOut設定的毫秒數,也不會跳出超出時間提醒的對話方塊,反之則相反;重新設定 Language 屬性會清空AddCode載入的代碼;對於TimeOut屬性,發生逾時時,ScriptControl 檢查對象的 AllowUI 屬性,確定是否允許顯示使用者介面元素。 如果讀者需要更詳細的瞭解,可以查看MSDN文檔。 為了使控制項更容易使用,我用一個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> /// 指令碼運行錯誤代理 /// </summary> public delegate void RunErrorHandler(); /// <summary> /// 指令碼運行逾時代理 /// </summary> public delegate void RunTimeoutHandler(); /// <summary> /// ScriptEngine類 /// </summary> public class ScriptEngine { private ScriptControl msc; //定義指令碼運行錯誤事件 public event RunErrorHandler RunError; //定義指令碼運行逾時事件 public event RunTimeoutHandler RunTimeout; /// <summary> ///建構函式 /// </summary> public ScriptEngine():this(ScriptLanguage.VBscript) { } /// <summary> /// 建構函式 /// </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> /// 運行Eval方法 /// </summary> /// <param name="expression">運算式</param> /// <param name="codeBody">函數體</param> /// <returns>傳回值object</returns> public object Eval(string expression,string codeBody) { msc.AddCode(codeBody); return msc.Eval(expression); } /// <summary> /// 運行Eval方法 /// </summary> /// <param name="language">指令碼語言</param> /// <param name="expression">運算式</param> /// <param name="codeBody">函數體</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> /// 運行Run方法 /// </summary> /// <param name="mainFunctionName">入口函數名稱</param> /// <param name="parameters">參數</param> /// <param name="codeBody">函數體</param> /// <returns>傳回值object</returns> public object Run(string mainFunctionName,object[] parameters,string codeBody) { this.msc.AddCode(codeBody); return msc.Run(mainFunctionName,ref parameters); } /// <summary> /// 運行Run方法 /// </summary> /// <param name="language">指令碼語言</param> /// <param name="mainFunctionName">入口函數名稱</param> /// <param name="parameters">參數</param> /// <param name="codeBody">函數體</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> /// 放棄所有已經添加到 ScriptControl 中的 Script 代碼和對象 /// </summary> public void Reset() { this.msc.Reset(); } /// <summary> /// 擷取或設定指令碼語言 /// </summary> public ScriptLanguage Language { get{return (ScriptLanguage)Enum.Parse(typeof(ScriptLanguage),this.msc.Language,false);} set{this.msc.Language = value.ToString();} } /// <summary> /// 擷取或設定指令碼執行時間,單位為毫秒 /// </summary> public int Timeout { get{return this.msc.Timeout;} set{this.msc.Timeout = value;} } /// <summary> /// 設定是否顯示使用者介面元素 /// </summary> public bool AllowUI { get{return this.msc.AllowUI;} set{this.msc.AllowUI = value;} } /// <summary> /// 宿主應用程式是否有保密性要求 /// </summary> public bool UseSafeSubset { get{return this.msc.UseSafeSubset;} set{this.msc.UseSafeSubset = true;} } /// <summary> /// RunError事件激發 /// </summary> private void OnError() { if(RunError!=null) RunError(); } /// <summary> /// OnTimeout事件激發 /// </summary> private void OnTimeout() { if(RunTimeout!=null) RunTimeout(); } private void ScriptEngine_Error() { OnError(); } private void ScriptEngine_Timeout() { OnTimeout(); } }}上面的封裝定義了一個ScriptLanguage枚舉,這樣操作起來更方便一點。另外指令碼引擎包括了Error事件和Timeout事件,根據實際使用方式可進行註冊。 二.指令碼引擎示範 我建了個表單程式,測試包括指令碼語言的選擇,是否開啟AllowUI屬性,逾時時間的設定,以及指令碼引擎調用方法的選擇。測試程式碼比較長,下面列出了主要部分: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 表單設計器產生的程式碼 private void InitializeComponent() { //省略 } #endregion [STAThread] static void Main() { Application.Run(new Form1()); } //運行指令碼 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)//執行Eval方法 { this.textBoxResult.Text = this.scriptEngine.Eval(this.textBoxMethodName.Text+"("+this.textBoxParams.Text+")",this.textBoxCodeBody.Text).ToString(); } else//執行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(); } //錯誤函數 private void scriptEngine_RunError() { MessageBox.Show("RunError執行指令碼錯誤!"); } private void scriptEngine_RunTimeout() { MessageBox.Show("RunTimeout執行指令碼逾時,引發錯誤!"); } }} 下面是測試程式運行介面: 在文字框中寫了一個JavaScript的函數。輸入12,輸出12000012。 如果把逾時時間調整為1毫秒,那麼執行該指令碼就會跳出下面的逾時提醒框,同時激發事件。 總結,上面示範了JavaScript指令碼,如果有興趣讀者可以寫一些VBsript函數進行測試,指令碼語言也只列出了三種,看了協助,他還支援其他一些指令碼,如果需要可以添加。另外,因為是調用Com,有些傳回值是obejct類型的,需要進行轉換。在CSDN的技術論壇C#板塊下時常有朋友問這方面的問題,對於碰到這類問題的朋友,希望通過這篇文章能獲得一些你需要的協助,很高興能和搞.net的朋友進行交流,我的郵件地址zhzuocn@163.com |