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.
Related
I am trying to compile razor html templates for the usage in a webbrowser control in Microsoft .net Framework 4 Development. Everything is fine until I want to call "codeProvider.GenerateCodeFromCompileUnit". The IDE says that the type reference to type CodeCompileUnit is mssing in System, even though I am able to declare a CodeCompileUnit on my own ...
I already checked the references, cleaned the solution, tried to restart the IDE and stuff like that but nothing seems to fix the problem.
I don't really know how to go on. Here is the current code:
public static Assembly Compile(IEnumerable<RazorTemplateModel> models)
{
var builder = new StringBuilder();
var codeProvider = new CSharpCodeProvider();
using (var writer = new StringWriter(builder))
{
foreach (var razorTemplateModel in models)
{
GeneratorResults generatorResults = GenerateCode(razorTemplateModel);
codeProvider.GenerateCodeFromCompileUnit(generatorResults.GeneratedCode, writer, new CodeGeneratorOptions());
}
}
var result = codeProvider.CompileAssemblyFromSource(BuildCompilerParameters(), new[] { builder.ToString() });
if (result.Errors != null && result.Errors.Count > 0)
throw new RazorTemplateCompileException(result.Errors, builder.ToString());
return result.CompiledAssembly;
}
The following error message is shown for Line 10 of the code:
ErrorMessage
Here is a screenshot of my System references in the project:
SystemReferences
Can anybody help?
Edit: Forgot to mention that I am using the references in an Mono.Android Project with Xamarin.Android.Support.v4
That's present under System.CodeDOM namespace and so you will have to add using System.CodeDOM; probably. See https://msdn.microsoft.com/en-us/library/system.codedom.codecompileunit(v=vs.110).aspx
Also, can you check the version of System dll you have referenced.
Is it possible to allow dynamically generated assemblies to access dependencies that exist in the project that generates the new assembly? I am working with Unity and C#, and I add an assembly that contains dependencies that exist in the project it now belongs to, but I get this error: FileNotFoundException: Could not load file or assembly 'ModAssembly000.dll' or one of its dependencies . I get this error because I try to put 'using UnityEngine' at the top of the script. This is the code that already exists in the project that gets the new assembly and invokes a method:
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.GenerateInMemory = true;
parameters.OutputAssembly = generatedName;
CompilerResults r = CodeDomProvider.CreateProvider("CSharp").CompileAssemblyFromFile(parameters, filePath);
r.CompiledAssembly.GetType("ModData").GetMethod("Run").Invoke(null, BindingFlags.Static, null, null, null);
This is the source for the dynamically created assembly:
using UnityEngine;
public class ModData {
public static string modName = "Super kool mod";
public static string modVersion = "1.2.1";
public static void Run() {
Debug.Log("it worked :D");
}
}
UnityEngine (thus, Debug.Log) exists in the code that is generating this assembly. Is there a way I can get the new created assembly to use the UnityEngine that exists above it so that I could allow the new code to do anything in the project above it? I know ModAssembly000.dll exists because if I remove the 'using UnityEngine' line, then I can access the static string filds of the dynamic assembly without issue.
My guess is that you need to add Unity to the list of referenced assemblies:
parameters.ReferencedAssemblies.Add("UnityEngine.dll");
For test purposes, I need to get a System.Reflection.Assembly from a string source which contains a source code. I am using Roslyn:
SyntaxTree tree = CSharpSyntaxTree.ParseText(source);
CSharpCompilation compilation = CSharpCompilation.Create("TestCompilation", new[] { tree });
Assembly assembly = null;
using (var stream = new MemoryStream())
{
var emitResult = compilation.Emit(stream);
if (!emitResult.Success)
{
var message = emitResult.Diagnostics.Select(d => d.ToString())
.Aggregate((d1, d2) => $"{d1}{Environment.NewLine}{d2}");
throw new InvalidOperationException($"Errors!{Environment.NewLine}{message}");
}
stream.Seek(0, SeekOrigin.Begin);
assembly = Assembly.Load(stream.ToArray());
}
As you can see my attempt here is to emit a CSHarpCompilation object so that I can get the Assembly later. I am trying to do this with:
var source = #"
namespace Root.MyNamespace1 {
public class MyClass {
}
}
";
Emit errors
But I fail at var emitResult = compilation.Emit(stream) and enter the conditional which shows the error. I get 1 warning and 3 errors:
Warning CS8021: No value for RuntimeMetadataVersion found. No assembly containing System.Object was found nor was a value for RuntimeMetadataVersion specified through options.
(3,34): Error CS0518: Predefined type 'System.Object' is not defined or imported
(3,34): Error CS1729: 'object' does not contain a constructor that takes 0 arguments
Error CS5001: Program does not contain a static 'Main' method suitable for an entry point
So it seems I need to add reference to mscorelib and it also seems like I need to tell Roslyn that I want to emit a class library, not an executable assembly. How to do that?
You're missing a metadata reference to mscorlib and you can change the compilation options via CSharpCompilationOptions.
Create your compilation as follows:
var Mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
var compilation = CSharpCompilation.Create("TestCompilation",
syntaxTrees: new[] { tree }, references: new[] { Mscorlib }, options: options);
For creating a netstandard lib from not-netstandard code (in my case I create a netstandard lib from core3.1) the code should be
var compilation = CSharpCompilation.Create("TestCompilation",
syntaxTrees: new[] {
tree
},
references: new[] {
MetadataReference.CreateFromFile(#"C:\Users\YOURUSERNAME\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll"
},
options:
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
A crux here is the path.
As the host code is core3.1 one cannot use MetadataReference.CreateFromFile(typeof(object).Assembly.Location) as it references a core3.1 object and not a netcore2.0 object.
As referencing a nuget package (nowadays) downloads them to the %USERPROFILE%\.nuget\packages folder it can be loaded from there. This does not hold for any other user though so a different solution must be designed. One could utilise System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile) but that probably won't hold for CI/CD.
Update:
System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile) does hold for CI/CD.
MetadataReference.CreateFromFile( Path.Combine(
UserProfilePath, ".nuget", "packages", "netstandard.library", "2.0.3", "build",
"netstandard2.0", "ref", "netstandard.dll"))
See LehmanLaidun builds.
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
}
}
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.