如何在C#中创建应用程序脚本?

时间:2010-03-02 23:59:17

标签: c# .net scriptable

我有一个用C#编写的桌面应用程序我想在C#/ VB上编写脚本。 理想情况下,用户可以打开侧窗格并编写

之类的内容
foreach (var item in myApplication.Items)
   item.DoSomething();

语法高亮和代码完成会很棒,但我可以没有它。 我希望要求用户安装Visual Studio 2010

我正在考虑调用编译器,加载并运行输出程序集。

有更好的方法吗?

答案是Microsoft.CSharp吗?

7 个答案:

答案 0 :(得分:4)

您是否考虑过IronPython或IronRuby?

答案 1 :(得分:1)

使用脚本语言。想到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;
}

答案 2 :(得分:1)

无论如何,您将调用编译器,因为C#是一种编译语言。可以在 CSharpCodeProvider - класс 中查看最佳方法。

答案 3 :(得分:1)

我会使用PowerShell或MEF。这实际上取决于你的scritable是什么意思,你有什么类型的应用程序。关于PowerShell的最好的部分是它可以直接托管并直接设计为以脚本方式使用.NET接口。

答案 4 :(得分:1)

您可以使用以下开源解决方案作为示例:https://bitbucket.org/jlyonsmith/coderunner/wiki/Home

答案 5 :(得分:1)

我遇到了完全相同的问题,并且使用了一些谷歌搜索和一些修改,我使用Microsoft.CSharp.CSharpCodeProvider解决了它,它允许用户编辑我呈现给他们的C#模板,公开我的应用程序的完整对象模型和他们甚至可以从/传递参数并将结果返回给应用程序本身。

可以从http://qurancode.com下载完整的C#解决方案。 但是这里的主要代码就是:

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。

祝你好运!

答案 6 :(得分:0)

您的申请书是用哪种语言撰写的?如果是C ++,您可以考虑Google V8,一个可嵌入的ECMAScript / JavaScript引擎。