How to get error info from dynamic C# code compiling - c#

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.

Related

How can I allow users to write C# lambda in config and load them dynamically

I have a set of data which is not particularly clean, and I have written functions for LINQ queries that filter out what I think is unnecessary data. I have written these as lambdas. However I would like to be able to put the lambdas in the .config file so they can be fiddled with without having to recompile the entire application. I was sure that this could be done, and I have managed to find some code online which takes C# source code and compiles it:
internal static class DynamicDelegates
{
internal static Assembly CompileAssembly(string source)
{
var compilerParameters = new CompilerParameters()
{
GenerateExecutable = false,
GenerateInMemory = true,
ReferencedAssemblies =
{
"System.Core.dll", // needed for linq + expressions to compile
"PatchDataLibrary.dll" // A dependency on the main application.
},
};
var providerOptions = new Dictionary<string, string>();
providerOptions.Add("CompilerVersion", "v4.5.2");
var compileProvider = new CSharpCodeProvider(providerOptions);
var results = compileProvider.CompileAssemblyFromSource(compilerParameters, source);
if (results.Errors.HasErrors)
{
Console.Error.WriteLine("{0} errors during compilation of rules", results.Errors.Count);
foreach (CompilerError error in results.Errors)
{
Console.Error.WriteLine(error.ErrorText);
}
throw new InvalidOperationException("Broken rules configuration, please fix");
}
var assembly = results.CompiledAssembly;
return assembly;
}
}
The code following leverages this to return the delegate (ReleaseType.ProductBelongs):
namespace Shibboleth.ReleaseHandoverUtility.Configuration
{
internal class ProductBelongsDelegateElement : ConfigurationElement
{
private const string _classTemplate = #"
using System;
using System.Linq.Expressions;
using PatchDataLibrary.Models.ReleaseModel;
namespace Shibboleth.ReleaseHandoverUtility
{{
public static class ProductBelongsLib
{{
public static Shibboleth.ReleaseHandoverUtility.ReleaseType.ProductBelongs ProductBelongs {{ get {{ return {0}; }} }}
}}
}}
";
private ProductBelongs _value;
protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey)
{
string value = (reader.ReadElementContentAsString());
var tempAssembly = DynamicDelegates.CompileAssembly(string.Format(_classTemplate, value));
var type = tempAssembly.GetTypes().Single();
var property = type.GetRuntimeProperties().Single();
var propertyValue = property.GetValue(null, null);
_value = (ProductBelongs)propertyValue;
}
public ProductBelongs Value { get { return _value; } }
}
The problem is that I am targeting Framework 4.5.2, but when it compiles the code, I get the exception:
System.InvalidOperationException occurred
HResult=-2146233079
Message=Compiler executable file csc.exe cannot be found.
Looking online, it seems that this is because this is used to construct the path, and v4.5.2 is in the v4.0 path. Fair enough, so I change:
providerOptions.Add("CompilerVersion", "v4.5.2");
to
providerOptions.Add("CompilerVersion", "v4.0");
This time I get an error in the errors collection of the compiler:
[0] = {c:\Users\Mark.Bertenshaw\AppData\Local\Temp\nyposo3b.0.cs(10,74) : error CS0234: The type or namespace name 'ReleaseType' does not exist in the namespace 'Shibboleth.ReleaseHandoverUtility' (are you missing an assembly reference?)}
Taking the code that causes this compilation error and putting it into a new project for framework v4.5.2 doesn't get an error. However, when as an experiment I change it to v4.0 I reproduce the error.
So can anyone suggest how I can force the framework to be 4.5.2? Or maybe there is an alternative way of doing this? Ideally, this doesn't involve downloading extra compilers and scripting frameworks.

The type or namespace Windows does not exists in namespace System when compiling code dynamically c# [duplicate]

This question already has an answer here:
C# CompileAssemblyFromSource, add referenced assemblies it needs?
(1 answer)
Closed 5 years ago.
I need to compile some code dynamically in my Winform application to execute C# code wrote by a user.
To make some tests, I wrote this function :
public object executeDynamicCode(string code)
{
using (Microsoft.CSharp.CSharpCodeProvider foo = new Microsoft.CSharp.CSharpCodeProvider())
{
try
{
var res = foo.CompileAssemblyFromSource(
new System.CodeDom.Compiler.CompilerParameters()
{
GenerateInMemory = true
},
"using System.Windows.Forms; public class FooClass { public void Execute() {MessageBox.Show();}}");
if (res.Errors.Count > 0)
MessageBox.Show(res.Errors[0].ErrorText);
var type = res.CompiledAssembly.GetType("FooClass");
var obj = Activator.CreateInstance(type);
var output = type.GetMethod("Execute").Invoke(obj, new object[] { });
return output;
}
catch (Exception e)
{
MessageBox.Show(e.Message);
return null;
}
}
}
But when I execute it, I got this error :
The type or namespace Windows does not exists in System namespace (an
assembly reference are missing ?)
If someone got any idea ?
Thank you in advance !
Add a namespace to it, matching your main project's namespace. I believe it's executing in a separate context since it doesn't have the same namespace, so it doesn't find the references included in the rest of the project.
Also, change your compiler options to
new System.CodeDom.Compiler.CompilerParameters()
{
GenerateInMemory = true,
CoreAssemblyFileName = "mscorlib.dll",
ReferencedAssemblies = { "System.dll", "System.Windows.dll", "System.Windows.Forms.dll","Microsoft.CSharp.dll" }
}
that works, I've tried it. You may not need all of those referenced assemblies, its the CoreAssemblyFileName that really mattered.

Cannot Access static field c# Error

I have the following c# code within a 2012 SSIS package Script Task using 4.5 Framework:
[Microsoft.sqlServer.Dts.Tasks.ScripTask.SSISScriptTaskEntryPointAttribute]
Public partial class ScriptMain
{
static ScriptMain()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomai_AssemblyResolveForDLL);
}
Static System.Reflection.Assembly CurrentDomain_AssemblyResolveForDLL(object sender, ResolveEventArgs args)
{
If (args.Name.Contains("NameofMydll"))
{
string path = #"c:\Temp\";
return System.Reflection.Assembly.LoadFile(System.IO.Path.Combine(path, "NameofMydll.dll"));
}
}
return null;
}
The purpose of this code is to load a .dll file used in a SSIS Script task during run time. The way the code works is just like what is described here. With the path hard coded, it works fine.
The problem I have is I need to dynamically set the value of the path so I can promote the code from server to server with no hard coded values. Since the function is static, I didn't know how to do it.
The line of code here:
MyVariable = (string)Dts.Variables.["MyVariable"].value;
...receives the value with MyVariable declared as a global public string variable (this works fine as long as its not within the code above). But using "MyVariable" in place of the #"c:\Temp\" is where I get the Cannot access non static field...error.
Changing the declaration to public static string allows it to compile, but throws an error at run time.
Exception has been thrown by the target of invocation.
The value is assigned through a function just before the line of code below is called:
AppDomain.CurentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolveForDLL)
The call to the function is the first line of code in Main(), the line of code above is the 2nd.
All I need to do is assign the value of the path from a variable. Any assistance will be greatly appreciated.
Change to MyVariable = (string)Dts.Variables.["MyVariable"].value;
or access via it's positionMyVariable = (string)Dts.Variables.[0].value;

Add new cs files on-the-fly to load on my running application?

I have a command handler which basically works like this:
ControlList.Handlers[CommandType.MyCommandComesHere].Handle(data);
Handlers is a Dictionary<CommandType, ICommandHandler> and CommandType is a enum.
Handle by its turn would lead it to this:
using System;
using log4net;
namespace My_Application
{
public class MyCommand : ICommandHandler
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(MyCommand));
public void Handle(Events data)
{
Console.WriteLine("I can load cs files on the fly yay!!");
}
}
}
My question is how can I make so my application would compile and let me use that cs file while its running?
Any simple example of this would be greatly appreciated but not required as long as I can get some pointers as to what I need to look for as I am not even sure what do I need to make this happen.
To put it simple I am currently trying to understand how could I load a cs file into my application that is already compiled and is currently running.
Using CodeDOM, you need to first create a compiler provider. (You might want to set GenerateExecutable to false and GenerateInMemory to true for your purposes.)
var csc = new CSharpCodeProvider();
var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" }, "foo.exe", true);
parameters.GenerateExecutable = false;
parameters.GenerateInMemory = true;
Then, you can compile the assembly using CompileAssemblyFromSource and get the CompilerResults returned from it. From this returned object, get a reference to the generated assembly, using its CompiledAssembly property.
var results = csc.CompileAssemblyFromSource(parameters, "contents of the .cs file");
var assembly = results.CompiledAssembly;
Then you can use reflection to create instances from that assembly and call methods on them.
var instance = assembly.CreateInstance("MyCommand");
// etc...
Alternatively, if you're only interested in short code snippets, it might be worth it to use Roslyn instead. You need to create a ScriptEngine first.
var engine = new ScriptEngine();
Then you can just Execute strings on it - or Execute<T> if you're confident that the expression in the string returns a type assignable to T.
var myObject = engine.Execute("1+1");
var myInt = engine.Execute<int>("1+1");
It's definitely more immediate, so it's worth looking into if it serves your purpose.
I have looked for different ways to achieve this and found cs script library lightweight and usable. Here is code snippet how I use it. It runs cs code within app domain so it presumes, that the cs script being compiled comes form trusted source.
using CSScriptLibrary;
using csscript;
using System.CodeDom.Compiler;
using System.Reflection;
//Method example - variable script contains cs code
//This is used to compile cs to DLL and save DLL to a defined location
public Assembly GetAssembly(string script, string assemblyFileName)
{
Assembly assembly;
CSScript.CacheEnabled = true;
try
{
bool debugBuild = false;
#if DEBUG
debugBuild = true;
#endif
if (assemblyFileName == null)
assembly = CSScript.LoadCode(script, null);
else
assembly = CSScript.LoadCode(script, assemblyFileName, debugBuild, null);
return assembly;
}
catch (CompilerException e)
{
//Handle compiler exceptions
}
}
/// <summary>
/// Runs the code either form script text or precompiled DLL
/// </summary>
public void Run(string script)
{
try
{
string tmpPath = GetPathToDLLs(); //Path, where you store precompiled DLLs
string assemblyFileName;
Assembly assembly = null;
if (Directory.Exists(tmpPath))
{
assemblyFileName = Path.Combine(tmpPath, GetExamScriptFileName(exam));
if (File.Exists(assemblyFileName))
{
try
{
assembly = Assembly.LoadFrom(assemblyFileName); //Načtení bez kompilace
}
catch (Exception exAssemblyLoad)
{
Tools.LogError(exAssemblyLoad.Message);
assembly = null;
}
}
}
else
assemblyFileName = null;
//If assembly not found, compile it form script string
if (assembly ==null)
assembly = GetAssembly(script, assemblyFileName);
AsmHelper asmHelper = new AsmHelper(assembly);
//This is how I use the compiled assembly - it depends on your actual code
ICalculateScript calcScript = (ICalculateScript)asmHelper.CreateObject(GetExamScriptClassName(exam));
cex = calcScript.Calculate(this, exam);
Debug.Print("***** Calculated {0} ****", exam.ZV.ZkouskaVzorkuID);
}
catch (Exception e)
{
//handle exceptions
}
}

XNA C# Scripting with CSharpCodeProvider

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);

Categories

Resources