Runtime Scripting in .Net
The ability to ship source code with your product that can be compiled or interpreted at runtime is a very valuable asset. Video games do this all the time with things like AI allowing the end-user to easily mod the game. In compiled languages like C++, this technique is highly advantageous as it allows you to avoid the costly compile-link cycle for every small change to a source file. This is also helpful in an environment where creating a new build, generating the installer, and deploying it is overkill or difficult to achieve. In this article we will examine how you can achieve this from .Net programs consuming a C# or VB.Net script. I’ll refer to this as “scripting”, even though an unambiguous definition of a “script” is somewhat difficult to nail down.
Using a C# or VB script from a .Net language is actually very simple. The basic idea is to read in a source file, which could come from the network or disk, and compile it into a temporary assembly on file or IL in memory. Then you can instantiate types from the script file and use them just as easily as any other types already defined. Normally the hard part in this process is locating and invoking the proper compiler. Luckily, we can compile IL code using a CodeDomProvider like CSharpCodeProvider. And, since these types are part of the .Net framework we know they are available on the end-user PC! Life is great eh?
Ok, so first make sure you have referenced the following namespaces:
using System.CodeDom.Compiler; using Microsoft.CSharp; using Microsoft.VisualBasic;
Next you need to setup the compiler parameters. These are simply the options given to the compiler such as whether to create the resulting assembly in memory or if debug information should be included in the generated code. You can do this with the CompilerParameters type. Here is an example for creating an in memory assembly with debug information:
CompilerParameters cparams = new CompilerParameters(); cparams.GenerateInMemory = false; cparams.IncludeDebugInformation = true; cparams.CompilerOptions += " /debug:pdbonly"; cparams.TreatWarningsAsErrors = false; cparams.GenerateExecutable = false;
You would also want to add any references that your code needs using CompilerParameters. ReferencedAssemblies. So, after you have the compiler options set you need to actually invoke the C# or Visual Basic compiler. You do this by creating a CodeDomCompiler of the appropriate type:
VBCodeProvider – for Visual Basic
CSharpCodeProvider – for C Sharp
Then you can use the provider to compile an assembly like so:
CodeDomProvider provider = CSharpCodeProvider(); CompilerResults result = provider.CompileAssemblyFromFile(cparams, script);
Where cparams are your compiler parameters and result is well, your results. Within results you will find out if the compilation was successful or generated warnings. It will also give you access to the resulting assembly. You can check for errors using results.Errors.HasErrors and reference the assembly with results.CompiledAssembly.
Now that you have a compiled assembly in memory, there is one last issue to address. How do you instantiate types from the assembly? The easiest way to attack the problem is to create an interface that your application can access and derive a type from that interface in your script. I’ve created a full example below (explained in code comments) to illustrate the technique I am talking about.
C# Example
Main application – Script.cs
using System; using System.CodeDom.Compiler; using Microsoft.CSharp; using Microsoft.VisualBasic; using System.Reflection; using System.IO; namespace ScriptingExample { public class Script { public LoadedType LoadScript<LoadedType>(string script, string referenceString) where LoadedType:class { // this will load/compile the script source file, "script", and return a type of LoadedType // it is assumed that a type of "LoadedType" is defined in the script // the referenceString is a comma delimited string identifying all references needed by the script // e.g. "System.dll,System.Windows.Forms.dll" // either the VB or C# compiler will be invoked, based upon the file extenstion .vb or .cs // try and compile the script CompilerParameters cparams = CreateCompilerParameters(referenceString); CompilerResults results = CompileScript(script, cparams); // check for errors if (results.Errors.HasErrors) { string e = ""; foreach (CompilerError error in results.Errors) e += (error.ErrorText + Environment.NewLine); throw new Exception("ERROR IN SCRIPT: " + Environment.NewLine + e); } // find the "exported" type to load LoadedType scriptObject = FindScriptObject<LoadedType>(results.CompiledAssembly); if (scriptObject == null) throw new Exception("CAN'T LOAD SCRIPT, TARGET TYPE NOT IMPLEMENTED IN SCRIPT."); else return scriptObject; } private CompilerResults CompileScript(string script, CompilerParameters cparams) { // compiles the script using the specified parameters // either the VB or C# compiler will be invoked, based upon the file extenstion .vb or .cs CodeDomProvider provider = GetCompiler(script); CompilerResults result = provider.CompileAssemblyFromFile(cparams, script); return result; } CodeDomProvider GetCompiler(string script) { // returns the correct compiler to use based on the file extension of the script string extension = Path.GetExtension(script); if (extension == ".vb") return new VBCodeProvider(); else if (extension == ".cs") return new CSharpCodeProvider(); else throw new Exception("UNKNOWN SCRIPT TYPE"); } private LoadedType FindScriptObject<LoadedType>(Assembly assembly) where LoadedType : class { // attempts to locate the type "LoadedType" within the assembly that was created from the "script" foreach (Type t in assembly.GetTypes()) if (typeof(LoadedType).IsAssignableFrom(t)) return Activator.CreateInstance(t) as LoadedType; return default(LoadedType); } private CompilerParameters CreateCompilerParameters(string referenceString) { // creates compiler parameters to generate an in memory assembly, with debug // symbols if this is a debug build // the referenceString is a comma delimited string identifying all references needed by the script CompilerParameters cparams = new CompilerParameters(); cparams.GenerateInMemory = true; #if DEBUG // create debug symbols if this is a debug build cparams.IncludeDebugInformation = true; cparams.CompilerOptions += " /debug:pdbonly"; #endif cparams.TreatWarningsAsErrors = false; cparams.GenerateExecutable = false; // seperate reference string string[] references = referenceString.Split(','); foreach (string reference in references) cparams.ReferencedAssemblies.Add(reference); return cparams; } } }
App Showing How to Use Script.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.IO; namespace ScriptingExample { // we have to define an interface so we know how to "talk" to // the script, that is, there must be a type that we *know* the // script implements so we can call methods on it public interface MyScript { void Execute(); } public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { // just populate the combo box with all "scripts" located in the directory "Scripts" foreach (string file in Directory.GetFiles("Scripts")) comboBox1.Items.Add(file); if (comboBox1.Items.Count > 0) comboBox1.SelectedIndex = 0; else { button1.Enabled = false; comboBox1.Enabled = false; } } private void button1_Click(object sender, EventArgs e) { // load the script, which contains a type that implements MyScript // and call the execute method on it Script s = new Script(); // these are the references the script will need, we must reference this executable // because the script needs to see the MyScript data type // this is just a sample, in a real application such types would be defined in an assembly dll // that is shared between the app consuming the script and the script itself string references = "System.dll,System.Windows.Forms.dll," + Directory.GetCurrentDirectory() + "\\ScriptingExample.exe"; MyScript script = s.LoadScript<MyScript>(comboBox1.SelectedItem as string, references); script.Execute(); } } }
A VB and C# Script
using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; namespace ScriptingExample.Scripts { public class CSharpScript : MyScript { public void Execute() { MessageBox.Show("I AM A C# SCRIPT"); } } }
Imports System Imports System.Collections.Generic Imports System.Text Imports System.Windows.Forms Namespace ScriptingExample.Scripts Public Class VBScript Implements MyScript Public Sub Execute() Implements MyScript.Execute MessageBox.Show("I AM A VB SCRIPT!!") End Sub End Class End Namespace
Conclusion
And that’s it! A very simple technique that can be used in many interesting ways. I’ve included a link to a Visual Studio project containing an implementation using the above information. The project can be found here As always, let me know if you have any questions or ideas!
See ya next time!
September 30, 2009
Posted in: C#

6 Responses
Thank you! I would now go on this blog every day!
Bodyc
thnak nice
http://csharptalk.com
lazy
its really nice.
is it possible to creat script engine. in c#?
actually i have one VB6 application through which i can execute vbscript and java script text file .Named as Script Tester.
But whatever the script i write it is in .tst or .Seq file.
we dont use .vbs extension for script file.
i am not getting how we can compile code writen in .txt file using vb application.
i tried to compile these file using Wscript.exe but it not executeing.
can you please guide me on this how this application works
is it possible create simillar application in dotnet.
Please Mail me if any suggestion on below mailID.
ajay_dhuppe@rediffmail.com
Сергей - June 6, 2010
http://rel” rel=”nofollow”> Спасибо,…
Хотя новость уже читал…
Soviet - June 9, 2010
http://rel” rel=”nofollow”>Хм…..…
Ссылки как то непонятно отображаются…
Webmaster - June 10, 2010
Please e-mail me your contacts. I have a question webmaster@spottovo.ru” rel=”nofollow”>……
Thank you!!!…
Leave a Reply