Scripted C# - or how to run dynamic C# code, without compilation
As I started to develop small applications in C#, I thought why shouldn't I use the same paradigms that I used in Java... dynamic code execution, execution without compile/build/deploy process, hot-swap, etc.
I am far away from acomplishing the same functionality, but I started with writing a small program (81 lines) mentioned below.
When executing, it is able to compile and execute C# code that is written in text files, on the spot.
The mechanism is quite simple: it expects in the command line the name of an XML file (mentioned below this script) that describes:
- the list of CS files that should be compiled;
- the list of external binaries, if required;
- the title of the application;
- the entry point (the class name that contains the Main function).
An example (SMTPServer.csscript) of the input XML looks like attached.
Useful information:
- the class mentioned in the CSScript/EntryClass node must have a Main(string[]args) method;
- the Main method must be public and static!
- additional parameters to be sent to the Main method are added to the executed command line!
Example of using the script attached.
In my development (that I started yesterday evening) I used some information from the following links:
The application code is straight forward, and I don't think any comment is required. If you need any comment, then either you should learn programming, or study a bit some C-like languages.
If you wonder why I don't also give the EXE file, please feel free to open the Visual Studio, create an empty project, add a class and paste this code.
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Xml;
using System.Xml.XPath;
namespace CSScript {
static class CSScript {
static void Fatal(string message) {
Console.WriteLine("FATAL: "+message);
Console.ReadKey();
Environment.Exit(1);
}
static private XPathNavigator config;
[STAThread]
static void Main(string[] args) {
try {
if(args.Length<1)
Fatal("Usage: CSScript <csscript_file> [<params>]");
if(!System.IO.File.Exists(args[0]))
Fatal("e1204141741 - File '"+args[0]+"' does not exist");
XmlDocument configXml=new XmlDocument();
configXml.Load(args[0]);
config=configXml.CreateNavigator();
if(config.Select("CSScript/EntryClass").Count!=1)
Fatal("'EntryClass' must be one and only one");
if(config.Select("CSScript/Title").Count!=1)
Fatal("'Title' must be one and only one");
Assembly compiledScript=CompileCode();
var title=config.SelectSingleNode("CSScript/Title");
Console.WriteLine("Starting application '{0}'",title);
Console.Title=title.ToString();
string[] parameters=new string[args.Length-1];
for(var i=1;i<args.Length;i++)
parameters[i-1]=args[i];
if(compiledScript==null)
Fatal("e1204141028 - Application not compiled properly.");
var entryClass=config.SelectSingleNode("CSScript/EntryClass").ToString();
Type mainType=getMainType(compiledScript,entryClass);
if(mainType==null)
Fatal("e1204141029 - Type "+entryClass+" could not be found");
var mainMethod=mainType.GetMethod("Main");
if(mainMethod==null)
Fatal("e1204141030 - Main Method 'Main' not found in class "+entryClass);
mainMethod.Invoke(null,new string[][] { parameters });
Console.Write("Press any key to exit...");
Console.ReadKey();
} catch(Exception e) {
Fatal("e1204141747 - "+e.ToString());
}
}
static Assembly CompileCode() {
Microsoft.CSharp.CSharpCodeProvider csProvider=new Microsoft.CSharp.CSharpCodeProvider();
CompilerParameters options=new CompilerParameters();
options.GenerateExecutable=false;
options.GenerateInMemory=true;
options.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
foreach(var assembly in config.Select("CSScript/ReferencedAssemblies/File"))
options.ReferencedAssemblies.Add(assembly.ToString());
var sources=config.Select("CSScript/CSSources/File");
string[]files=new string[sources.Count];
for(int i=0;i<files.Length;i++) {
sources.MoveNext();
files[i]=sources.Current.ToString();
}
CompilerResults results=csProvider.CompileAssemblyFromFile(options,files);
if(results.Errors.HasErrors||results.Errors.HasWarnings)
foreach(CompilerError error in results.Errors)
Console.WriteLine("COMPILATION "+(error.IsWarning?"WARNING":"ERROR")+": "+error.ToString()+": "+error.FileName+":"+error.Line+"@"+error.Column);
if(results.Errors.HasErrors)
Fatal("e1204141031 - Execution aborted, compilation generated errors!");
return results.CompiledAssembly;
}
static Type getMainType(Assembly script,string typeFullName) {
foreach(Type exportedType in script.GetExportedTypes())
if(exportedType.FullName==typeFullName)
return exportedType;
return null;
}
}
}