I have this console type thing that accepts a line of C# code, wraps it up in some surrounding code and compiles it into an assembly. Then, I invoke the method from that assembly, output the result and that's it.
The problem is, the assembly needs to have a name so I can set it as a friend assembly so it could access the non-public classes. I named it "console".
Everything worked as expected but the problem is that I can't run a second script after one has finished because the file named "console" already exists in the directory and can't be overwritten.
I've tried Disposing of everything that has a Dispose method. I've tried manually deleting the file with File.Delete. Nothing helped.
So here's the code I'm using. I hope someone can help me.
CSharpCodeProvider provider = new CSharpCodeProvider();
var param = new CompilerParameters();
param.GenerateInMemory = false;
param.GenerateExecutable = false;
param.OutputAssembly = "console";
param.ReferencedAssemblies.Add("System.dll");
param.ReferencedAssemblies.Add("System.Core.dll");
param.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
var results = provider.CompileAssemblyFromSource(param,
#"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace FireflyGL
{
class DebugConsole
{
static void Run(StringWriter writer)
{
var stdOut = Console.Out;
Console.SetOut(writer);
" + input.Text + #"
Console.SetOut(stdOut);
}
}
}");
if (results.Errors.HasErrors)
{
for (int i = 0; i < results.Errors.Count; ++i)
{
PushText(results.Errors[i].ErrorText);
}
}
else
{
try
{
var writter = new StringWriter();
results.CompiledAssembly.GetType("FireflyGL.DebugConsole").GetMethod("Run", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { writter });
PushText(writter.ToString());
history.Add(input.Text);
currentHistory = history.Count;
input.Text = "";
writter.Dispose();
}
catch (Exception)
{
}
}
provider.Dispose();
File.Delete(results.CompiledAssembly.Location);
You have to unload the assembly to get rid of all the references. Unfortunately, you can't do that. You can however unload an AppDomain and provided you reference your assembly in that AppDomain, it will be unloaded as well.
If you don't care about creating a memory leak, you could also just create unique names for your assembly (console1, console2 etc.)...
Related
We have an issue where simple c# code executed via CSharpCodeProvider doesn't work the same on running local command line and docker.
Code example is below, and it will not return any Types of the assembly when run on Roslyn, but works fine locally.
I have literally no idea how to debug this from here - any help welcome!
using System;
using Microsoft.CodeDom.Providers.DotNetCompilerPlatform;
using System.IO;
using System.Reflection;
using System.Text;
using System.CodeDom.Compiler;
namespace TestContainerIssue
{
class Program
{
static void Main(string[] args)
{
using (var codeProvider = new CSharpCodeProvider())
{
var compilerParameters = new CompilerParameters
{
GenerateExecutable = false,
GenerateInMemory = true
};
compilerParameters.CompilerOptions = String.Format("/lib:\"{0}\"", Path.GetDirectoryName(Uri.UnescapeDataString((new UriBuilder(Assembly.GetExecutingAssembly().CodeBase)).Path)));
Console.WriteLine(compilerParameters.CompilerOptions);
compilerParameters.ReferencedAssemblies.Add("System.dll");
compilerParameters.ReferencedAssemblies.Add("System.Core.dll");
CompilerResults compilerResults = codeProvider.CompileAssemblyFromSource(compilerParameters, ExecutionWrapperCode);
if (compilerResults.Errors.HasErrors)
{
StringBuilder errors = new StringBuilder();
foreach (CompilerError error in compilerResults.Errors)
errors.AppendLine(error.ErrorText);
throw new Exception(errors.ToString());
}
Console.WriteLine(compilerResults.PathToAssembly);
Assembly assembly = compilerResults.CompiledAssembly;
Console.WriteLine(assembly.FullName);
Console.WriteLine("Types:");
foreach (Type t in assembly.GetTypes())
{
Console.WriteLine(t);
}
Type type = assembly.GetType("Validation.Execution");
Console.WriteLine("Type:");
Console.WriteLine(type); //Empty when run in Docker (both mcr.microsoft.com/windows:1909 and mcr.microsoft.com/windows/servercore:ltsc2019
var methodInfo = type.GetMethod("Execute");
Console.WriteLine(methodInfo);
}
}
private const string ExecutionWrapperCode = #"
using System;
namespace Validation
{
public static class Execution
{
public static string Execute()
{
return ""test"";
}
}
}";
}
}
I tried below docker file (I tried two windows image: mcr.microsoft.com/windows:1909 and mcr.microsoft.com/windows/servercore:ltsc2019
FROM mcr.microsoft.com/windows/servercore:ltsc2019
ADD /bin/Debug /
ENTRYPOINT TestContainerIssue.exe
EDIT: I built the two dlls, and compared them in dotPeek - as you can see the one in Docker is missing the namespace. They are exactly the same bytes length though.
It turns out this line of code:
compilerParameters.CompilerOptions = String.Format("/lib:\"{0}\"", Path.GetDirectoryName(Uri.UnescapeDataString((new UriBuilder(Assembly.GetExecutingAssembly().CodeBase)).Path)));
was causing the problem. I don't know why it causes it and doesn't throw an error in compiler, but perhaps will help someone else.
Was
I am trying to create a DLL file in runtime ,as a matter of fact i need to save an encoded data to DLL .My code is like this :
class DllFile
{
public static void CreateDllFile(string source)
{
source = #"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LicensecodeDll
{
class Check
{
public string returnValue()
{
return " + source + ";" + "}}}";
var provider = new CSharpCodeProvider();
var options = new CompilerParameters
{
OutputAssembly = "test.dll"
};
var results = provider.CompileAssemblyFromSource(options, new[] { source });
}
}
}
every thing works fine and my ddl is created ,but i need to read the value that is saved on the dll file ,i mean i need the returnValue.how can i do that?
Best regards.Any ideas will be appreciated.
You can load the assembly dynamically and use reflection to call that method. The code should be like this.
Assembly a = Assembly.Load("test.dll");
Type myType = a.GetType("LicensecodeDll.Check");
MethodInfo myMethod = myType.GetMethod("returnValue");
object obj = Activator.CreateInstance(myType);
myMethod.Invoke(obj, null);
More detail is on MSDN: How to: Load Assemblies into an Application Domain
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 trying to load dll libraries during runtime using the following code so that I don't have to provide the user with lot of dll files along with the main executable file. I have inlude all the dll files as an embedded resource and also in the reference part I have include them and have set the CopyLocal property to false. But the problems here are:1. All the dll are getting copied to Bin\Debug folder2. I'm getting FileNotFoundException.I did lot of searches to get these things resolved and finally I'm here. I got a similar code here but still couldn't do anything. What should I do to prevent this exception...??
Is there a better way to do the same thing for a Windows Form Application(Not WPF)...??
using System;
using System.Linq;
using System.Windows.Forms;
using System.Diagnostics;
using System.Reflection;
using System.Collections.Generic;
using System.IO;
namespace MyNameSpace
{
static class Program
{
static int cnt;
static IDictionary<string, Assembly> assemblyDictionary;
[STAThread]
static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
if (cnt != 1)
{
cnt = 1;
Assembly executingAssembly = Assembly.GetExecutingAssembly();
string[] resources = executingAssembly.GetManifestResourceNames();
foreach (string resource in resources)
{
if (resource.EndsWith(".dll"))
{
using (Stream stream = executingAssembly.GetManifestResourceStream(resource))
{
if (stream == null)
continue;
byte[] assemblyRawBytes = new byte[stream.Length];
stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
try
{
assemblyDictionary.Add(resource, Assembly.Load(assemblyRawBytes));
}
catch (Exception ex)
{
MessageBox.Show("Failed to load: " + resource + " Exception: " + ex.Message);
}
}
}
}
Program.Main();
}
if (cnt == 1)
{
cnt = 2;
System.Threading.Thread.CurrentThread.Priority = System.Threading.ThreadPriority.Highest;
Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
AssemblyName assemblyName = new AssemblyName(args.Name);
string path = assemblyName.Name + ".dll";
if (assemblyDictionary.ContainsKey(path))
{
return assemblyDictionary[path];
}
return null;
}
}
}
If I'm using something unnecessarily in my code then you can show me the right way...
I'm a student working on Windows Form Application v4.0 project for my papers to be submitted.
If it is still the case that you must do this, then use this OnResolveAssembly method. There is no need to preload them into an array if you don't want to. This will load them the first time they are actually needed.
Then just:
add the some.assembly.dll file to the project.
probably not a reference to the project's output
but the file that is the result of the DLL project.
mark it as a Resource in the file properties.
// This function is not called if the Assembly is already previously loaded into memory.
// This function is not called if the Assembly is already in the same folder as the app.
//
private static Assembly OnResolveAssembly(object sender, ResolveEventArgs e)
{
var thisAssembly = Assembly.GetExecutingAssembly();
// Get the Name of the AssemblyFile
var assemblyName = new AssemblyName(e.Name);
var dllName = assemblyName.Name + ".dll";
// Load from Embedded Resources
var resources = thisAssembly.GetManifestResourceNames().Where(s => s.EndsWith(dllName));
if (resources.Any())
{
// 99% of cases will only have one matching item, but if you don't,
// you will have to change the logic to handle those cases.
var resourceName = resources.First();
using (var stream = thisAssembly.GetManifestResourceStream(resourceName))
{
if (stream == null) return null;
var block = new byte[stream.Length];
// Safely try to load the assembly.
try
{
stream.Read(block, 0, block.Length);
return Assembly.Load(block);
}
catch (IOException)
{
return null;
}
catch (BadImageFormatException)
{
return null;
}
}
}
// in the case the resource doesn't exist, return null.
return null;
}
-Jesse
PS: This comes from http://www.paulrohde.com/merging-a-wpf-application-into-a-single-exe/
Try the following:
For each .dll resource:
If the file allready exists on the AppDomain.Current.BaseDirectory then continue to the next resource
Else save the resource to the AppDomain.Current.BaseDirectory. Do this in a try-catch and if it fails, notify the user. For this step to complete successfully you will need write access on the installation folder (usually a subfolder of "Program Files"). This will be solved by running the program as an administrator the first time only ao that the files are written on the file system.
Ιf the assemblies are referenced by your VS project then you do not have to load them yourself. To understand why this work's you will need to understand how assemblies are located by the CLR.
Else you will need to load each assembly yourself using one of the Assembly.Load that take either a string or and AssemblyName as a parameter.
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.