簡體   English   中英

如何在C#中創建應用程序腳本?

[英]How can I make my application scriptable in C#?

我有一個用C#編寫的桌面應用程序我想在C#/ VB上編寫腳本。 理想情況下,用戶可以打開側窗格並編寫類似的內容

foreach (var item in myApplication.Items)
   item.DoSomething();

語法高亮和代碼完成會很棒,但我可以沒有它。 不想要求用戶有Visual Studio 2010的安裝。

我正在考慮調用編譯器,加載和運行輸出程序集。

有沒有更好的辦法?

Microsoft.CSharp是答案嗎?

你有沒有想過IronPython或IronRuby?

我有完全相同的問題,並使用一些谷歌搜索和一些修改,我使用Microsoft.CSharp.CSharpCodeProvider解決它,它允許用戶編輯我提供給他們的C#模板,公開我的應用程序的完整對象模型,他們甚至可以從/傳遞參數並將結果返回給應用程序本身。

完整的C#解決方案可以從http://qurancode.com下載。 但是這里的主要代碼就是:

using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Security;
using Model; // this is my application Model with my own classes


public static class ScriptRunner
{
    private static string s_scripts_directory = "Scripts";
    static ScriptRunner()
    {
        if (!Directory.Exists(s_scripts_directory))
        {
            Directory.CreateDirectory(s_scripts_directory);
        }
    }

    /// <summary>
    /// Load a C# script fie
    /// </summary>
    /// <param name="filename">file to load</param>
    /// <returns>file content</returns>
    public static string LoadScript(string filename)
    {
        StringBuilder str = new StringBuilder();
        string path = s_scripts_directory + "/" + filename;
        if (File.Exists(filename))
        {
            using (StreamReader reader = File.OpenText(path))
            {
                string line = "";
                while ((line = reader.ReadLine()) != null)
                {
                    str.AppendLine(line);
                }
            }
        }
        return str.ToString();
    }

    /// <summary>
    /// Compiles the source_code 
    /// </summary>
    /// <param name="source_code">source_code must implements IScript interface</param>
    /// <returns>compiled Assembly</returns>
    public static CompilerResults CompileCode(string source_code)
    {
        CSharpCodeProvider provider = new CSharpCodeProvider();

        CompilerParameters options = new CompilerParameters();
        options.GenerateExecutable = false;  // generate a Class Library assembly
        options.GenerateInMemory = true;     // so we don;t have to delete it from disk

        Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
        foreach (Assembly assembly in assemblies)
        {
            options.ReferencedAssemblies.Add(assembly.Location);
        }

        return provider.CompileAssemblyFromSource(options, source_code);
    }

    /// <summary>
    /// Execute the IScriptRunner.Run method in the compiled_assembly
    /// </summary>
    /// <param name="compiled_assembly">compiled assembly</param>
    /// <param name="args">method arguments</param>
    /// <returns>object returned</returns>
    public static object Run(Assembly compiled_assembly, object[] args, PermissionSet permission_set)
    {
        if (compiled_assembly != null)
        {
            // security is not implemented yet !NIY
            // using Utilties.PrivateStorage was can save but not diaplay in Notepad
            // plus the output is saved in C:\Users\<user>\AppData\Local\IsolatedStorage\...
            // no contral over where to save make QuranCode unportable applicaton, which is a no no
            //// restrict code security
            //permission_set.PermitOnly();

            foreach (Type type in compiled_assembly.GetExportedTypes())
            {
                foreach (Type interface_type in type.GetInterfaces())
                {
                    if (interface_type == typeof(IScriptRunner))
                    {
                        ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes);
                        if ((constructor != null) && (constructor.IsPublic))
                        {
                            // construct object using default constructor
                            IScriptRunner obj = constructor.Invoke(null) as IScriptRunner;
                            if (obj != null)
                            {
                                return obj.Run(args);
                            }
                            else
                            {
                                throw new Exception("Invalid C# code!");
                            }
                        }
                        else
                        {
                            throw new Exception("No default constructor was found!");
                        }
                    }
                    else
                    {
                        throw new Exception("IScriptRunner is not implemented!");
                    }
                }
            }

            // revert security restrictions
            //CodeAccessPermission.RevertPermitOnly();
        }
        return null;
    }

    /// <summary>
    /// Execute a public static method_name(args) in compiled_assembly
    /// </summary>
    /// <param name="compiled_assembly">compiled assembly</param>
    /// <param name="methode_name">method to execute</param>
    /// <param name="args">method arguments</param>
    /// <returns>method execution result</returns>
    public static object ExecuteStaticMethod(Assembly compiled_assembly, string methode_name, object[] args)
    {
        if (compiled_assembly != null)
        {
            foreach (Type type in compiled_assembly.GetTypes())
            {
                foreach (MethodInfo method in type.GetMethods())
                {
                    if (method.Name == methode_name)
                    {
                        if ((method != null) && (method.IsPublic) && (method.IsStatic))
                        {
                            return method.Invoke(null, args);
                        }
                        else
                        {
                            throw new Exception("Cannot invoke method :" + methode_name);
                        }
                    }
                }
            }
        }
        return null;
    }

    /// <summary>
    /// Execute a public method_name(args) in compiled_assembly
    /// </summary>
    /// <param name="compiled_assembly">compiled assembly</param>
    /// <param name="methode_name">method to execute</param>
    /// <param name="args">method arguments</param>
    /// <returns>method execution result</returns>
    public static object ExecuteInstanceMethod(Assembly compiled_assembly, string methode_name, object[] args)
    {
        if (compiled_assembly != null)
        {
            foreach (Type type in compiled_assembly.GetTypes())
            {
                foreach (MethodInfo method in type.GetMethods())
                {
                    if (method.Name == methode_name)
                    {
                        if ((method != null) && (method.IsPublic))
                        {
                            object obj = Activator.CreateInstance(type, null);
                            return method.Invoke(obj, args);
                        }
                        else
                        {
                            throw new Exception("Cannot invoke method :" + methode_name);
                        }
                    }
                }
            }
        }
        return null;
    }
}

然后我定義了一個C#接口,由用戶代碼實現,他們可以在其具體的Run方法中隨意放置任何他們喜歡的東西:

/// <summary>
/// Generic method runner takes any number and type of args and return any type
/// </summary>
public interface IScriptRunner
{
    object Run(object[] args);
}

這是用戶可以擴展的啟動模板:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Text;
using System.IO;
using Model;

public class MyScript : IScriptRunner
{
    private string m_scripts_directory = "Scripts";

    /// <summary>
    /// Run implements IScriptRunner interface
    /// to be invoked by QuranCode application
    /// with Client, current Selection.Verses, and extra data
    /// </summary>
    /// <param name="args">any number and type of arguments</param>
    /// <returns>return any type</returns>
    public object Run(object[] args)
    {
        try
        {
            if (args.Length == 3)   // ScriptMethod(Client, List<Verse>, string)
            {
                Client client = args[0] as Client;
                List<Verse> verses = args[1] as List<Verse>;
                string extra = args[2].ToString();
                if ((client != null) && (verses != null))
                {
                    return MyMethod(client, verses, extra);
                }
            }
            return null;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, Application.ProductName);
            return null;
        }
    }

    /// <summary>
    /// Write your C# script insde this method.
    /// Don't change its name or parameters
    /// </summary>
    /// <param name="client">Client object holding a reference to the currently selected Book object in TextMode (eg Simplified29)</param>
    /// <param name="verses">Verses of the currently selected Chapter/Page/Station/Part/Group/Quarter/Bowing part of the Book</param>
    /// <param name="extra">any user parameter in the TextBox next to the EXE button (ex Frequency, LettersToJump, DigitSum target, etc)</param>
    /// <returns>true to disply back in QuranCode matching verses. false to keep script window open</returns>
    private long MyMethod(Client client, List<Verse> verses, string extra)
    {
        if (client == null) return false;
        if (verses == null) return false;
        if (verses.Count == 0) return false;

        int target;
        if (extra == "")
        {
            target = 0;
        }
        else
        {
            if (!int.TryParse(extra, out target))
            {
                return false;
            }
        }

        try
        {
            long total_value = 0L;
            foreach (Verse verse in verses)
            {
                total_value += Client.CalculateValue(verse.Text);
            }
            return total_value;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, Application.ProductName);
            return 0L;
        }
    }
}

這就是我從MainForm.cs中調用它的方式

#region Usage from MainForm
if (!ScriptTextBox.Visible)
{
    ScriptTextBox.Text = ScriptRunner.LoadScript(@"Scripts\Template.cs");
    ScriptTextBox.Visible = true;
}
else // if visible
{
    string source_code = ScriptTextBox.Text;
    if (source_code.Length > 0)
    {
        Assembly compiled_assembly = ScriptRunner.CompileCode(source_code);
        if (compiled_assembly != null)
        {
            object[] args = new object[] { m_client, m_client.Selection.Verses, "19" };
            object result = ScriptRunner.Run(compiled_assembly, args);
            // process result here
        }
    }
    ScriptTextBox.Visible = false;
}
#endregion

仍然要做的是語法高亮和CodeCompletion。

祝好運!

使用腳本語言。 想到TclLUA甚至是JavaScript。

使用Tcl非常簡單:

using System.Runtime.InteropServices;
using System;

namespace TclWrap {
    public class TclAPI {
         [DllImport("tcl84.DLL")]
         public static extern IntPtr Tcl_CreateInterp();
         [DllImport("tcl84.Dll")]
         public static extern int Tcl_Eval(IntPtr interp,string skript);
         [DllImport("tcl84.Dll")]
         public static extern IntPtr Tcl_GetObjResult(IntPtr interp);
         [DllImport("tcl84.Dll")]
         public static extern string Tcl_GetStringFromObj(IntPtr tclObj,IntPtr length);
    }
    public class TclInterpreter {
        private IntPtr interp;
        public TclInterpreter() {
            interp = TclAPI.Tcl_CreateInterp();
            if (interp == IntPtr.Zero) {
                throw new SystemException("can not initialize Tcl interpreter");
            }
        }
        public int evalScript(string script) {
            return TclAPI.Tcl_Eval(interp,script);        
        }
        public string Result {
            get { 
                IntPtr obj = TclAPI.Tcl_GetObjResult(interp);
                if (obj == IntPtr.Zero) {
                    return "";
                } else {
                    return TclAPI.Tcl_GetStringFromObj(obj,IntPtr.Zero);
                }
            }
        }
    }
}

然后使用它像:

TclInterpreter interp = new TclInterpreter();
string result;
if (interp.evalScript("set a 3; {exp $a + 2}")) {
    result = interp.Result;
}

無論如何,您將調用編譯器,因為C#是一種編譯語言。 可以在CSharpCodeProvider - класс中檢查最佳方法。

我會使用PowerShell或MEF 這實際上取決於你的scritable是什么意思,你有什么類型的應用程序。 關於PowerShell的最好的部分是它可以直接托管並直接設計為以腳本方式使用.NET接口。

您可以使用以下開源解決方案作為示例: https//bitbucket.org/jlyonsmith/coderunner/wiki/Home

您的應用程序使用什么語言編寫? 如果是C ++,您可以考慮使用嵌入式ECMAScript / JavaScript引擎Google V8

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM