Currently I'm compiling and loading an assembly at runtime. The assembly contains always the same namespaces and classes. When I'm doing this
multiple times in the same application instance, does always the newest assembly will be used, when creating new instances of the classes which are in the assembly? Or is this not guaranteed?
You're creating exactly what you're trying to create. This may or may not be what you want to create, though.
Most likely, your assemblies don't have a specific name, but rather, a randomized unique name - in that case, the types are entirely different and only accidentally similar as far as .NET is concerned. Types from two different compilations are entirely unrelated, and are not compatible. This may or may not be a problem when you're accessing them through an interface defined outside of the dynamic assembly, depending on how exactly you're using the types.
If you add an assembly name, the situation gets a bit more complicated. You can't load the same assembly twice (the old one is not replaced by the new one), so you need to change the version. However, two versions of the same assembly cannot be loaded in the same application domain (except when doing "fun" with AssemblyResolve etc. - but that's quite tricky to get right). The second assembly would simply fail to load.
In the end, the Type you're trying to instantiate is the one you do instantiate (barring the use of binding redirects, which are bonus fun :P). If some piece of your code holds on to a Type from a previous compilation, that's what it's going to create.
If your question is if I load an assembly in AppDomain
Assembly a1=Assembly.Load(Array of Assembly);
And then change code with roslyn like class name and create new assembly of your project and load it again
Assembly a2 =Assembly.Load(Array of Assembly);
Now is a2 is loaded in CurrentDomain ?
My answer is no .a1 is now in CurrentDomain.
You can test it .
So for work with new assembly you have to use below solution.
You need to load this assembly in another AppDomain and every time you can Unload this AppDomain and create it again and load assembly again
First create a class that CurrentDomain will load instance of that to another AppDomain this object of class must load your assembly and it's dependencies to second AppDomain .
// you can create this class in another project and
// make assembly .because you need a copy of it in
//folder that you set for ApplicationBase of second AppDomain
public class AssemblyLoader : MarshallByRefObject
{
AssemblyLoader()
{
AppDomain.CurrentAppDomain.AssemblyResolve += LoaddependencyOfAssembly;
}
public void LoaddependencyOfAssembly(object sender,)
{
//load depdency of your assembly here
// if you has replaced those dependencies to folder that you set for ApplicationBase of second AppDomain doesn't need do anything here
}
public Assembly asm {get;set;}
public void LoadAssembly(MemoryStream ms)
{
asm= Assembly.Load(ms.ToArray());
}
}
in where you want to load assembly
AppDomainSetup s=new AppDomainSetup(){ApplicationBase="anotherFolderFromYourAppBinFoldr};
AppDomain ad= AppDomain.CreateDomain("name",AppDomain.CurrentDomain.Evidence,s);
Type t = typeof( AssemblyLoader);
AssemblyLoader al = ( AssemblyLoader) ad.CreateInstanceAndUnwrap(t.Assembly.FullName,t.FullName);
// load assembly here by Stream or fileName
al.LoadAssembly(ms );
// now assembly loaded on ad
// you can do your work with this assembly by al
// for example create a method in AssemblyLoader to
// get il of methods with name of them
// Below IL is in CurrentDomain
//when compiler goes to GetIlAsByteArray you are in second AppDomain
byte[] IL = al.GetILAsByteArray("NameOfMethod");
//And unload second AppDomain
AppDomain.Unload(ad);
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'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).
I am trying to host a text template class proxy inside a new AppDomain.
I have some old scripting code that does something similar, that contains this working code:
_ScriptAppDomain = AppDomain.CreateDomain(scriptDomainFriendlyName);
_ScriptProxy = (IScriptEngineProxy)_ScriptAppDomain.CreateInstanceAndUnwrap(
Assembly.GetExecutingAssembly().FullName,
"LVK.Scripting.ScriptEngineProxy");
However, when I try this with my new class, with the following
_TemplateDomain = AppDomain.CreateDomain(templateDomainFriendlyName);
_TemplateProxy = (ITemplateProxy)_TemplateDomain.CreateInstanceAndUnwrap(
Assembly.GetExecutingAssembly().FullName,
"TextTemplate.TemplateProxy");
I just get "FileNotFoundException", with the following details:
Could not load file or assembly 'TextTemplate, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bb70a2e62a722ace' or one of its dependencies. The system cannot find the file specified.
What am I missing?
Basically, I have a Template class in the TextTemplate namespace (and assembly), which tries to load a TemplateProxy class (descending from MarshalByRefObject) into the new appdomain, but it appears my main assembly is not loaded into this domain.
This works if I use the older code, but not with this new one, but I can't spot the difference.
Here's some more details:
Assembly is not registered with the GAC (neither was the old one, which works)
I have not overridden any AssemblyResolve event (neither did the old one, which works)
I'm not averse to handling the AssemblyResolve event, if that is what is needed. I just found it odd that my old code worked, and this didn't.
Assumying your assembly is in the same directory as your current application base, try specifying Application Base:
AppDomain.CreateDomain(templateDomainFriendlyName, null,
new AppDomainSetup
{
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
});
If you don't know what you are doing, the best way to create a new domain is to copy all settings from the current one, like this:
var newDomain = AppDomain.CreateDomain("NAME",
AppDomain.CurrentDomain.Evidence,
AppDomain.CurrentDomain.SetupInformation);
I had similar issues, and making a copy resolved them for me
I'm making a little tool to analyze code dependencies recursively. I found a problem: if I try to get a member of a class whose signature contains a reference to another dll the method fails. For example, if I have a simple class in Main.exe
public class MainClass {
public MainClass () {
foo();
}
public ContainedClass GetPublicClass () {
return new ContainedClass ();
}
}
and ContainedClass is defined in other file refers.dll, when I try the following code it throw a FileNotFoundException in met3.ReturnType() method cause .net not find refers.dll.
Assembly assem = Assembly.LoadFile(#"D:\dir\Main.exe");
Type typ = assem.GetType ("MultipleReference.MainClass");
MethodInfo met3 = typ.GetMethod ("GetPublicClass");
met3.ReturnType.ToString ();
Is there any way to indicate where to search the dll?
Thanks in advance and sorry for my English.
Use LoadFrom instead of LoadFile because it will also resolve and load dependent assemblies. Quoting the documentation:
Use the LoadFile method to load and
examine assemblies that have the same
identity, but are located in different
paths. LoadFile does not load files
into the LoadFrom context, and does
not resolve dependencies using the
load path, as the LoadFrom method
does. LoadFile is useful in this
limited scenario because LoadFrom
cannot be used to load assemblies that
have the same identities but different
paths; it will load only the first
such assembly.