Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Scripting Engine for .NET

0.00/5 (No votes)
23 Mar 2008 1  
Use scripts in services or applications with longer runtimes.

Introduction

The .NET Framework has great features to support compiling code at runtime and running it, to achieve some kinds of scripting functionalities. There are already some scripting engines available here on CodeProject, and Microsoft has its own library as well.

Unfortunately, none of the projects I've found on the net satisfied my requirements. My plan was to make a flexible service that could run some pre-defined tasks that should be changeable without having a compiler. As I said, the scripting libraries I found could compile and run the scripts, but every single one had at least one of the following disadvantages:

  • Support for VB.NET only (no C#)
  • They blow up memory
  • They have to be configured by the application that runs the script
  • They don't support scripts with more than one file

This article is the result of my decision to write my own library :)

Background

The main problem of runtime-compiling/running is that any assembly that is loaded needs memory, and once loaded, an assembly can not be unloaded again.

To solve this problem, I use a separate AppDomain to compile/run the scripts. The dotnetScriptor class unloads and reloads that AppDomain if required, blocks the script executions while the reload is in progress, and the ScriptRunner class holds the compiled assemblies, rebuilds them if required, and informs dotnetScriptor whether a restart is required or not.

The usage of Remoting and the restart capabilities brings new problems:

  • MarshalByRefObject proxies have a limited life-time.
  • If a MarshalByRefObject member-function takes a serialized parameter, eventual changes will not apply.

The life-time problem is solved with the ScriptSponsor class. If a MarshalByRefObject derived object is passed that leaves the Appdomain that holds the original object, dotnetScriptor or ScriptRunner will put a ScriptSponsor on it.

The serializable problem is solved by the MarshalSerializer class. I know this approach could solve the life-time problem as well, but sometimes, a script needs to interact with the main application.

The MarshalSerializer must be initialized by the host-application and will be explained later, the life-time sponsorship works automatically.

Using the code

The usage of dotnetScriptor in an application is quite easy. Take a look at this little class here:

public class ScriptorClient
{
 private dotnetScriptor scriptor;
 public ScriptorClient()
 {
  ArrayList list = new ArrayList(new string[]{"Text1","Text2","Text3"});
  // maximum Reboot frequency 10 Minutes
  dotnetScriptor.RebootTimeout = TimeSpan.FromMinutes(10);
  scriptor = new dotnetScriptor(); // initialize Scriptor
  scriptor.PopulateObject("Parameters",list); // share Parameters with Script
  scriptor.PopulateObject("MyNonProxyObject", 
                          new MarshalSerializer(typeof(SomeMarshalByRefObjectClass),
                          "Param1","Param2",3,4L));
 }
 public void RunMyScript()
 {
  scriptor.RunScript(@"C:\MyFooScript.cs"); // run a script
 }
}

If the script does not change, it will not be re-compiled. Only if you change the script, will ScriptRunner recompile it. If you keep changing the script all the time, dotnetScriptor will restart in a 10-minute frequency, but the changes are applied instantly. Memory will blow up within these 10 minutes, and go down after reboot.

The additional populated object ("MyNonProxyObject") is a wrapper object that will be converted into an object of SomeMarshalByRefObjectClass by using the appropriate constructor. This won't be used in most of the cases. But, it's useful if you have a MarshalByRef object with a member function that takes a serializable object as a parameter and changes something in it (i.e., it takes an ArrayList and adds an item or something...).

How to script

The scripts has two parts:

  1. configuration
  2. script

The configuration part is an XML that is terminated by a '!' followed by 70 '-'s like this:

/* <?xml version=&quot;1.0&quot; standalone=&quot;yes&quot;?>
* <Script>
* <EntryPoint>Test.TestScript.RunMe</EntryPoint>
* <Reference>System.Dll</Reference>
* <Reference>C:\Development\c#\dotnetScriptor\bin\debug\dotnetScriptor.dll</Reference>
* <Language>csharp</Language>
* </Script>
*/

//!-----------------------------------------------------------------------------------------

There are several options in the XML:

  • EntryPoint is the fully qualified (Namespace.Classname.FunctionName) main function of the script. It must be a static void, and either parameterless, or with one parameter of type ScriptRunner.
  • Reference references an assembly. It's either a path or an assembly name.
  • Language tells ScriptRunner which compiler you want to use, VB or C#.
  • RefScript adds a file you want to compile with the script.
  • Flag sets CompilerParameters to the ICompiler used for the script compilation.
  • Debug turns the debug mode on this tag. If it doesn't take parameters, the usage is simply: <Debug/>.

The script part is just a simple C# or VB.NET file:

using System;
using System.Collections;
using dotnetScriptor;
namespace Test
{
 public class TestScript
 {
  private static ScriptRunner myRunner;
  public static void RunMe(ScriptRunner MyRunner)
  {
   Console.WriteLine(&quot;Hello World&quot;);
  }
 }
}

Points of Interest

The main part of the engine, compiling, and running the scripts were pretty easy to code, and should be easy to understand.

The more time consuming for me was the sponsor handling. I never used them before because I didn't have Remoting sessions that would take so long that it would make a difference, or I simply set the initial lifetime to one day, which was mostly enough. But for the scripting, I thought a dynamic lifetime lease handling would be more appropriate.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here