I made a script tool using the .NET integrated compiler.
Shortly, at runtime it compiles a source file to a assembly, creates a object defined in the assembly and starts a method of the object.
Here's the code (heavily simplified):
CodeDomProvider provider = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = false;
cp.GenerateInMemory = true;
// ...
CompilerResults res = provider.CompileAssemblyFromSource(cp, source);
// ...
return res.CompiledAssembly;
This can happen multiple times during the tool is running.
So far so good, it's working great.
Problem:
The compiled assembly is loaded into my process, the debugger tells me this:
'ScriptTool.exe' (Managed): Loaded 'v7wyfy7w', No symbols loaded.
This happens every time i 'run' a script. So after a while, there are a lot of assemblies loaded into the process, killing memory.
Now the question is:
Is it possible to unload the assemblies?
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'm compiling a C# assembly at runtime within my program using CSharpCodeProvider, which depends on a few libraries which are built as .dlls, and also the program that's building it.
I'd ideally like to only build one executable, and not have to copy around all the dependencies with it.
Here's the code I'm using to compile the assembly:
//Create the compiler (with arguments).
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = true;
cp.OutputAssembly = "example.exe";
cp.GenerateInMemory = false;
//Reference the main assembly (this one) when compiling.
Assembly entryasm = Assembly.GetEntryAssembly();
cp.ReferencedAssemblies.Add(entryasm.Location);
//Reference an external assembly it depends on.
cp.ReferencedAssemblies.Add("someExternal.dll");
//Attempt to compile.
CompilerResults results = provider.CompileAssemblyFromSource(cp, someScript);
The executable this produces still needs someExternal.dll and the program that built it in the same directory when run, and I'd prefer it to be a single executable with all dependencies included.
Is there any way to do this?
In the end, I ended up using the ILRepack.Lib NuGet package for the job, which allows you to merge binaries programmatically, without using a command line tool.
Here's the code I'm using to pack all .dll files in the directory into the executable:
//Setting required options.
RepackOptions opt = new RepackOptions();
opt.OutputFile = "example_packed.exe";
opt.SearchDirectories = new string[] { AppDomain.CurrentDomain.BaseDirectory, Environment.CurrentDirectory };
//Setting input assemblies.
string[] files = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
opt.InputAssemblies = new string[] { "example.exe", entryasm.Location }.Concat(files).ToArray();
//Merging.
ILRepack pack = new ILRepack(opt);
pack.Repack();
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 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.
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 .