Как я могу настроить приложение на С#?

У меня есть настольное приложение, написанное на С#, которое я хотел бы сделать scriptable на С#/VB. В идеале пользователь откроет боковую панель и напишет такие вещи, как

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

Выделение синтаксиса и завершение кода было бы удивительным, но я мог бы жить без него. Я бы не хотел, чтобы пользователи установили Visual Studio 2010.

Я думаю о вызове компилятора, загрузке и запуске сборки вывода.

Есть ли лучший способ?

Microsoft.CSharp ответ?

Ответ 1

Задумывались ли вы о IronPython или IronRuby?

Ответ 2

Используйте язык сценариев. Tcl, LUA или даже 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;
}

Ответ 3

Вы все равно вызовете компилятор, потому что С# - это скомпилированный язык. Лучший способ сделать это можно проверить в CSharpCodeProvider - класс.

Ответ 4

Я бы использовал PowerShell или MEF. Это действительно зависит от того, что вы подразумеваете под начинкой и какого типа приложения у вас есть. Самая лучшая часть в PowerShell - это прямое перемещение и прямое использование интерфейсов .NET в сценарии.

Ответ 6

У меня была такая же проблема и с небольшим количеством поисковых запросов и несколькими изменениями я решил ее с помощью Microsoft.CSharp.CSharpCodeProvider, который позволяет пользователю редактировать шаблон С#, который я им представляю, который предоставляет полную объектную модель моего приложения и они могут даже передавать параметры из/и возвращать результат самому приложению.

Полное решение С# можно загрузить с 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;
    }
}

Затем я определил интерфейс С#, который будет реализован с помощью кода пользователя, где они могут свободно помещать любое из них в свой конкретный метод 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.

Удачи!

Ответ 7

На каком языке написано ваше приложение? Если С++, вы можете рассмотреть Google V8, вложенный механизм ECMAScript/JavaScript.