在C#中調用VBScript、JavaScript等腳本的實現

以前在做工作流(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#板塊下時常有朋友問這方面的問題,對於碰到這類問題的朋友,希望通過這篇文章能獲得一些你需要的幫助

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章