XNA C# Scripting with CSharpCodeProvider - c#

I'm working on an RPG-style game in XNA and I'm working on implementing a scripting engine.
I've followed a few tutorials to try to get this working. Currently I read in the following from an XML file:
namespace MyGame
{
public class EngagedCode : ScriptingInterface.IScriptType1
{
public string RunScript()
{
ChangeFrame( 2 );
}
}
}
After I get that successfully into the project, I try to compile it with the following code:
Microsoft.CSharp.CSharpCodeProvider csProvider = new Microsoft.CSharp.CSharpCodeProvider();
CompilerParameters options = new CompilerParameters();
options.GenerateExecutable = false; //DLL
options.GenerateInMemory = true;
options.IncludeDebugInformation = true;
options.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
CompilerResults result = csProvider.CompileAssemblyFromSource(options, code);
However, at this point I always get the following error:
'result.CompiledAssembly' threw an exception of type 'System.IO.FileNotFoundException'
It seems as if the system is unable to find the .dll I've compiled, and I don't know why. I don't know how to get past this error. Does anybody have any suggestions?

Even if you generate it in memory it still writes a .dll to disk, unless you have compilation errors, and then you get this useless System.IO.FileNotFoundException. So most likely you have compile errors.
In order to pull those compile errors you need to add the below.
CompilerResults results = csProvider.CompileAssemblyFromSource(parameters, textBox1.Text);
if (results.Errors.Count > 0)
{
foreach (CompilerError CompErr in results.Errors)
{
//Hooray a list of compile errors
}
else
{
//Successful Compile
}
}
Also if you want to skip all this. Take a look at this class. It allows you just use the method body, this may not be sufficient for you though. Also you will need to change the namespace in the const CodeStart string.

The following line is not required:
options.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);

Related

C# Loading assemblies dynamically

I'm having an issue when compiling text into dynamic objects at runtime.
I wrote a simple piece of code to compile the text:
public class CompileFactory
{
public dynamic Compile(String classCode, String mainClass, Object[] requiredAssemblies)
{
CSharpCodeProvider provider = new CSharpCodeProvider(new Dictionary<string, string>
{
{ "CompilerVersion", "v4.0" }
});
CompilerParameters parameters = new CompilerParameters
{
GenerateExecutable = true, // Create a dll
GenerateInMemory = true, // Create it in memory
WarningLevel = 3, // Default warning level
CompilerOptions = "/optimize", // Optimize code
TreatWarningsAsErrors = false // Better be false to avoid break in warnings
};
// Add all extra assemblies required
foreach (var extraAsm in requiredAssemblies)
{
parameters.ReferencedAssemblies.Add(extraAsm as string);
}
CompilerResults results = provider.CompileAssemblyFromSource(parameters, classCode);
if (results.Errors.Count != 0)
{
return "FAILED";
}
return results.CompiledAssembly.CreateInstance(mainClass); ;
}
}
This is how I am using the Compile method.
List<string> assemblies = new List<string>{"System.Net.Mail.dll", "System.Net.dll"};
dynamic obj = compile.Compile(fileText, pluginName, assemblies.ToArray());
As you can see I'm adding references to extra assemblies at some point. For some reason when I add using System.Net; to the text file, it will not be referenced and I get errors. The text I'm compiling is literally a .cs file saved as text. I thought of working around this by extracting the using * and adding them separately, however for when adding System.Net.Mail.dll, the metadata file cannot be found.
Has anyone experienced something similar? I really would like to just add the using * to the file and be ready with it.
Any input would be greatly appreciated.
The issue here is that System.Net.dll does not exist. You can check in which assembly a .Net type is by right clicking somewhere it is referenced and choosing "Go to definition". This will bring up a tab with the class definition "from metadata". At the top of this file, you've got a #region showing where this type comes from. In the case of a TcpClient, we can see this:
#region Assembly System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.dll
#endregion
Change your call to Compile with "System.dll" instead of "System.Net.dll" and it should work just fine
Edit/Clarification: It is not possible to get an assembly name from a using statement.

Possible to use C# codeDOM to call back to pre-compiled functions?

I'm working on a silly game where the player controls their character by programming procedures for it to follow. I'm using C# codeDOM to compile the code the player writes and I'd like for the player to be able to call functions written into the pre-compiled part of the software. For example:
Player-written code to be compiled at run-time by codeDOM:
namespace AutoCrawl
{
public class Player
{
public void Go_Up()
{
Move("Up");
}
}
}
My pre-compiled code:
private void compileUserCode()
{
string code = UserCodeTextBox.Text;
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = false;
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
}
private void Move(string direction)
{
//move the player's character in the direction specified by "direction"
}
The problem is that I don't know how to tell the codeDOM compiler that it can find the function 'Move' in my own, pre-compiled code. I get the following error from the codeDOM compiler:
Error (CS0103): The name 'Move' does not exist in the current context
Is it even possible? I can't seem to find any examples of other people using codeDOM in this way.
Thank you for your help!
Best way to do this is set the ReferencedAssemblies property on your CompilerParameters to be a lib that contains your additional code.
var parameters = CompilerParameters
{
ReferencedAssemblies = {
"my.dll",
// etc
}
};
Here is a longer blog post on the subject including link to github
http://danielslaterblog.blogspot.co.uk/2015/05/programming-programming-computer-game.html

CodeDomProvider Code Generation Fails With Certain Linq Syntax

I'm using the CodeDomProvider to compile some Linq code and execute queries dynamically. However, I'm hitting a very strange issue.
If my Linq query in the generated code looks like this everything works:
namespace Dynamic
{
using System.Linq;
using System.Collections.Generic;
public static class Query
{
public static int GetRecords()
{
MyData.Data.DataMart container = new MyData.Data.DataMart();
return (container.EventDetails).Count();
}
}
}
This compiles and runs just fine. However, if I change the linq query to the following then it fails to compile:
return (from e in container.EventDetails select e).Count();
It works fine if I put this as static code, but if I try to compile it with the CodeDomProvider it fails (and I haven't found any good method to get error messages on why it fails). I would like to use the from-in-select style of syntax since this will make it easier for me to generate the linq queries but I can't figure out why they are not compiling.
You can see some of the code I use to compile this snippet at the link on the top of this post.
Thanks!
Edit: Copying the code from the post I linked to:
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters cp = new CompilerParameters();
cp.GenerateInMemory = true;
cp.ReferencedAssemblies.Add("mscorlib.dll");
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Core.dll");
cp.ReferencedAssemblies.Add("System.Data.Linq.dll");
cp.ReferencedAssemblies.Add("System.Data.Entity.dll");
cp.ReferencedAssemblies.Add("MyApp.Data.dll");
var results = provider.CompileAssemblyFromSource(cp, source);
var assm = results.CompiledAssembly;
Edit2: As far as the exception goes, I get an exception on the second to last line of code (var results = ...). The exception is a BadImageFormatException:
Could not load file or assembly '0 bytes loaded from System,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' or
one of its dependencies. An attempt was made to load a program with an
incorrect format
This seems to work for me:
static void Main(string[] args)
{
string sourceCode = #"namespace Dynamic {
using System.Linq;
using System.Collections.Generic;
public static class Query
{
public static int GetRecords()
{
MyApp.Data.DataMart container = new MyApp.Data.DataMart();
//return (container.EventDetails).Count();
return (from e in container.EventDetails select e).Count();
}
} }";
string sDynamDll = "Dynamic.dll";
string sDynamClass = "Query";
string sDynamMethod = "GetRecords";
System.CodeDom.Compiler.CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = false;
cp.GenerateInMemory = true;
cp.OutputAssembly = sDynamDll;
cp.ReferencedAssemblies.Add("mscorlib.dll");
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Core.dll");
cp.ReferencedAssemblies.Add("System.Data.Linq.dll");
cp.ReferencedAssemblies.Add("System.Data.Entity.dll");
cp.ReferencedAssemblies.Add("MyApp.Data.dll");
var providerOptions = new Dictionary<string, string>();
providerOptions.Add("CompilerVersion", "v4.0");
CodeDomProvider compiler = CodeDomProvider.CreateProvider("C#", providerOptions);
CompilerResults cr = compiler.CompileAssemblyFromSource(cp, sourceCode);
if (cr.Errors.HasErrors)
{
StringBuilder errors = new StringBuilder("Compiler Errors :\r\n");
foreach (CompilerError error in cr.Errors)
{
errors.AppendFormat("Line {0},{1}\t: {2}\n", error.Line, error.Column, error.ErrorText);
}
}
// verify assembly
Assembly theDllAssembly = null;
if (cp.GenerateInMemory)
theDllAssembly = cr.CompiledAssembly;
else
theDllAssembly = Assembly.LoadFrom(sDynamDll);
Type theClassType = theDllAssembly.GetType(sDynamClass);
foreach (Type type in theDllAssembly.GetTypes())
{
if (type.IsClass == true)
{
if (type.FullName.EndsWith("." + sDynamClass))
{
theClassType = type;
break;
}
}
}
// invoke the method
if (theClassType != null)
{
object[] method_args = new object[] { };
Object rslt = theClassType.InvokeMember(
sDynamMethod,
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
null, // for static class
method_args);
Console.WriteLine("Results are: " + rslt.ToString());
}
Console.ReadKey();
}
You're probably getting BadImageFormatException because your code isn't actually compiling to a valid assembly. This might be because the old 2.0 compiler is used by default. Check the link below for enabling the C# the 3.5 version (I don't know if 4.0 is supported, but you don't need it):
http://blogs.msdn.com/b/lukeh/archive/2007/07/11/c-3-0-and-codedom.aspx
Also check the Errors collection on the CompilerResult that is returned from the CompileAssemblyFromSource() method. Failure to compile does not throw an exception, you must manually check for compile errors.
I didn't find an answer of how to get good exception information, however, I did solve this problem. The class library that contained the compiler code above was set to AnyCpu but the context it was running in under ASP.Net was x86. So this was causing it to fail when it tried to load System.dll since it was loading the wrong version (or something silly like that).
I'll be happy to give someone else the answer checkmark if you can (a) figure out how to get a real error message from this or (b) load the right reference type.

How to get error info from dynamic C# code compiling

I have a C# application which uses a C# script interface. That means that my application will compile C# code and run it.
I am using the System.CodeDom.Compiler class to do it with.
The problem is that if I run the code below it throws an InvalidCastException because it is trying to cast a string to an int in my dynamic code.
If I catch the exception I have no indication where in the 'dynamic code' that error occured. For instance 'InvalidCastException on line 8'.
I get a stack trace, but no line numbers.
Any ideas? I want to present to our users enough information to know where their error is.
public class NotDynamicClass
{
public object GetValue()
{
return "value";
}
}
class Program
{
public static void Main()
{
var provider = CSharpCodeProvider.CreateProvider("c#");
var options = new CompilerParameters();
options.ReferencedAssemblies.Add("DynamicCodingTest.exe");
var results = provider.CompileAssemblyFromSource(options, new[]
{
#"
using DynamicCodingTest;
public class DynamicClass
{
public static void Main()
{
NotDynamicClass #class = new NotDynamicClass();
int value = (int)#class.GetValue();
}
}"
});
var t = results.CompiledAssembly.GetType("DynamicClass");
t.GetMethod("Main").Invoke(null, null);
}
}
You need to set IncludDebugInformation to true on your CompilerParameters.
Update: At the bottom of the MSDN documentation there is a community remark:
For C#, if you set this property to true you need to also set GenerateInMemory to false and set the value of OutputAssembly to a valid file name. This will generate an assembly and a .pdb file on disk and give you file and line number information in any stacktraces thrown from your compiled code.

Compile simple string

Was just wondering if there are any built in functions in c++ OR c# that lets you use the compiler at runtime? Like for example if i want to translate:
!print "hello world";
into:
MessageBox.Show("hello world");
and then generate an exe which will then be able to display the above message? I've seen sample project around the web few years ago that did this but can't find it anymore.
It is possible using C#. Have a look at this Sample Project from the CodeProject.
Code Extract
private Assembly BuildAssembly(string code)
{
Microsoft.CSharp.CSharpCodeProvider provider = new CSharpCodeProvider();
ICodeCompiler compiler = provider.CreateCompiler();
CompilerParameters compilerparams = new CompilerParameters();
compilerparams.GenerateExecutable = false;
compilerparams.GenerateInMemory = true;
CompilerResults results = compiler.CompileAssemblyFromSource(compilerparams, code);
if (results.Errors.HasErrors)
{
StringBuilder errors = new StringBuilder("Compiler Errors :\r\n");
foreach (CompilerError error in results.Errors )
{
errors.AppendFormat("Line {0},{1}\t: {2}\n", error.Line, error.Column, error.ErrorText);
}
throw new Exception(errors.ToString());
}
else
{
return results.CompiledAssembly;
}
}
public object ExecuteCode(string code, string namespacename, string classname, string functionname, bool isstatic, params object[] args)
{
object returnval = null;
Assembly asm = BuildAssembly(code);
object instance = null;
Type type = null;
if (isstatic)
{
type = asm.GetType(namespacename + "." + classname);
}
else
{
instance = asm.CreateInstance(namespacename + "." + classname);
type = instance.GetType();
}
MethodInfo method = type.GetMethod(functionname);
returnval = method.Invoke(instance, args);
return returnval;
}
In C++ you can't use the compiler at runtime but you can embed an interpreter in your project, like CINT.
You can always do it in the dirty way, with system() and calling the compiler "gcc..." or your equivalent
Nick's suggestion is good, but there is an alternative which is probably simpler to implement (but might not be appropriate for all projects). If you can assume that your user has a compiler installed you can generate a file and then compile it using their compiler.
The .NET-framework provides a few classes which give you access to compilers and code generators for C# and VB.NET, resulting in either an assembly loaded into memory or a simple .exe-file. See CSharpCodeProvider and this article.
Alternately, you can just create the source files and compile them manually (command-line calls (system) to the compiler, makefiles).
Concerning the translation of your source: You'll have to use parsing mechanisms like regular expressions here, or use a compiler-compiler tool like Coco/R, yacc etc. (Note that under C++, boost::spirit can also be quite useful)
In C# you can create a .NET "CodeDom" tree and then compile this using the .NET compiler. This gives you full access to most features of .NET.
See the "System.CodeDom" namespace or the MSDN help for CodeCompileUnit for details.

Categories

Resources