What is the best scripting language to embed in a C# desktop application? [closed]
Asked Answered
J

15

100

We are writing a complex rich desktop application and need to offer flexibility in reporting formats so we thought we would just expose our object model to a scripting langauge. Time was when that meant VBA (which is still an option), but the managed code derivative VSTA (I think) seems to have withered on the vine.

What is now the best choice for an embedded scripting language on Windows .NET?

Jaggy answered 26/9, 2008 at 5:40 Comment(1)
FWIW, I started with the approach in @GrantPeter's answer, used an AppDomain to allow unloading, handled the cross-domain object lease renewal, and fiddled with sandbox security measures. The compiled script can call methods in the main program, back across the AppDomain boundary. The experiment can be found here: github.com/fadden/DynamicScriptSandboxHunkydory
A
24

I've used CSScript with amazing results. It really cut down on having to do bindings and other low level stuff in my scriptable apps.

Arjun answered 26/9, 2008 at 20:41 Comment(2)
I've been using CS-Script in production for over a year and it has performed really well.Reina
Btw - to enable C# script support you can either integrate C# script library inside, or make C# script compilation on your own. I have done similar lightweight C# script compiler into my own project in here: sourceforge.net/p/syncproj/code/HEAD/tree/CsScript.cs C# script compilation itself is bit slow (than for example comparing to .Lua), makes sense to avoid extra compilation step if not needed.Metalloid
L
118

Personally, I'd use C# as the scripting language. The .NET framework (and Mono, thanks Matthew Scharley) actually includes the compilers for each of the .NET languages in the framework itself.

Basically, there's 2 parts to the implementation of this system.

  1. Allow the user to compile the code This is relatively easy, and can be done in only a few lines of code (though you might want to add an error dialog, which would probably be a couple dozen more lines of code, depending on how usable you want it to be).

  2. Create and use classes contained within the compiled assembly This is a little more difficult than the previous step (requires a tiny bit of reflection). Basically, you should just treat the compiled assembly as a "plug-in" for the program. There are quite a few tutorials on various ways you can create a plug-in system in C# (Google is your friend).

I've implemented a "quick" application to demonstrate how you can implement this system (includes 2 working scripts!). This is the complete code for the application, just create a new one and paste the code in the "program.cs" file. At this point I must apologize for the large chunk of code I'm about to paste (I didn't intend for it to be so large, but got a little carried away with my commenting)


using System;
using System.Windows.Forms;
using System.Reflection;
using System.CodeDom.Compiler;

namespace ScriptingInterface
{
    public interface IScriptType1
    {
        string RunScript(int value);
    }
}

namespace ScriptingExample
{
    static class Program
    {
        /// 
        /// The main entry point for the application.
        /// 
        [STAThread]
        static void Main()
        {

            // Lets compile some code (I'm lazy, so I'll just hardcode it all, i'm sure you can work out how to read from a file/text box instead
            Assembly compiledScript = CompileCode(
                "namespace SimpleScripts" +
                "{" +
                "    public class MyScriptMul5 : ScriptingInterface.IScriptType1" +
                "    {" +
                "        public string RunScript(int value)" +
                "        {" +
                "            return this.ToString() + \" just ran! Result: \" + (value*5).ToString();" +
                "        }" +
                "    }" +
                "    public class MyScriptNegate : ScriptingInterface.IScriptType1" +
                "    {" +
                "        public string RunScript(int value)" +
                "        {" +
                "            return this.ToString() + \" just ran! Result: \" + (-value).ToString();" +
                "        }" +
                "    }" +
                "}");

            if (compiledScript != null)
            {
                RunScript(compiledScript);
            }
        }

        static Assembly CompileCode(string code)
        {
            // Create a code provider
            // This class implements the 'CodeDomProvider' class as its base. All of the current .Net languages (at least Microsoft ones)
            // come with thier own implemtation, thus you can allow the user to use the language of thier choice (though i recommend that
            // you don't allow the use of c++, which is too volatile for scripting use - memory leaks anyone?)
            Microsoft.CSharp.CSharpCodeProvider csProvider = new Microsoft.CSharp.CSharpCodeProvider();

            // Setup our options
            CompilerParameters options = new CompilerParameters();
            options.GenerateExecutable = false; // we want a Dll (or "Class Library" as its called in .Net)
            options.GenerateInMemory = true; // Saves us from deleting the Dll when we are done with it, though you could set this to false and save start-up time by next time by not having to re-compile
            // And set any others you want, there a quite a few, take some time to look through them all and decide which fit your application best!

            // Add any references you want the users to be able to access, be warned that giving them access to some classes can allow
            // harmful code to be written and executed. I recommend that you write your own Class library that is the only reference it allows
            // thus they can only do the things you want them to.
            // (though things like "System.Xml.dll" can be useful, just need to provide a way users can read a file to pass in to it)
            // Just to avoid bloatin this example to much, we will just add THIS program to its references, that way we don't need another
            // project to store the interfaces that both this class and the other uses. Just remember, this will expose ALL public classes to
            // the "script"
            options.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);

            // Compile our code
            CompilerResults result;
            result = csProvider.CompileAssemblyFromSource(options, code);

            if (result.Errors.HasErrors)
            {
                // TODO: report back to the user that the script has errored
                return null;
            }

            if (result.Errors.HasWarnings)
            {
                // TODO: tell the user about the warnings, might want to prompt them if they want to continue
                // runnning the "script"
            }

            return result.CompiledAssembly;
        }

        static void RunScript(Assembly script)
        {
            // Now that we have a compiled script, lets run them
            foreach (Type type in script.GetExportedTypes())
            {
                foreach (Type iface in type.GetInterfaces())
                {
                    if (iface == typeof(ScriptingInterface.IScriptType1))
                    {
                        // yay, we found a script interface, lets create it and run it!

                        // Get the constructor for the current type
                        // you can also specify what creation parameter types you want to pass to it,
                        // so you could possibly pass in data it might need, or a class that it can use to query the host application
                        ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes);
                        if (constructor != null && constructor.IsPublic)
                        {
                            // lets be friendly and only do things legitimitely by only using valid constructors

                            // we specified that we wanted a constructor that doesn't take parameters, so don't pass parameters
                            ScriptingInterface.IScriptType1 scriptObject = constructor.Invoke(null) as ScriptingInterface.IScriptType1;
                            if (scriptObject != null)
                            {
                                //Lets run our script and display its results
                                MessageBox.Show(scriptObject.RunScript(50));
                            }
                            else
                            {
                                // hmmm, for some reason it didn't create the object
                                // this shouldn't happen, as we have been doing checks all along, but we should
                                // inform the user something bad has happened, and possibly request them to send
                                // you the script so you can debug this problem
                            }
                        }
                        else
                        {
                            // and even more friendly and explain that there was no valid constructor
                            // found and thats why this script object wasn't run
                        }
                    }
                }
            }
        }
    }
}

Laocoon answered 27/2, 2009 at 18:52 Comment(12)
Do you know if this will work in Mono too, or is it only available on .NET?Backhouse
i don't know, never used MonoLaocoon
FYI, and for anyone else that's curious, yes, this does compile and run on Mono just fine. Only need the System reference for the compilation parts.Backhouse
Doing this pollutes the AppDomainClaytonclaytonia
@Lavinski If you don't want it to pollute your AppDomain, then simply make a new one (which is probably a good idea anyway so you can place more stringent security on the "scripts")Laocoon
As assembly can not be unloaded, this approach results in process memory usage increasing every time script is compiledBanneret
@Banneret - The assembly can be unloaded if you load it into a separate AppDomain (which again seems like a good idea as it will allow you to have more stringent control over what the script can do).Laocoon
What exactly is the difference between this, and csscript.net ?Messinger
@Lander - This is extremely lightweight. The above code is an entire program that supports "scripting". csscript.net looks like it's some sort of wrapper over this. This is basically a bare-bones implementation. I think a better question would be "what does csscript.net do for me that this doesn't". I don't know what csscript is doing, but they definitely know what the above code here does, they have it (or something extremely similar) in their library.Laocoon
@GrantPeters Excellent example. Is the ScriptingInterface namespace just to keep scope clean within the script? I imagine you are recommending a separate assy (DLL) to prevent the script from simply loading the main namespace as they are the same exposed assy. Is it possible to restrict the "script" to only loading a specific namespace of an assy? This would prevent having another project to juggle. Or what about using the internal and protected internal keywords to limit access from scripts?Bayern
@Bayern Yeah, the namespace was simply there to keep the code clean and help provide clarity for people reading it. AFAIK, there's no way to really restrict what namespaces they have access to, only what assemblies they have access to. You could always make everything but what you want exposed private/internal (though they could still get at it with reflection then). If you want a fully secure environment, you'll have to severely restrict what assemblies they use and put them in their own AppDomain (probably a separate one per script so scripts can't mess around with other scripts).Laocoon
This answer does not apply to .net standard or .net core at this time.Gabfest
I
37

IronPython. Here's a guide on how to embed it.

Interpellate answered 26/9, 2008 at 5:41 Comment(0)
A
24

I've used CSScript with amazing results. It really cut down on having to do bindings and other low level stuff in my scriptable apps.

Arjun answered 26/9, 2008 at 20:41 Comment(2)
I've been using CS-Script in production for over a year and it has performed really well.Reina
Btw - to enable C# script support you can either integrate C# script library inside, or make C# script compilation on your own. I have done similar lightweight C# script compiler into my own project in here: sourceforge.net/p/syncproj/code/HEAD/tree/CsScript.cs C# script compilation itself is bit slow (than for example comparing to .Lua), makes sense to avoid extra compilation step if not needed.Metalloid
C
20

The PowerShell engine was designed to be easily embedded in an application to make it scriptable. In fact, the PowerShell CLI is just a text based interface to the engine.

Edit: See https://devblogs.microsoft.com/powershell/making-applications-scriptable-via-powershell/

Chaparro answered 26/9, 2008 at 5:52 Comment(1)
I think this is the best solution because it doesn't add objects to the AppDomain. In .Net you can never unload assemblies or classes.Claytonclaytonia
D
12

Boo language.

Demisec answered 26/9, 2008 at 5:53 Comment(2)
Looks dead if it's the project at github.com/boo-lang/booImmesh
@Immesh as of now, there are a few commits every month on the project (until as recently as a month ago). So maybe it has lost a bit of traction, but it still seems to be alive.Retrochoir
O
8

My scripting language of choice would be Lua these days. It's small, fast, clean, fully documented, well supported, has a great community , it's used by many big companies in the industry (Adobe, Blizzard, EA Games), definetely worth a try.

To use it with .NET languages the LuaInterface project will provide all you need.

Outcast answered 26/9, 2008 at 7:59 Comment(1)
Lua is also used for scripting in Garry's Mod, which is a great game :)Libb
N
3

Why not try C#? Mono has a great new project especially for dynamically evaluating C# :

http://tirania.org/blog/archive/2008/Sep-10.html

Nuts answered 26/9, 2008 at 8:10 Comment(0)
S
2

IronRuby as mentioned above. An interesting project to me as a C# programmer is C# Eval support in Mono. But it's not available yet (will be part of Mono 2.2).

Scharff answered 26/9, 2008 at 5:52 Comment(0)
S
2

Another vote for IronPython. Embedding it is simple, interoperation with .Net classes is straightforward, and, well, it's Python.

Sailor answered 26/9, 2008 at 20:37 Comment(0)
D
1

I may suggest S# which I currently maintain. It is an open source project, written in C# and designed for .NET applications.

Initially (2007-2009) it was hosted at http://www.codeplex.com/scriptdotnet, but recently it was moved to github.

Distribute answered 30/10, 2012 at 15:4 Comment(2)
Thanks for posting your answer! Please be sure to read the FAQ on Self-Promotion carefully. Also note that it is required that you post a disclaimer every time you link to your own site/product.Drislane
Thanks for advice Andrew. One of the previous answers contains an outdated link to this scripting language. For some reason I can't add comment to the original answer, thus I've posted a new one to provide correct link.Distribute
A
1

Try Ela. This is a functional language similar to Haskell and can be embedded into any .Net application. Even it has simple but usable IDE.

Adnah answered 23/11, 2013 at 13:4 Comment(0)
A
0

I havnt tried this yet but it looks pretty cool:

http://www.codeplex.com/scriptdotnet

Ambidexter answered 26/9, 2008 at 6:3 Comment(0)
M
0

I've just created a plugin for a client, allowing them to write C# code in modules that act like VBA does for Office.

Micky answered 26/9, 2008 at 6:6 Comment(0)
B
0

I've used Lua before; in a Delphi app but it can be embedded in lots of things. It's used in Adobe's Photoshop Lightroom.

Bettor answered 5/4, 2010 at 10:49 Comment(0)
C
0

I like scripting with C# itself. Now, in 2013, there's quite good support for C# scripting, more and more libraries for it are becoming available.

Mono has a great support to script C# code, and you can use it with .NET by just including the Mono.CSharp.dll in your application. For C# scripting application that I've made check out CShell

Also check out the `ScriptEngine' in Roslyn which is from Microsoft, but this is only CTP.

As some people already mentioned, CS-Script has been around for quite a while as well.

Culinarian answered 6/6, 2013 at 18:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.