I need to compile C# code at run-time. I'm using the code like this:
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("MyLibrary.dll"); // File Path on Hard Drive
...
But I want to use the libraries loaded on memory instead of their file addresses. Is it possible?
If it is an assembly that isn't generated in-memory only, you could use:
parameters.ReferencedAssemblies.Add
( typeof(ClassInAssemblyYouWantToAdd).Assembly.Location
);
Or:
parameters.ReferencedAssemblies.Add
( Assembly.Load("Full.Qualified.Assembly.Name").Location
);
The Location property has the path to the assembly loaded.
It has to have a hard copy of the assembly, and not just something in memory, so you can't just use generated assemblies for that. You could save the in-memory generated assemblies to disk first if you need to use them.
Related
Problem
CSharpCodeProvider can be used to compile source .cs files into an assembly.
However, the assembly is automatically loaded into the AppDomain.CurrentDomain by default. In my case, this is a problem because I need to be able to re-compile the assembly again during runtime, and since it's already loaded in the CurrentDomain, I can't unload that, so I'm stuck.
I have looked through the docs and there seems to be no way to set the target app domain. I have also tried searching it on Google and only found answers where Assembly.Load was used, which I don't think I can use because I need to compile from raw source code, not a .dll
How would one go about doing this? Are there any alternatives or workarounds?
Main program
using (var provider = new CSharpCodeProvider())
{
param.OutputAssembly = "myCompiledMod"
var classFileNames = new DirectoryInfo("C:/sourceCode").GetFiles("*.cs", SearchOption.AllDirectories).Select(fi => fi.FullName).ToArray();
CompilerResults result = provider.CompileAssemblyFromFile(param, classFileNames);
Assembly newAssembly = result.CompiledAssembly // The assembly is already in AppDomain.CurrentDomain!
// If you try compile again, you'll get an error; that class Test already exists
}
C:/sourceCode/test.cs
public class Test {}
What I tried already
I already tried creating a new AppDomain and loading it in there. What happens is the assembly ends up being loaded in both domains.
// <snip>compile code</snip>
Evidence ev = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("NewDomain", ev);
domain.Load(newAssembly);
The answer was to use CSharpCodeProvider().CreateCompiler() instead of just CSharpCodeProvider, and to set param.GenerateInMemory to false. Now I'm able to see line numbers and no visible assembly .dll files are being created, and especially not being locked. This allows for keeping an assembly in memory and reloading it when needed.
I am aware of a class called AssemblyBuilder, and I would have thought I could use it to pass a folder containing C# source files, or pass a single C# source file to it in order to compile the source into an assembly (.dll) which can then be referenced in a config file.
I'm aware of csc.exe which can compile C#, and I'm effectively looking for a way to replicate this dynamically.
I couldn't figure out how to use AssemblyBuilder, or whether this is the wrong class to be using, or whether I should be doing something similar to the following:
http://support.microsoft.com/kb/304655
Can you point me in the right direction please.
You might want to look into CodeDomProvider
Example snippet:
CompilerParameters parms = new CompilerParameters
{
GenerateExecutable = false,
GenerateInMemory = true,
IncludeDebugInformation = false
};
parms.ReferencedAssemblies.Add("System.dll");
parms.ReferencedAssemblies.Add("System.Data.dll");
CodeDomProvider compiler = CSharpCodeProvider.CreateProvider("CSharp");
return compiler.CompileAssemblyFromSource(parms, source);
Warning: assemblies built dynamically in this fashion won't be handled by the garbage collector.
Suppose Project Main has a reference to Project Ref. In Main, I have defined a CSharpCodeProvider and use it to compile code at runtime.
var provider = new CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v4.0" } });
var parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.dll");
// Rest of the referenced assemblies.
The code which is compiled at runtime, might require a newer version of Project Ref to run correctly. So I tried to add the new Ref.Dll in a relative subfolder (plugins):
parameters.ReferencedAssemblies.Add(#"d:\project-output-path\plugins\Ref.dll");
I have also added the following:
AppDomain.CurrentDomain.AppendPrivatePath("plugins");
Problem is when I try to compile the script dynamically, the Ref.dll in the main folder is being used and causes error.
So, What would be the best way to reference the new Ref project only for my script?
P.S. I really prefer not having to create another AppDomain since the dynamically executing code is coupled with the code loaded in current AppDomain and cannot be separated.
I can execute a C# source from PowerShell and a PowerShell source from C#.
The question is, How can I execute a C# source from a C# program without compiling with csc.exe?
Yes. This is explicitly catered for in the .net framework using the CodeDom class namespace. http://msdn.microsoft.com/en-us/library/650ax5cx(v=vs.110).aspx System.CodeDom and System.CodeDom.Compiler.
(from the documention)
CSharpCodeProvider provider = new CSharpCodeProvider();
// Build the parameters for source compilation.
CompilerParameters cp = new CompilerParameters();
// Add an assembly reference.
cp.ReferencedAssemblies.Add( "System.dll" );
// Generate an executable instead of
// a class library.
cp.GenerateExecutable = true;
// Set the assembly file name to generate.
cp.OutputAssembly = exeFile;
// Save the assembly as a physical file.
cp.GenerateInMemory = false;
// Invoke compilation.
CompilerResults cr = provider.CompileAssemblyFromFile(cp, sourceFile);
I realise this does use the compiler internally, which is something the OP wished to avoid, but I can't see any reason not to use this to .
I am having difficulty with using reflections dynamically eg. query a .exe file without requiring a reference to be added for every assembly which I wish to query against.
So for instance, the code below is the regular way to get a hold of a class to then be checked.
AssemblyName assembly_name = new AssemblyName( "Name" );
The issue is not adding the argument in to the code but the code requirng direct reference to the new assembly to check against.
Any suggestions are welcome.
It sounds like you're really just trying to load an assembly at execution time. Look at Assembly.Load and Assembly.ReflectionOnlyLoad.
Maybe you're looking for something like Cecil. It's a library (available on Windows and other platforms) that allows to query metadata without the need to resolve all references.
I'm not really sure what you mean by "query". If you want to know how to create an instance from an assembly using reflection, here is an example:
// From within the current assembly
public CartesianType CreateInstance(string fullyQualifiedClassName)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Type target = assembly.GetType(fullyQualifiedClassName, true, true);
return (CartesianType)Activator.CreateInstance(target);
}
// From an external assembly already referenced in your project
public SomeClass CreateInstance(string fullyQualifiedClassName)
{
Assembly assembly = Assembly.GetAssembly(typeof(SomeClass));
Type target = assembly.GetType(fullyQualifiedClassName, true, true);
return (SomeClass)Activator.CreateInstance(target);
}
All other methods must use Load or LoadFile, LoadFrom etc.