In C#, you can compile and run code at runtime (after the program has started). This allows users to add code or functionality to the program. I used the following code in IceBlink to allow users to create and run their own scripts (from IceBlink Engine GitHub):
Code:
#region Script Stuff
public void executeScript(string scriptPathFileName)
{
bool foundOne = false;
Assembly compiledScript = null;
string filename = Path.GetFileNameWithoutExtension(scriptPathFileName);
foreach (AssemblyObjects ao in assemblyObjList)
{
if (ao.AssemblyFileName == filename)
{
foundOne = true;
compiledScript = ao.AssemblyCompiled;
break;
}
}
if (!foundOne)
{
string textReturn = readTextFile(scriptPathFileName);
compiledScript = CompileCode(textReturn);
AssemblyObjects newAO = new AssemblyObjects();
newAO.AssemblyFileName = filename;
newAO.AssemblyCompiled = compiledScript;
assemblyObjList.Add(newAO);
}
if (compiledScript != null)
{
RunScript(compiledScript);
}
}
public 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);
options.ReferencedAssemblies.Add("system.dll");
options.ReferencedAssemblies.Add("system.data.dll");
options.ReferencedAssemblies.Add("System.Windows.Forms.dll");
options.ReferencedAssemblies.Add("System.Drawing.dll");
//options.ReferencedAssemblies.Add("System.Drawing.Bitmap.dll");
options.ReferencedAssemblies.Add("IceBlinkCore.dll");
options.ReferencedAssemblies.Add("IceBlinkScriptFunctions.dll");
options.ReferencedAssemblies.Add("IceBlink.exe");
options.ReferencedAssemblies.Add("IceBlinkToolset.exe");
// 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
StringBuilder sbErr;
sbErr = new StringBuilder("Compiling file: ");
sbErr.AppendFormat("\"{0}\"", "script.cs");
sbErr.Append("\n\n");
foreach (CompilerError err in result.Errors)
{
sbErr.AppendFormat("{0} at line {1} column {2} ", err.ErrorText, err.Line, err.Column);
sbErr.Append("\n");
}
MessageBox.Show(sbErr.ToString(), "C#Script – Error");
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;
}
public void RunScript(Assembly script)
{
try
{
object o = script.CreateInstance("IceBlink.IceBlinkScript");
object[] parms = new object[] { frm.sf, parm1, parm2, parm3, parm4 };
o.GetType().GetMethod("Script").Invoke(o, parms);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
this.errorLog(ex.ToString());
}
}
public string readTextFile(string filePath)
{
StreamReader streamReader = new StreamReader(filePath);
string text = streamReader.ReadToEnd();
streamReader.Close();
return text;
}
#endregion