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);
}
}
}
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 have an in-memory assembly MyAssembly (class library) that is used in my main assembly MyApp.exe:
byte[] assemblyData = GetAssemblyDataFromSomewhere();
(For testing, the GetAssemblyDataFromSomewhere method can just do File.ReadAllBytes for an existing assembly file, but in my real app there is no file.)
MyAssembly has only .NET Framework references and has no dependencies to any other user code.
I can load this assembly into the current (default) AppDomain:
Assembly.Load(assemblyData);
// this works
var obj = Activator.CreateInstance("MyAssembly", "MyNamespace.MyType").Unwrap();
Now, I want to load this assembly into a different AppDomain and instantiate the class there. MyNamespace.MyType is derived from MarshalByRefObject, so I can share the instance across the app domains.
var newAppDomain = AppDomain.CreateDomain("DifferentAppDomain");
// this doesn't really work...
newAppDomain.Load(assemblyData);
// ...because this throws a FileNotFoundException
var obj = newAppDomain.CreateInstanceAndUnwrap("MyAssembly", "MyNamespace.MyType");
Yes, I know there is a note in the AppDomain.Load docs:
This method should be used only to load an assembly into the current application domain.
Yes, it should be used for that, but...
If the current AppDomain object represents application domain A, and the Load method is called from application domain B, the assembly is loaded into both application domains.
I can live with that. There's no problem for me if the assembly will be loaded into both app domains (because I actually load it into the default app domain anyway).
I can see that assembly loaded into the new app domain. Kind of.
var assemblies = newAppDomain.GetAssemblies().Select(a => a.GetName().Name);
Console.WriteLine(string.Join("\r\n", assemblies));
This gives me:
mscorlib
MyAssembly
But trying to instantiate the class always leads to a FileNotFoundException, because the CLR tries to load the assembly from file (despite it is already loaded, at least according to AppDomain.GetAssemblies).
I could do this in MyApp.exe:
newAppDomain.AssemblyResolve += CustomResolver;
private static Assembly CustomResolver(object sender, ResolveEventArgs e)
{
byte[] assemblyData = GetAssemblyDataFromSomewhere();
return Assembly.Load(assemblyData);
}
This works, but this causes the second app domain to load the calling assembly (MyApp.exe) from file. It happens because that app domain now needs the code (the CustomResolver method) form the calling assembly.
I could move the app domain creation logic and the event handler into a different assembly, e.g. MyAppServices.dll, so the new app domain will load that assembly instead of MyApp.exe.
However, I want to avoid the file system access to my app's directory at any cost: the new app domain must not load any user assemblies from files.
I also tried AppDomain.DefineDynamicAssembly, but that did't work either, because the return value's type System.Reflection.Emit.AssemblyBuilder is neither MarshalByRefObject nor marked with [Serializable].
Is there any way to load an assembly from byte array into a non-default AppDomain without loading the calling assembly from file into that app domain? Actually, without any file system access to my app's directory?
You first problem is the way you load the assembly into the second AppDomain.
You need some type loaded / shared between both AppDomains. You can't load assembly into the second AppDomain from the first AppDomain if the assembly is not already loaded into the first AppDomain (it also won't work if you load the assembly bytes into the first AppDomain uisng .Load(...)).
This should be a good starting point:
Lets say i have class library named Models with single class Person as follows:
namespace Models
{
public class Person : MarshalByRefObject
{
public void SayHelloFromAppDomain()
{
Console.WriteLine($"Hello from {AppDomain.CurrentDomain.FriendlyName}");
}
}
}
and console application as follows (the Models class library is NOT references from the project)
namespace ConsoleApp
{
internal class Program
{
[LoaderOptimizationAttribute(LoaderOptimization.MultiDomain)]
public static void Main(String[] args)
{
CrossAppDomain();
}
private static Byte[] ReadAssemblyRaw()
{
// read Models class library raw bytes
}
private static void CrossAppDomain()
{
var bytes = ReadAssemblyRaw();
var isolationDomain = AppDomain.CreateDomain("Isolation App Domain");
var isolationDomainLoadContext = (AppDomainBridge)isolationDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, "ConsoleApp.AppDomainBridge");
// person is MarshalByRefObject type for the current AppDomain
var person = isolationDomainLoadContext.ExecuteFromAssembly(bytes);
}
}
public class AppDomainBridge : MarshalByRefObject
{
public Object ExecuteFromAssembly(Byte[] raw)
{
var assembly = AppDomain.CurrentDomain.Load(rawAssembly: raw);
dynamic person = assembly.CreateInstance("Models.Person");
person.SayHelloFromAppDomain();
return person;
}
}
}
The way it works is by creating instance of the AppDomainBridge from the ConsoleApp project which is loaded into both AppDomains. Now this instance is living into the second AppDomain. Then you can use the AppDomainBridge instance to actually load the assembly into the second AppDomain and skipping anything to do with the first AppDomain.
This is the output of the console when i execute the code (.NET Framework 4.7.2), so the Person instance is living in the second AppDomain:
Your second problem is sharing instances between AppDomains.
The main problem between AppDomains sharing the same code is the need to share the same JIT compiled code (method tables, type information ... etc).
From docs.microsoft:
JIT-compiled code cannot be shared for assemblies loaded into the
load-from context, using the LoadFrom method of the Assembly class, or
loaded from images using overloads of the Load method that specify
byte arrays.
So you won't be able to fully share the type information when you load assmebly from bytes, which means your object at this point is just MarshalByRefObject for the first AppDomain. This means that you can execute and access methods / properties only from the MarshalByRefObject type (it does not matter if you try to use dynamic / reflection - the first AppDomain does not have the type information of the instance).
What you can do is not to return the object from ExecuteFromAssembly, but to extend the AppDomainBridge class to be simple wrapper around the created Person instance and use it to delegate any method execution from the first AppDomain to the second if you really need it for those purposes.
I'm not quite sure what are you trying to achieve, but I would try the following.
In general, your approach seems OK. You have to make sure your probing paths (especially, the appbase path) for the secondary appdomain are set correctly. Otherwise, .NET Fusion will probe these locations for dependencies, and you'll have those unwanted file system access attempts, you're trying to avoid. (Well, at least make sure that these paths are configured to some temp folders with no real permissions set up).
PROPOSED SOLUTION
In any case, you can try adding to your dynamic (is this how I should call it?) assembly an entry point (ex. Main method in some Bootstrap class), and
try calling AppDomain.ExecuteAssemblyByName, after loading the assembly into the secondary AppDomain.
I would add to the Bootstrap class your CustomResolver method, and in the Main method, I would subscribe to AssemblyResolve.
This way, when the Main method is called (and hopefully it works as expected), the subscription to AppDomain's AssemblyResolve won't trigger fusion.
I didn't test this solution, and it could be a long shot, but worse trying.
P.S.:
I do see that documentation on this method does state, that the runtime will try to load the assembly first (by probably using regular probing logic), but it doesn't say anything about situation in which the assembly was pre-loaded into the AppDomain before the call was made.
Remarks
The ExecuteAssemblyByName method provides similar functionality to the ExecuteAssembly method, but specifies the assembly by display name or AssemblyName rather than by file location. Therefore, ExecuteAssemblyByName loads assemblies with the Load method rather than with the LoadFile method.
The assembly begins executing at the entry point specified in the .NET Framework header.
This method does not create a new process or application domain, and it does not execute the entry point method on a new thread.
Load method documentation doesn't provide a clear answer either.
P.P.S:
Calling the Unwrap method, may trigger the fusion in your main AppDomain, since a proxy is created for your class. I would think, that at this point your main AppDomain would try to locate that dynamically loaded assembly. Are you sure it's the secondary AppDomain that throws the exception?
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've found a lot of similar questions but couldn't find any solution.
I have following code:
string file = "c:\\abc.dll";
AppDomainSetup ads = new AppDomainSetup();
ads.PrivateBinPath = Path.GetDirectoryName(file);
AppDomain ad2 = AppDomain.CreateDomain("AD2", null, ads);
ProxyDomain proxy = (ProxyDomain)ad2.CreateInstanceAndUnwrap(typeof(ProxyDomain).Assembly.FullName, typeof(ProxyDomain).FullName);
Assembly asm = proxy.GetAssembly(file); // exception File.IO assembly not found is thrown here after succesfully running the funktion GetAssembly.
public class ProxyDomain : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
Assembly asm = Assembly.LoadFile(assemblyPath);
//...asm is initialized correctly, I can see everything with debugger
return asm;
}
catch
{
return null;
}
}
}
The most Interesting thing that then my GetAssembly funktion returns some other type, even my custom serializable class, everything is fine. Does someone know what I'm missing? Or It's just impossible to return loaded assembly to another domain?
Thank you
I imagine File.IO is sitting in your main application's bin directory? If so, your abc.dll will not know where to find it (unless your main application is also sitting in C:\\).
You need to do one of
Bind to the AppDomain.AssemblyResolve event and manually load the referenced dll
Change the AppDomainSetup's base directory (which is one of the places .NET knows to look for dlls)
Install File.IO to the GAC (which is another one of the places .NET knows to look for dlls)
Add the location of File.IO to your AppDomainSetup's private probing path (which is another one of the places .NET will try to look for dlls).
We implement a plugin framework for our application and load plugin assemblies using Assembly.Loadfrom. We then use GetTypes() and further examine the types with each plugin file for supported Interfaces.
A path for the plugins is provided by the user and we cycle through each of the files in the folder to see if it (the plugin) supports our plugin interface. If it does, we create an instance, if not we move onto the next file.
We build two versions of software from the one code base (appA_1 and appA_2).
Loading the plugins works well when the plugins are loaded by the application that was built at the same time as the plugin file. However if we build appA_2 and point to the plugin folder of appA_1, we get an exception when GetTypes() is called.
A basic version of our code is;
var pluginAssembly = Assembly.LoadFrom(FileName);
foreach (var pluginType in pluginAssembly.GetTypes())
{
We get a "ReflectionTypeLoadException" exception.
This is concerning because we want our application to be able to load the types of any plugin, built by anyone. Is there something we are missing?
EDIT:
After iterating through the LoaderExceptions we have discovered that there is a single file libPublic.dll that generates a System.IO.FileNotFoundException exception. The strange thing is that this file resides in the application directory and the plugin is referenced to the project file.
EDIT 2:
In the exception log we find the following
"Comparing the assembly name resulted in the mismatch: Revision Number"
A few things:
Make sure you don't have duplicate assemblies in the plugin directory (i.e. assemblies that you're already loading in your main app from your app directory.) Otherwise, when you load your plugin, it may load an additional copy of the same assembly. This can lead to fun exceptions like:
Object (of type 'MyObject') is not of type 'MyObject'.
If you're getting the exception when instantiating a type, you may need to handle AppDomain.AssemblyResolve:
private void App_Startup(object sender, StartupEventArgs e)
{
// Since we'll be dynamically loading assemblies at runtime,
// we need to add an appropriate resolution path
// Otherwise weird things like failing to instantiate TypeConverters will happen
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var domain = (AppDomain) sender;
foreach (var assembly in domain.GetAssemblies())
{
if (assembly.FullName == args.Name)
{
return assembly;
}
}
return null;
}
I realize it's a bit strange to have to tell the CLR that, in order to resolve an assembly, find the assembly with the name we're using to resolve, but I've seen odd things happen without it. For example, I could instantiate types from a plugin assembly, but if I tried to use TypeDescriptor.GetConverter, it wouldn't find the TypeConverter for the class, even though it could see the Converter attribute on the class.
Looking at your edits, this is probably not what's causing your current exception, though you may run into these issues later as you work with your plugins.
Thanks to this post I could solve the ReflectionTypeLoadException that I was getting in a UITypeEditor. It's a designer assembly (a winforms smart-tag used at design-time) of a custom class library, that scan for some types.
/// <summary>
/// Get the types defined in the RootComponent.
/// </summary>
private List<Type> getAssemblyTypes(IServiceProvider provider)
{
var types = new List<Type>();
try
{
IDesignerHost host = (IDesignerHost)provider.GetService(typeof(IDesignerHost));
ITypeResolutionService resolution = (ITypeResolutionService)provider.GetService(typeof(ITypeResolutionService));
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
foreach (var assembly in ((AppDomain)sender).GetAssemblies())
{
if (assembly.FullName == args.Name)
{
return assembly;
}
}
return null;
};
Type rootComponentType = resolution.GetType(host.RootComponentClassName, false);
types = rootComponentType.Assembly.GetTypes().ToList();
}
catch
{
}
return types;
}
You are getting an assembly version mismatch. Since your plugins refer to this libPublic.dll, you must version it carefully and in particular not bump its revision/build/etc. numbers at every compile.