I have some C# code which is using CSharpCodeProvider.CompileAssemblyFromSource to create an assembly in memory. After the assembly has been garbage collected, my application uses more memory than it did before creating the assembly. My code is in a ASP.NET web app, but I've duplicated this problem in a WinForm. I'm using System.GC.GetTotalMemory(true) and Red Gate ANTS Memory Profiler to measure the growth (about 600 bytes with the sample code).
From the searching I've done, it sounds like the leak comes from the creation of new types, not really from any objects that I'm holding references to. Some of the web pages I've found have mentioned something about AppDomain, but I don't understand. Can someone explain what's going on here and how to fix it?
Here's some sample code for leaking:
private void leak()
{
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = false;
parameters.ReferencedAssemblies.Add("system.dll");
string sourceCode = "using System;\r\n";
sourceCode += "public class HelloWord {\r\n";
sourceCode += " public HelloWord() {\r\n";
sourceCode += " Console.WriteLine(\"hello world\");\r\n";
sourceCode += " }\r\n";
sourceCode += "}\r\n";
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, sourceCode);
Assembly assembly = null;
if (!results.Errors.HasErrors)
{
assembly = results.CompiledAssembly;
}
}
Update 1: This question may be related: Dynamically loading and unloading a a dll generated using CSharpCodeProvider
Update 2: Trying to understand application domains more, I found this: What is an application domain - an explanation for .Net beginners
Update 3: To clarify, I'm looking for a solution that provides the same functionality as the code above (compiling and providing access to generated code) without leaking memory. It looks like the solution will involve creating a new AppDomain and marshaling.
I think I have a working solution. Thanks to everyone for pointing me in the right direction (I hope).
Assemblies can't be unloaded directly, but AppDomains can. I created a helper library that gets loaded in a new AppDomain and is able to compile a new assembly from code. Here's what the class in that helper library looks like:
public class CompilerRunner : MarshalByRefObject
{
private Assembly assembly = null;
public void PrintDomain()
{
Console.WriteLine("Object is executing in AppDomain \"{0}\"",
AppDomain.CurrentDomain.FriendlyName);
}
public bool Compile(string code)
{
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = false;
parameters.ReferencedAssemblies.Add("system.dll");
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, code);
if (!results.Errors.HasErrors)
{
this.assembly = results.CompiledAssembly;
}
else
{
this.assembly = null;
}
return this.assembly != null;
}
public object Run(string typeName, string methodName, object[] args)
{
Type type = this.assembly.GetType(typeName);
return type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, assembly, args);
}
}
It's very basic, but was enough for testing. PrintDomain is there to verify that it does live in my new AppDomain. Compile takes some source code and tries to create an assembly. Run lets us test executing static methods from the given source code.
Here's how I use the helper library:
static void CreateCompileAndRun()
{
AppDomain domain = AppDomain.CreateDomain("MyDomain");
CompilerRunner cr = (CompilerRunner)domain.CreateInstanceFromAndUnwrap("CompilerRunner.dll", "AppDomainCompiler.CompilerRunner");
cr.Compile("public class Hello { public static string Say() { return \"hello\"; } }");
string result = (string)cr.Run("Hello", "Say", new object[0]);
AppDomain.Unload(domain);
}
It basically creates the domain, creates an instance of my helper class (CompilerRunner), uses it to compile a new assembly (hidden), runs some code from that new assembly, and then unloads the domain to free up memory.
You'll notice the use of MarshalByRefObject and CreateInstanceFromAndUnwrap. These are important for ensuring that the helper library really does live in the new domain.
If anyone notices any problems or has suggestions for improving this, I'd love to hear them.
Unloading an assembly is not supported. Some information on why can be found here.
Some information on using an AppDomain can be found here.
You may also find this blog entry useful: Using AppDomain to Load and Unload Dynamic Assemblies. It provides some example code demonstrating how create an AppDomain, load a (dynamic) assembly into it, do some work in the new AppDomain then unload it.
Edit: fixed link as pointed out in comments below.
Can you wait until .NET 4.0? With it you can use expression trees and the DLR to dynamically generate code without the code-gen memory loss issue.
Another option is to use .NET 3.5 with a dynamic language like IronPython.
EDIT: Expression Tree Example
http://www.infoq.com/articles/expression-compiler
Related
I'm developing a module for an external application, which is a dll that is loaded.
However in order to develop, you have to restart the application to see the results of your code.
We have built a piece of code that loads a dll dynamically from a startassembly:
startassembly
var dllfile = findHighestAssembly(); // this works but omitted for clarity
Assembly asm = Assembly.LoadFrom(dllFile);
Type type = asm.GetType("Test.Program");
MethodInfo methodInfo = type.GetMethod("Run");
object[] parametersArray = new object[] { };
var result = methodInfo.Invoke(methodInfo, parametersArray);
Effectively we have a solution with a startassembly which will be static and a test assembly which will be invoked dynamically, which allows us to swap the assembly during runtime.
The problem
This piece of code will load a new dll every time and search for the highest version at the end of the assembly name. e.g. test02.dll will be loaded instead of test01.dll, because the application locks both startassemly.dll as well as test01.dll. Now we have to edit the properties > assembly name all the time.
I want to build a new dll while the main application still runs. However for now I get the message
The process cannot access the file test.dll because it is being used
by another process
I have read that you can unload a .dll using AppDomains however the problem is that I don't know how to properly unload an AppDomain and where to do this.
The goal is to have to reload the new test.dll everytime the window is re-opened (by a button click from the main application).
You cannot unload a single assembly, but you can unload an Appdomain. This means you need to create an app domain and load the assembly in the App domain.
Exmaple:
var appDomain = AppDomain.CreateDomain("MyAppDomain", null, new AppDomainSetup
{
ApplicationName = "MyAppDomain",
ShadowCopyFiles = "true",
PrivateBinPath = "MyAppDomainBin",
});
ShadowCopyFiles property will cause the .NET runtime to copy dlls in "MyAppDomainBin" folder to a cache location so as not to lock the files in that path. Instead the cached files are locked. For more information refer to article about Shadow Copying Assemblies
Now let's say you have an class you want to use in the assembly you want to unload. In your main app domain you call CreateInstanceAndUnwrap to get an instance of the object
_appDomain.CreateInstanceAndUnwrap("MyAssemblyName", "MyNameSpace.MyClass");
However, and this is very important, "Unwrap" part of CreateInstanceAndUnwrap will cause the assembly to be loaded in your main app domain if your class does not inherit from MarshalByRefObject. So basically you achieved nothing by creating an app domain.
To solve this problem, create a 3rd Assembly containing an Interface that is implemented by your class.
For example:
public interface IMyInterface
{
void DoSomething();
}
Then add reference to the assembly containing the interface in both your main application and your dynamically loaded assembly project. And have your class implement the interface, and inherit from MarshalByRefObject. Example:
public class MyClass : MarshalByRefObject, IMyInterface
{
public void DoSomething()
{
Console.WriteLine("Doing something.");
}
}
And to get a reference to your object:
var myObj = (IMyInterface)_appDomain.CreateInstanceAndUnwrap("MyAssemblyName", "MyNameSpace.MyClass");
Now you can call methods on your object, and .NET Runtime will use Remoting to forward the call to the other domain. It will use Serialization to serialize the parameters and return values to and from both domains. So make sure your classes used in parameters and return values are marked with [Serializable] Attribute. Or they can inherit from MarshalByRefObject in which case the you are passing a reference cross domains.
To have your application monitor changes to the folder, you can setup a FileSystemWatcher to monitor changes to the folder "MyAppDomainBin"
var watcher = new FileSystemWatcher(Path.GetFullPath(Path.Combine(".", "MyAppDomainBin")))
{
NotifyFilter = NotifyFilters.LastWrite,
};
watcher.EnableRaisingEvents = true;
watcher.Changed += Folder_Changed;
And in the Folder_Changed handler unload the appdomain and reload it again
private static async void Watcher_Changed(object sender, FileSystemEventArgs e)
{
Console.WriteLine("Folder changed");
AppDomain.Unload(_appDomain);
_appDomain = AppDomain.CreateDomain("MyAppDomain", null, new AppDomainSetup
{
ApplicationName = "MyAppDomain",
ShadowCopyFiles = "true",
PrivateBinPath = "MyAppDomainBin",
});
}
Then when you replace your DLL, in "MyAppDomainBin" folder, your application domain will be unloaded, and a new one will be created. Your old object references will be invalid (since they reference objects in an unloaded app domain), and you will need to create new ones.
A final note: AppDomains and .NET Remoting are not supported in .NET Core or future versions of .NET (.NET 5+). In those version, separation is achieved by creating separate processes instead of app domains. And using some sort of messaging library to communicate between processes.
Not the way forward in .NET Core 3 and .NET 5+
Some of the answers here assume working with .NET Framework. In .NET Core 3 and .NET 5+, the correct way to load assemblies (with ability to unload them) in the same process is with AssemblyLoadContext. Using AppDomain as a way to isolate assemblies is strictly for .NET Framework.
.NET Core 3 and 5+, give you two possible ways to load dynamic assemblies (and potentially unload):
Load another process and load your dynamic assemblies there. Then use an IPC messaging system of your choosing to send messages between the processes.
Use AssemblyLoadContext to load them in the same process. Note that the scope does NOT provide any kind of security isolation or boundaries within the process. In other words, code loaded in a separate context is still able to invoke other code in other contexts within the same process. If you want to isolate the code because you expect to be loading assemblies that you can't fully trust, then you need to load it in a completely separate process and rely on IPC.
An article explaining AssemblyLoadContext is here.
Plugin unloadability discussed here.
Many people who want to dynamically load DLLs are interested in the Plugin pattern. The MSDN actually covers this particular implementation here:
https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support
2021-9-12 UPDATE
Off-the-Shelf Library for Plugins
I use the following library for plugin loading. It has worked extremely well for me:
https://github.com/natemcmaster/DotNetCorePlugins
what you're trying to do in the code you posted is unload the default app domain which your program will run in if another isn't specified. What you're probably wanting is to load a new app domain, load the assembly into that new app domain, and then unloaded the new app domain when the user destroys the page.
https://learn.microsoft.com/en-us/dotnet/api/system.appdomain?view=netframework-4.7
the reference page above should give you a working example of all of this.
Here is an example for loading and unloading an AppDomain.
In my example I have 2 Dll's: DynDll.dll and DynDll1.dll.
Both Dll's have the same class DynDll.Class and a method Run (MarshalByRefObject is required):
public class Class : MarshalByRefObject
{
public int Run()
{
return 1; //DynDll1 return 2
}
}
Now you can create a dynamic AppDomain and load a Assembly:
AppDomain loDynamicDomain = null;
try
{
//FullPath to the Assembly
string lsAssemblyPath = string.Empty;
if (this.mbLoad1)
lsAssemblyPath = Path.Combine(Application.StartupPath, "DynDll1.dll");
else
lsAssemblyPath = Path.Combine(Application.StartupPath, "DynDll.dll");
this.mbLoad1 = !this.mbLoad1;
//Create a new Domain
loDynamicDomain = AppDomain.CreateDomain("DynamicDomain");
//Load an Assembly and create an instance DynDll.Class
//CreateInstanceFromAndUnwrap needs the FullPath to your Assembly
object loDynClass = loDynamicDomain.CreateInstanceFromAndUnwrap(lsAssemblyPath, "DynDll.Class");
//Methode Info Run
MethodInfo loMethodInfo = loDynClass.GetType().GetMethod("Run");
//Call Run from the instance
int lnNumber = (int)loMethodInfo.Invoke(loDynClass, new object[] { });
Console.WriteLine(lnNumber.ToString());
}
finally
{
if (loDynamicDomain != null)
AppDomain.Unload(loDynamicDomain);
}
Here is an idea, instead of loading the DDL directly (as is), let the application rename it, then load the renamed ddl (e.g. test01_active.dll). Then, just check for the original file (test01.dll) before loading the assembly and if exists, just delete the current one(test01_active.dll) and then rename the updated version then reload it, and so on.
Here is a code shows the idea :
const string assemblyDirectoryPath = "C:\\bin";
const string assemblyFileNameSuffix = "_active";
var assemblyCurrentFileName = "test01_active.dll";
var assemblyOriginalFileName = "test01.dll";
var originalFilePath = Path.Combine(assemblyDirectoryPath, assemblyOriginalFileName);
var currentFilePath = Path.Combine(assemblyDirectoryPath, assemblyCurrentFileName);
if(File.Exists(originalFilePath))
{
File.Delete(currentFilePath);
File.Move(originalFilePath, currentFilePath);
}
Assembly asm = Assembly.LoadFrom(currentFilePath);
Type type = asm.GetType("Test.Program");
MethodInfo methodInfo = type.GetMethod("Run");
object[] parametersArray = new object[] { };
var result = methodInfo.Invoke(methodInfo, parametersArray);
I need to create executable file assembly at runtime. My main goal is to compile types from the actual Project (.dll) performing compilation task, without using strings keeping the code that needs to be compiled to the output exe file. Maintaining those strings during code development/refactoring would be a nightmare. As shown in the example below, when I refactor class CodeToCompile, I also need to remember to change string codeToCompile in RuntimeCompiler.Compile() method:
using System;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
namespace RuntimeCompiler
{
public class Compiler
{
public void Compile()
{
string codeToCompile =
#"public class CodeToCompile
{
public static void Main(string[] args)
{
Console.ReadLine();
}
}";
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
ICodeCompiler icc = codeProvider.CreateCompiler();
CompilerParameters parameters = new CompilerParameters
{
GenerateExecutable = true,
OutputAssembly = "Generated.exe"
};
CompilerResults results = icc.CompileAssemblyFromSource(parameters, codeToCompile);
}
}
public class CodeToCompile
{
public static void Main(string[] args)
{
Console.ReadLine();
}
}
}
What I would like to achive is changing the string with code that needs to be compiled to reference of the type that needs to be compiled into .exe assembly.
string codeToCompile = "..."; //CodeToCompile source code
CompilerResults results = icc.CompileAssemblyFromSource(parameters, codeToCompile);
CompilerResults results = icc.CompileAssemblyFromSource(parameters, typeof(CodeToCompile));
Any ideas what would be the best way to achieve this?
I have easily found information how to dynamically compile C# code from string using System.CodeDom.Compiler namespace, as shown above. Thus any method to get a runtime object uncompiled code as a string would do the job. My research in this direction did not give me solution and I strongly doubt that it is possible to do in an easy way.
My second idea is to use mechanism to "move" compiled type from the running assembly to a newly created one, but unfortunately I also haven't found any solution to do this task.
...without using strings keeping the code that needs to be compiled to output exe file. Maintaining those strings during code development/refactoring would be a nightmare...
My advice here is to look into .NET Globalization
Globalization involves designing and developing a world-ready app that supports localized interfaces and regional data for users in multiple cultures...The handling of characters and strings is a central focus of globalization, because each culture or region may use different characters and character sets and sort them differently. More...
An alternative to CodeDom.Compiler is .NET's Reflection Emit:
...the System.Reflection.Emit namespace that allow a compiler or tool to emit metadata and Microsoft intermediate language (MSIL) at run time and optionally generate a portable executable (PE) file on disk. Script engines and compilers are the primary users of this namespace. More...
I'm experimenting with loading an assembly using just byte arrays, but I can't figure out how to get it to work properly. Here is the setup:
public static void Main()
{
PermissionSet permissions = new PermissionSet(PermissionState.None);
AppDomainSetup setup = new AppDomainSetup { ApplicationBase = Environment.CurrentDirectory };
AppDomain friendlyDomain = AppDomain.CreateDomain("Friendly", null, setup, permissions);
Byte[] primary = File.ReadAllBytes("Primary.dll_");
Byte[] dependency = File.ReadAllBytes("Dependency.dll_");
// Crashes here saying it can't find the file.
friendlyDomain.Load(dependency);
AppDomain.Unload(friendlyDomain);
Console.WriteLine("Stand successful");
Console.ReadLine();
}
I created two mock dlls, and renamed their extension to '.dll_' intentionally so the system wouldn't be able to find the physical files. Both primary and dependency fill correctly, but when I try to call the AppDomain.Load method with the binary data, it comes back with:
Could not load file or assembly 'Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
Why would it be searching the system for a file?
UPDATE
This on the other hand seems to work:
public class Program {
public static void Main() {
PermissionSet permissions = new PermissionSet(PermissionState.Unrestricted);
AppDomainSetup setup = new AppDomainSetup { ApplicationBase = Environment.CurrentDirectory };
AppDomain friendlyDomain = AppDomain.CreateDomain("Friendly", null, setup, permissions);
Byte[] primary = File.ReadAllBytes("Primary.dll_");
Byte[] dependency = File.ReadAllBytes("Dependency.dll_");
// Crashes here saying it can't find the file.
// friendlyDomain.Load(primary);
Stage stage = (Stage)friendlyDomain.CreateInstanceAndUnwrap(typeof(Stage).Assembly.FullName, typeof(Stage).FullName);
stage.LoadAssembly(dependency);
Console.WriteLine("Stand successful");
Console.ReadLine();
}
}
public class Stage : MarshalByRefObject {
public void LoadAssembly(Byte[] data) {
Assembly.Load(data);
}
}
So it appears there is a difference between AppDomain.Load and Assembly.Load.
This is normal, the CLR doesn't consider the "dependency" you loaded to be a suitable assembly when it searches for the assembly that "primary" needs. A problem associated with "loading context", there isn't one for assemblies loaded like this. This is intentional, the CLR cannot ensure that DLL Hell won't be an issue since it has no idea where the assembly came from. Since you opened the door to DLL Hell, you also have to avoid hell yourself.
You'll need to implement the AppDomain.AssemblyResolve event. It will fire when the CLR cannot find "dependency", you can return the assembly you get from Assembly.Load(byte[]). You will however have to do so consistently when it fires more than once for the same assembly, in other words return the exact same Assembly, or you'll have more problems induced by .NET type identity. Producing hard to understand casting exceptions, "can't cast Foo to Foo" style.
There are other problems, it is rather inefficient. The virtual memory for the assembly cannot be backed by a file on disk so it is backed by the paging file. Which increases the commit size for your process.
It is certainly better to not do this.
There is no difference between these two methods (you can check the official source code if you want).
In the MSDN page for AppDomain.Load Method (Byte[]) it is remarked that this method is loading the assembly in the current application domain:
This method should be used only to load an assembly into the current
application domain. This method is provided as a convenience for
interoperability callers who cannot call the static Assembly.Load
method. To load assemblies into other application domains, use a
method such as CreateInstanceAndUnwrap.
the line:
friendlyDomain.Load(dependency);
behaves exactly the same with:
Assembly.Load(dependency);
The reason it works in your updated sample code, is because the Stage object is actually calling Assembly.Load inside the child AppDomain.
Note: This answer complements the answers by Hans Passant and colinsmith.
If you use FusionLogViewer you can see more details about the particular problem the CLR is having in loading an assembly .... it can show you which locations it's trying to probe to give you a clue, etc.
http://msdn.microsoft.com/en-us/library/e74a18c4(v=vs.71).aspx
http://www.shujaat.net/2012/04/fusion-log-viewer-fuslogvw-for-assembly.html
You could also handle the AssemblyLoad / AssemblyResolve / ResourceResolve events on your AppDomain in your code, to trace through the sequence.
http://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve.aspx
This is a handy example that uses a custom MSBuild step to embed any of your project dependent assemblies as Resources into your EXE program, and then use AssemblyResolve to load them from a ResourceStream (doing Assembly.Load() on the byte[] array).
http://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application
So I was doing a bunch of research I came a across a few answers on stackoverflow and the solution I put together is as follows:
using System;
using System.Windows.Forms;
using System.Reflection;
public partial class MainForm : Form
{
private AppDomain = AppDomain.CreateDomain("asmDomain");
public MainForm()
{
InitializeComponent();
}
/// <summary>
/// Loads a Byte array as raw assmebly then loads and creates defined object from
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
[LoaderOptimizationAttribute(LoaderOptimization.MultiDomain)]
private void loadAsmObject(object sender, TileItemEventArgs e)
{
Byte[] rawAssembly = getFileAsm(); // Load the bytes however you wish.
try
{
AppDomain.Unload(appDomain);
appDomain = AppDomain.CreateDomain("asmDomain");
AppDomainBridge isolationDomainLoadContext = (AppDomainBridge)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof (AppDomainBridge).ToString());
// Form is MarshalByRefObject type for the current AppDomain
MyObject obj = isolationDomainLoadContext.ExecuteFromAssembly(rawAssembly, "MyNamespace.MyObject"/*, new object[] { "Arg1", "Arg2" } Optional args*/) as MyObject;
obj.callMethod();
}
catch (Exception Ex)
{
MessageBox.Show("Failed to load Object!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// Acts as a shared app domain so we can use AppDomain.CurrentDomain.Load without errors.
/// </summary>
private class AppDomainBridge : MarshalByRefObject
{
public Object ExecuteFromAssembly(Byte[] rawAsm, string typeName, params object[] args)
{
Assembly assembly = AppDomain.CurrentDomain.Load(rawAssembly: rawAsm);
return Activator.CreateInstance(assembly.GetType(typeName), args);
}
}
}
I am trying to create a POC with mef where i have the requirement to load dll dynamically in an all ready running project , for this i have created one console application project and a class Library
project .
the code for console application project is as follows-
namespace MefProjectExtension
{
class Program
{
DirectoryCatalog catalog = new DirectoryCatalog(#"D:\MefDll", "*.dll");
[Import("Method1", AllowDefault = true, AllowRecomposition = true)]
public Func<string> method1;
static void Main(string[] args)
{
AppDomainSetup asp = new AppDomainSetup();
asp.ShadowCopyFiles = "true";
AppDomain sp = AppDomain.CreateDomain("sp",null,asp);
string exeassembly = Assembly.GetEntryAssembly().ToString();
BaseClass p = (BaseClass)sp.CreateInstanceAndUnwrap(exeassembly, "MefProjectExtension.BaseClass");
p.run();
}
}
public class BaseClass : MarshalByRefObject
{
[Import("Method1",AllowDefault=true,AllowRecomposition=true)]
public Func<string> method1;
DirectoryCatalog catalog = new DirectoryCatalog(#"D:\MefDll", "*.dll");
public void run()
{
FileSystemWatcher sw = new FileSystemWatcher(#"D:\MefDll", "*.dll");
sw.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.Size;
sw.Changed += onchanged;
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
Console.WriteLine(this.method1());
sw.EnableRaisingEvents = true;
Console.Read();
}
void onchanged(object sender, FileSystemEventArgs e)
{
catalog.Refresh();
Console.WriteLine(this.method1());
}
}
}
the library project which satisfy import looks as follow-
namespace MefLibrary
{
public interface IMethods
{
string Method1();
}
public class CallMethods : IMethods
{
[Export("Method1")]
public string Method1()
{
return "Third6Hello";
}
}
}
once i compile the library project(MefLibrary) and put the dll in D:\MefDll location and run the console application for first time i will see the output as
Third6hello on screen
but now if i change the implementation of method1 and make it return "third7hello" build MEF Library project and replace at D:\MefDll while my console app is running the onchanged handler even after calling catalog refresh prints
Third6hello on screen rather than third7hello
Whether anyone knows what is the reason for this , if yes please help.
DirectoryCatalog.Refresh will only add new or remove existing assemblies. It will not update an assembly. A crude workaround is:
Move the updated assembly to a temp folder.
Call DirectoryCatalog.Refresh. This will remove the part(s) contained in the assembly.
Move the assembly back to the watched folder
Call DirectoryCatalog.Refresh. This will add the updated part(s) contained in the assembly.
Note:
For this to work your "plugin" assemblies have to be strong named and with different version numbers (AssemblyVersionAttribute). This is needed because when parts are removed using the DirectoryCatalog.Refresh the actual assembly will not be unloaded. Assemblies can only be unloaded when the whole application domain is unloaded. So if DirectoryCatalog.Refresh finds a new assembly it will create an AssemblyCatalog using the assembly filepath. AssemblyCatalog will then call Assembly.Load to load the assembly. But this method will not load an assembly that has the same AssemblyName.FullName with an already loaded assembly.
Make sure that the steps I mention will not trigger another FileSystemWatcher.Changed event. For example you could use this approach.
Your program will need to have write access on the watched folder. This can be a problem if you deploy in the %ProgramFiles% folder.
If you need thread-safety you can consider creating your CompositionContainer with the CompositionOption.IsThreadSafe flag.
As I mentioned this is a workaround. Another approach would be to download MEF's source code and use DirectoryCatalog.cs as a guideline for your own directory catalog implementation.
Once a dll is loaded in an app domain it can't be unloaded from that domain. Only the whole domain can be unloaded. As such it is not easy to implement what you are after. It is possible to constantly scan for the changes and load new copies and repoint the calls (you will be accumulating more and more useless assemblies in your domain this way), but I don't believe this is something that MEF implements out of the box. In other words the behaviour you are observing is by design.
The implementation of this can be also tricky and bug prone because of state. Imagine you set a filed in a class instance of the old DLL and assign it to a variable. Then the new dll comes through. What happens to the old instance and its fields? Apparently they will stay the same and now you have different version of your plug-in in use in memory. What a mess!
And in case you are interested here is the reason why there isn't an Assembly.Unload method. And possible (conceptual) workaround.
I read a LOT of things mostly via google (not here on SO) and didn't find something that answer to my question so I'm asking it here.
What I do want to add to "something" (to an AppDomain I think) so that my code can resolve AT RUNTIME how to Assembly.CreateInstance a specific DLL that is outside of my application's compilation folder.
I really feel like AppDomain is the class to use and AppendPrivatePath sounded like the method to use but it now is "Obsolete"...
msdn suggest to use PrivateBinPath but as far as I understood I have to create a new AppDomain and with my tests, I feel like Assembly.CreateInstance doesn't look for references in my new AppDomain
Some code like :
AppDomainSetup domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
domaininfo.PrivateBinPath = "D:\\.....\\bin\\Debug\\";
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain newDomain = AppDomain.CreateDomain("FrameworkDomain", adevidence, domaininfo);
works, but then, when I try to CreateInstance, I got a TargetInvocation exception
I, too, tried :
Thread.GetDomain().SetupInformation.PrivateBinPath = "D:\\.....\\bin\\Debug\\";
which sounds "special" but good to me, but it doesn't work...
I really feel like I HAVE TO give to D:\\.....\\bin\\Debug\\ path to the current AppDomain but its no longer possible since AppendPrivatePath is Obsolete...
Any help ?
You can resolve additional DLLs yourself on an event the assembly fires to let you know that it cannot find a type.
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += MyResolve;
AppDomain.CurrentDomain.AssemblyResolve += MyResolve;
private Assembly MyResolve(Object sender, ResolveEventArgs e) {
Console.Error.WriteLine("Resolving assembly {0}", e.Name);
// Load the assembly from your private path, and return the result
}
I might be totally of the track what you are actually trying to build but assuming that you want to build a plugin infrastructure where you want to organize plugins in subfolders like this:
\plugins\foo\foo.dll
\plugins\foo\dependency_that_foo_needs.dll
\plugins\bar\bar.dll
\plugins\bar\dependency_that_bar_needs.dll
The problem that you encountered is that that foo.dll fails to load its dependency because the framework does not look in that subdirectory for the assembly to load.
Using the latest preview of the Managed Extensibility Framework (MEF 2 Preview) one can build such things really easy. I assume you could even do that with an earlier version, however earlier versions forced you to use special attributes on your plugins that the most recent version does not depend on any longer.
So, assume that is your plugin contract:
public interface IPlugin
{
string Run();
}
To load and run all plugins in \plugins\foo\, \plugins\bar\, (...) just use this code:
class Program
{
static void Main(string[] args)
{
var registration = new RegistrationBuilder();
registration.ForTypesDerivedFrom<IPlugin>().Export().Export<IPlugin>();
var directoryInfo = new DirectoryInfo("./plugins");
var directoryCatalogs = directoryInfo
.GetDirectories("*", SearchOption.AllDirectories)
.Select(dir => new DirectoryCatalog(dir.FullName, registration));
var aggregateCatalog = new AggregateCatalog(directoryCatalogs);
var container = new CompositionContainer(aggregateCatalog);
container
.GetExportedValues<IPlugin>()
.ForEach(plugin => Console.WriteLine(plugin.Run()));
Console.ReadLine();
}
}
As I said, I might be totally of the track but chances are also that someone looking for an alternative finds it useful :-)
How about using
Assembly.LoadFrom("..path)
You could add an EventHandler to the Appdomains AssemblyResolve event and then load your assemblies form your special private path.