首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 發現之旅第 14 講:基於動態編譯的 VB.NET 腳本引擎核心實現 (上) — 原理、架構與動態編譯管線

     編輯:關於C#

本講從 **腳本技術本質** 出發,對比傳統編程與腳本差異,闡述 **為何選 VB.NET 做腳本語言**,並完整實現一個基於 **動態編譯** 的輕量級 VB.NET 腳本引擎核心 (XVBAEngine),涵蓋 **代碼生成器、編譯參數控制、快取機制、反射調用、全域物件語法支援** 等核心模組。

腳本 vs 傳統程式:核心差異

維度傳統程式腳本
運行形態預編譯獨立 EXE/DLL宿主容器內即時編譯/解釋執行
部署修改需重編譯、重部署、重啟宿主純文字修改即時生效,宿主無需重啟
語言特性靜態類型 (C#、C++)動態類型/晚期綁定 (VB.NET、JS、Lua)
權限完整信任受宿主沙箱限制
用途核心業務邏輯業務規則自訂、工作流、報表模板、自動化腳本

結論:腳本需 **動態語言特性** (晚期綁定、不嚴格型別檢查),VB.NET 繼承自 VB6 保留此特性,C# 則為靜態語言,**VB.NET 更適合作腳本語言**。

動態編譯實現腳本引擎原理

  1. 宿主收集使用者腳本文字 (VB.NET 語法)。
  2. 引擎 **預處理**:包裝成完整 VB.NET 模組代碼 (加 Option Strict Off、Imports、Namespace/Module 包裝)。
  3. 呼叫 Microsoft.VisualBasic.VBCodeProvider.CompileAssemblyFromSource **動態編譯** 產生記憶體組件。
  4. 用 **反射** 掃描模組內所有 Public Shared Sub (靜態方法) 建立方法索引表。
  5. 宿主依名稱查表,透過 MethodInfo.Invoke 或 **委託快取** 執行腳本方法。
  6. 腳本修改 → 重複 2-5 步驟 (增量編譯/快取優化)。

VB.NET 語法特性支撐腳本引擎設計

1. Module 包裝 → 靜態方法/欄位自動化

Module mdlXVBAScriptEngine
    Sub MyScript() ... End Sub     ' 編譯為 public static void MyScript()
    Dim GlobalVar As Integer       ' 編譯為 public static int GlobalVar
End Module

引擎自動在使用者代碼外層包裝 Module ... End Module,使用者只寫 Sub Name()/Dim Var 即可得到靜態成員,無需手寫 Public Shared。

2. StandardModuleAttribute → 全域物件語法

標記類別 [StandardModuleAttribute] class GlobalObject { public static Window Window {get;} },VB 編譯器會將腳本中的 Window.Alert("hi") 自動擴展為 GlobalObject.Window.Alert("hi")。**編譯期完成,運行期零開銷**。C# 無此特性。

3. 編譯器命令列 /imports → 全域函數/命名空間開啟

vbc.exe /imports:MyVBAScript.Global,System,Microsoft.VisualBasic ... 讓編譯器識別附加 StandardModuleAttribute 類別的靜態成員為全域可用。源碼 Imports 僅簡化寫法,/imports 才啟用全域語義。

XVBAEngine 核心架構

public class XVBAEngine
{
    // 參數控制屬性
    public string ScriptText { get; set; }           // 原始腳本
    public StringCollection SourceImports { get; }   // 代碼內 Imports
    public StringCollection VBCompilerImports { get; } // 編譯器 /imports
    public StringCollection ReferencedAssemblies { get; } // 參考組件
    public bool OutputDebug { get; set; } = true;

    // 內部狀態
    private bool _scriptModified = true;
    private Assembly _compiledAssembly;
    private readonly List _methods = new();
    private static readonly Hashtable _assemblyCache = new(); // 代碼文本->組件

    // 核心流程
    public bool Compile() { /* 生成代碼 -> 動態編譯 -> 反射掃描 -> 快取 */ }
    public object Execute(string methodName, object[] args, bool throwOnError) { /* 查表 -> Invoke */ }
}

代碼生成器:GenerateRuntimeSource()

private string GenerateRuntimeSource()
{
    var sb = new StringBuilder();
    sb.AppendLine("Option Strict Off");
    foreach (string ns in SourceImports) sb.AppendLine($"Imports {ns}");
    sb.AppendLine("Namespace NameSpaceXVBAScriptEngine");
    sb.AppendLine("Module mdlXVBAScriptEngine");
    sb.AppendLine(); // 用戶代碼插入點
    sb.AppendLine(ScriptText);
    sb.AppendLine();
    sb.AppendLine("End Module");
    sb.AppendLine("End Namespace");
    return sb.ToString();
}
  • 預設 SourceImports 含 System, Microsoft.VisualBasic。
  • 用戶可加 System.Drawing 等減少腳本前綴。

動態編譯管線:BuildAssembly()


private bool BuildAssembly(string sourceText, string outputFile = null)
{
    // 1. 檢查快取
    if (_assemblyCache[sourceText] is Assembly cached)
    { _compiledAssembly = cached; return true; }

    // 2. 編譯參數
    var cp = new CompilerParameters
    {
        GenerateExecutable = false,
        GenerateInMemory = true,
        IncludeDebugInformation = true,
        ReferencedAssemblies = { "mscorlib.dll", "System.dll", "System.Data.dll", 
                                 "System.Xml.dll", "System.Drawing.dll", 
                                 "System.Windows.Forms.dll", "Microsoft.VisualBasic.dll" }
    };
    // 加入用戶參考組件
    foreach (string asm in ReferencedAssemblies) cp.ReferencedAssemblies.Add(asm);

    // 3. /imports 指令
    if (VBCompilerImports.Count > 0)
    {
        var opt = new StringBuilder("/imports:");
        for (int i = 0; i < VBCompilerImports.Count; i++)
        {
            if (i > 0) opt.Append(',');
            opt.Append(VBCompilerImports[i].Trim());
        }
        cp.CompilerOptions = opt.ToString();
    }

    // 4. 編譯
    var provider = new VBCodeProvider();
    CompilerResults result;
    #if NET20_OR_GREATER
    result = provider.CompileAssemblyFromSource(cp, sourceText); // .NET 2.0+
    #else
    result = provider.CreateCompiler().CompileAssemblyFromSource(cp, sourceText); // .NET 1.1
    #endif

    // 5. 結果處理
    _compilerOutput = string.Join(Environment.NewLine, result.Output.Cast());
    if (result.Errors.HasErrors) return false;

    _compiledAssembly = result.CompiledAssembly;
    _assemblyCache[sourceText] = _compiledAssembly; // 快取
    if (outputFile != null) File.WriteAllText(outputFile, sourceText, Encoding.GetEncoding(936));
    return true;
}

反射掃描與方法索引表


private void ScanMethods()
{
    _methods.Clear();
    var moduleType = _compiledAssembly.GetType("NameSpaceXVBAScriptEngine.mdlXVBAScriptEngine");
    if (moduleType == null) return;

    foreach (var mi in moduleType.GetMethods(
        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
    {
        if (mi.IsSpecialName) continue; // 略過 property accessor 等
        _methods.Add(new ScriptMethodInfo
        {
            MethodName = mi.Name,
            MethodInfo = mi,
            ReturnType = mi.ReturnType,
            Delegate = Delegate.CreateDelegate(
                Expression.GetFuncType(mi.GetParameters().Select(p=>p.ParameterType).Append(mi.ReturnType).ToArray()),
                mi) // 建立強型別委託加速後續調用
        });
    }
}

委託快取:MethodInfo.Invoke 較慢,首次掃描建立 Delegate (對應簽名的 Func<...>/Action<...>),後續調用 delegate.DynamicInvoke(args) 顯著提速。

執行入口:Execute()


public object Execute(string name, object[] args, bool throwEx)
{
    if (!_scriptModified && _methods.Count == 0) return null; // 未編譯
    if (_scriptModified) { if (!Compile()) return null; }

    var mi = _methods.FirstOrDefault(m => m.MethodName.Equals(name, StringComparison.OrdinalIgnoreCase));
    if (mi == null) { if (throwEx) throw new MissingMethodException(name); return null; }

    try { return mi.Delegate?.DynamicInvoke(args) ?? mi.MethodInfo.Invoke(null, args); }
    catch (TargetInvocationException tie) when (tie.InnerException != null)
    { if (throwEx) throw tie.InnerException; return null; }
    catch { if (throwEx) throw; return null; }
}

Window 全域物件核心功能 (XVBAWindowObject)

  • UI 互動:Alert/AlertError/Confirm/Prompt/BrowseFile/BrowseFolder — 受 UserInteractive 控制,Service/ASP.NET 環境設 false 自動禁用。
  • 視窗映射:Left/Top/Width/Height/Title 映射 ParentWindow (IWin32Window)。
  • 螢幕資訊:ScreenWidth/Height。
  • 計時器:SetTimeout(ms, method) (一次)、SetInterval(ms, method) (週期) — 基於 System.Windows.Forms.Timer,UI 執行緒安全。
  • 引擎回參:Engine 屬性暴露 XVBAEngine,腳本可存取 Window.Engine.ScriptText 等。

小結

上集完整構建了腳本引擎核心:

  1. 利用 **VB.NET Module + StandardModuleAttribute + /imports** 三大語法特性,實現 **自然的全域物件/函數語法**。
  2. **動態編譯管線**:代碼生成 → 參數配置 → VBCodeProvider 編譯 → 反射掃描 → 委託快取 → 記憶體組件快取。
  3. XVBAEngine 封裝編譯/執行/快取/參考管理,XVBAWindowObject 提供豐富宿主互動 API。
  4. 下集將示範 **WinForm 主程式整合、腳本編輯器 UI、demo.vbs 示範庫、部署策略**。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved