Unloading the Assembly loaded with Assembly.LoadFrom() - c#

I need to check the time amount to run GetTypes() after loading the dll.
The code is as follows.
Assembly assem = Assembly.LoadFrom(file);
sw = Stopwatch.StartNew();
var types1 = assem.GetTypes();
sw.Stop();
double time1 = sw.Elapsed.TotalMilliseconds;
I'd like to unload and reload the dll to check the time to spend in running GetTypes() again.
How can I unload it? assem = null is good enough?
Is there an explicit way to call garbage collector to reclaim the resource allocated to assem?

Can you use another AppDomain?
AppDomain dom = AppDomain.CreateDomain("some");
AssemblyName assemblyName = new AssemblyName();
assemblyName.CodeBase = pathToAssembly;
Assembly assembly = dom.Load(assemblyName);
Type [] types = assembly.GetTypes();
AppDomain.Unload(dom);

Instead of using LoadFrom() or LoadFile() you can use Load with File.ReadAllBytes(). With this it does not use the assembly file but will read it and use read data.
Your code will then look like
Assembly assem = Assembly.Load(File.ReadAllBytes(filePath));
sw = Stopwatch.StartNew();
var types1 = assem.GetTypes();
sw.Stop();
double time1 = sw.Elapsed.TotalMilliseconds;
From here We cannot unload the file unless all the domains contained by it are unloaded.
Hope this helps.:)

Unfortunately you can not unload an assembly once it is loaded. But you can unload an AppDomain. What you can do is to create a new AppDomain (AppDomain.CreateDomain(...) ), load the assembly into this appdomain to work with it, and then unload the AppDomain when needed. When unloading the AppDomain, all assemblies that have been loaded will be unloaded. (See reference)
To call the garbage collector, you can use
GC.Collect(); // collects all unused memory
GC.WaitForPendingFinalizers(); // wait until GC has finished its work
GC.Collect();
GC calls the finalizers in a background thread, that's why you have to wait and call Collect() again to make sure you deleted everything.

You can't unload assembly from the current AppDomain. But you can create new AppDomain, load assemblies into it, execute some code inside new AppDomain and then unload it. Check the following link: MSDN

If you only want to load the initial assembly without any of its dependent assemblies, you can use Assembly.LoadFile in an AppDomain, and then unload the AppDomain when done.
Create a loader class to load and work with the assembly:
class Loader : MarshalByRefObject
{
public void Load(string file)
{
var assembly = Assembly.LoadFile(file);
// Do stuff with the assembly.
}
}
Run the loader in a separate app domain like this:
var domain = AppDomain.CreateDomain(nameof(Loader), AppDomain.CurrentDomain.Evidence, new AppDomainSetup { ApplicationBase = Path.GetDirectoryName(typeof(Loader).Assembly.Location) });
try {
var loader = (Loader)domain.CreateInstanceAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName);
loader.Load(myFile);
} finally {
AppDomain.Unload(domain);
}

Assembly cannot be unloaded unfortunately, and moreover - if you use appdomains - then it will prevent you to communicate with api's / assemblies of your main application.
Best description on problem can be found here:
Script Hosting Guideline
http://www.csscript.net/help/Script_hosting_guideline_.html
If you want to run C# code without communication to your main application - then best approach is to integrate C# scripting API:
https://github.com/oleg-shilo/cs-script/tree/master/Source/deployment/samples/Hosting/Legacy%20Samples/CodeDOM/Modifying%20script%20without%20restart
And for integration you will need following packages:
C# script:
http://www.csscript.net/CurrentRelease.html
Visual studio extension:
https://marketplace.visualstudio.com/items?itemName=oleg-shilo.cs-script
If however you want to communicate from your C# script to your application - then using same appDomain with assembly name constantly changing is only way at the moment - but that unfortunately eats ram and disk space.
Code sample how to do it can be done - can be found from here:
https://github.com/tapika/cppscriptcore
CsScriptHotReload.sln
And here is demo video:
https://drive.google.com/open?id=1jOECJj0_UPNdllwF4GWb5OMybWPc0PUV

Related

AppDomain.Unload does not unload assemblies

I'm trying to check some metadata of an assembly without loading it (permanently) into my app. To do this, I create a temporary sandbox AppDomain, load the assembly into it and then unload the whole sandbox. According to the answers to this question that's "correct" way to do it.
However after unloading the assembly it still remains in the current AppDomain. Why?
The answer to this question suggest that the assembly can be "bled into" the current domain, but I don't see how can this be possible in my example. The rest of the application does not use the assembly at all, it's not even referenced. The observed behavior persists even when I unload the sandobx immediately after the assembly load.
This article says that domain-neutral assemblies cannot be unloaded this way. Is that the reason? If yes, can I somehow stop the assembly from being treated as domain-neutral?
private void Check()
{
string assemblyName = "SomeUnrelatedAssembly";
var sandbox = AppDomain.CreateDomain("sandbox"); //create a discardable AppDomain
//load the assembly to the sandbox
byte[] arr;
using (var memoryStream = new MemoryStream())
{
using (var fileStream = new FileStream($"{assemblyName}.dll", FileMode.Open))
fileStream.CopyTo(memoryStream);
arr = memoryStream.ToArray();
}
Console.WriteLine(IsAssemblyLoaded(assemblyName)); //prints false
sandbox.Load(arr);
//and unload it
AppDomain.Unload(sandbox);
Console.WriteLine(IsAssemblyLoaded(assemblyName)); //prints true!
}
private static bool IsAssemblyLoaded(string assemblyName)
{
return AppDomain.CurrentDomain.GetAssemblies().Any(a => a.FullName.Contains(assemblyName));
}
EDIT: I've checked the loading process with Process Explorer (like this). The loaded assembly is NOT domain-neutral.
You are correct. Your assembly is being deemed domain-neutral and shared across app domains.
Use the AppDomain.CreateDomain overload that allows you to provide setup information:
Info:
AppDomainSetup info = new AppDomainSetup();
info.ApplicationBase = domainDir;
info.ApplicationName = executableNameNoExe;
info.LoaderOptimization = LoaderOptimization.SingleDomain;
Change:
AppDomain.CreateDomain("sandbox");
To:
AppDomain.CreateDomain("sandbox",null, info);

How do I unload and reload an assembly generated at runtime? [duplicate]

I need to check the time amount to run GetTypes() after loading the dll.
The code is as follows.
Assembly assem = Assembly.LoadFrom(file);
sw = Stopwatch.StartNew();
var types1 = assem.GetTypes();
sw.Stop();
double time1 = sw.Elapsed.TotalMilliseconds;
I'd like to unload and reload the dll to check the time to spend in running GetTypes() again.
How can I unload it? assem = null is good enough?
Is there an explicit way to call garbage collector to reclaim the resource allocated to assem?
Can you use another AppDomain?
AppDomain dom = AppDomain.CreateDomain("some");
AssemblyName assemblyName = new AssemblyName();
assemblyName.CodeBase = pathToAssembly;
Assembly assembly = dom.Load(assemblyName);
Type [] types = assembly.GetTypes();
AppDomain.Unload(dom);
Instead of using LoadFrom() or LoadFile() you can use Load with File.ReadAllBytes(). With this it does not use the assembly file but will read it and use read data.
Your code will then look like
Assembly assem = Assembly.Load(File.ReadAllBytes(filePath));
sw = Stopwatch.StartNew();
var types1 = assem.GetTypes();
sw.Stop();
double time1 = sw.Elapsed.TotalMilliseconds;
From here We cannot unload the file unless all the domains contained by it are unloaded.
Hope this helps.:)
Unfortunately you can not unload an assembly once it is loaded. But you can unload an AppDomain. What you can do is to create a new AppDomain (AppDomain.CreateDomain(...) ), load the assembly into this appdomain to work with it, and then unload the AppDomain when needed. When unloading the AppDomain, all assemblies that have been loaded will be unloaded. (See reference)
To call the garbage collector, you can use
GC.Collect(); // collects all unused memory
GC.WaitForPendingFinalizers(); // wait until GC has finished its work
GC.Collect();
GC calls the finalizers in a background thread, that's why you have to wait and call Collect() again to make sure you deleted everything.
You can't unload assembly from the current AppDomain. But you can create new AppDomain, load assemblies into it, execute some code inside new AppDomain and then unload it. Check the following link: MSDN
If you only want to load the initial assembly without any of its dependent assemblies, you can use Assembly.LoadFile in an AppDomain, and then unload the AppDomain when done.
Create a loader class to load and work with the assembly:
class Loader : MarshalByRefObject
{
public void Load(string file)
{
var assembly = Assembly.LoadFile(file);
// Do stuff with the assembly.
}
}
Run the loader in a separate app domain like this:
var domain = AppDomain.CreateDomain(nameof(Loader), AppDomain.CurrentDomain.Evidence, new AppDomainSetup { ApplicationBase = Path.GetDirectoryName(typeof(Loader).Assembly.Location) });
try {
var loader = (Loader)domain.CreateInstanceAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName);
loader.Load(myFile);
} finally {
AppDomain.Unload(domain);
}
Assembly cannot be unloaded unfortunately, and moreover - if you use appdomains - then it will prevent you to communicate with api's / assemblies of your main application.
Best description on problem can be found here:
Script Hosting Guideline
http://www.csscript.net/help/Script_hosting_guideline_.html
If you want to run C# code without communication to your main application - then best approach is to integrate C# scripting API:
https://github.com/oleg-shilo/cs-script/tree/master/Source/deployment/samples/Hosting/Legacy%20Samples/CodeDOM/Modifying%20script%20without%20restart
And for integration you will need following packages:
C# script:
http://www.csscript.net/CurrentRelease.html
Visual studio extension:
https://marketplace.visualstudio.com/items?itemName=oleg-shilo.cs-script
If however you want to communicate from your C# script to your application - then using same appDomain with assembly name constantly changing is only way at the moment - but that unfortunately eats ram and disk space.
Code sample how to do it can be done - can be found from here:
https://github.com/tapika/cppscriptcore
CsScriptHotReload.sln
And here is demo video:
https://drive.google.com/open?id=1jOECJj0_UPNdllwF4GWb5OMybWPc0PUV

Loading an assembly into an AppDomain but also in the AppDomain.CurrentDomain resulting in memory leaks

I have to execute some line which comes from a jscript into another appdomain than the current one. For this i have the following piece of code.
AppDomain ad = null;
try
{
ad = AppDomain.CreateDomain("new AD" + new Random(), null, null);
ad.CreateInstanceAndUnwrap
(
assembly,
type,
true,
BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance,
null,
args,
null,
null,
null
);
}
catch (Exception e)
{
throw new Exception("Automated script engine had an error. " + e.Message + "\n" + e.InnerException.StackTrace + "\n" + e.HelpLink);
}
finally
{
if (ad != null)
{
Assembly[] a = ad.GetAssemblies();
Console.WriteLine(a.Length);
Assembly[] mainAssemblies = AppDomain.CurrentDomain.GetAssemblies();
Console.WriteLine(mainAssemblies.Length);
AppDomain.Unload(ad);
GC.AddMemoryPressure(GC.GetTotalMemory(true));
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.Collect();
ad = null;
}
}
}
But when I inspect all assemblies loaded into the current AppDomain (via AppDomain.CurrentDomain.GetAssemblies()) my asembly is also loaded.
And as i might have to run let's say 2000 tests, i understand that 2000 assemblies will be loaded into my CurrentDomain.
Is there a way to load this assembly into an AppDomain but without adding it to the CurrentDomain ?
The method calls in the finally block are just my tries over here. Don't judge me for those if they are not useful maybe.
The culprit of the leak is:
Assembly[] a = ad.GetAssemblies();
You cannot pass assemblies from one AppDomain to another without loading them. So, although you have initially loaded the assembly into "ad", the call AppDomain.GetAssemblies will load them to the current AppDomain (not the "ad" variable).
The easiest way to overcome this problem is to add a method to your MarshalByRefObject derived class (the type variable in your sample) that returns AssemblyName objects which will not cause such a leak to the main AppDomain.
public AssemblyName[] GetAssemblyNames()
{
return AppDomain.CurrentDomain
.GetAssemblies()
.Select(asm => asm.GetName()).ToArray();
}
And instead of:
Assembly[] a = ad.GetAssemblies();
you do:
AssemblyNames[] a = someRemoteObject.GetAssemblyNames();
where someRemoteObject is the return value from the call to CreateInstanceAndUnwrap.
IIRC you can't unload an assembly in .NET 2.0, but correct me if it's possible in later versions, I didn't check that out. So, be sure it's possible because once upon a time unloading was impossible.
Second, you need 2000 different FILES to have 2000 different assemblies. Maybe you mean to load the same assembly 2000 times? In that case, you really will have only one assembly in memory.
There is a way to only load the reflection data into memory without actually loading the assembly, but this is not what you want.
The GC won't help much here. Look into test driven development for more pointers...
First you have to learn that you cannot unload any assembly when it is loaded into you appdomain.
To ensure that the assembly is not loaded in you main app domain, you need remoting.
This means your type must derive from MarshalByRefObject and must be Serializable.
The GC is not responsible to unload or load any assembly. The garbage collector ensures memeory clean up for managed memory.

Why assembly is still visible? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Unloading the Assembly loaded with Assembly.LoadFrom()
I use custom AppDomain to load/unload assembly. But when assembly is unloaded I am able to see it under the AppDomain.CurrentDomain.
How it could be? Is this normal behavior or I am missing something?
Thank you for any clue!
string assemblyPath = #"C:\MyFile.dll";
var assemblyName = AssemblyName.GetAssemblyName(assemblyPath);
var ads = new AppDomainSetup
{
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
DisallowCodeDownload = true
};
AppDomain newDomainName = AppDomain.CreateDomain("newDomainName", null, ads);
try
{
Assembly testLibrary = newDomainName.Load(assemblyName);
var c1 = AppDomain.CurrentDomain.GetAssemblies();
var c2 = newDomainName.GetAssemblies();
}
finally
{
AppDomain.Unload(newDomainName);
var c3 = AppDomain.CurrentDomain.GetAssemblies();
// The assembly is still visible here!!!
}
You are calling the Load() method of an AppDomain, which according to the documentation: "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."
In other words, you're loading the assembly into the primary AppDomain because you're calling Load() from the primary AppDomain (even though you're using calling it on an instance of your secondary AppDomain), and this is why it is appearing even after you unload your secondary AppDomain.
As indicated in the extract from the documentation above, you probably want to use AppDomain.CreateInstanceAndUnwrap.
You can't remove a loaded assembly from an app domain.
http://blogs.msdn.com/b/jasonz/archive/2004/05/31/145105.aspx
http://msdn.microsoft.com/en-us/library/ms173101(v=vs.80).aspx
There is no way to unload an individual assembly without unloading all
of the application domains that contain it. Even if the assembly goes
out of scope, the actual assembly file will remain loaded until all
application domains that contain it are unloaded.
http://blogs.msdn.com/b/suzcook/archive/2003/07/08/unloading-an-assembly.aspx
There's no way to unload an individual assembly without unloading all
of the appdomains containing it.

Swap out DLL while in use

I am building a plugin-type system with each plugin represented as a DLL. I would like to be able to reload them without stopping the main application. This means that they must be loaded at runtime, without pre-built links between them (do a filesearch for dlls and load them). I have this set up using Assembly.LoadFile(filename), however, when I try to use File.Copy to replace the DLL it throws an exception, saying something akin to 'file in use'. I have tried using AppDomain, loading all plugins through this secondary domain, and unloading it before reloading, but this throws the same exception.
My current code:
if (pluginAppDomain != null)
AppDomain.Unload(pluginAppDomain);
foreach (string s in Directory.GetFiles(path_to_new_DLLs))
{
string name = s.Substring(s.LastIndexOf('\\') + 1);
Console.WriteLine("Copying " + name);
File.Copy(s, Path.Combine(current_directory, name), true); // Throws exception here
}
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = Environment.CurrentDirectory;
setup.ShadowCopyFiles = "true";
// I think this is where the problem is, maybe I'm forgetting to set something
pluginAppDomain = AppDomain.CreateDomain("KhybotPlugin", null, setup);
foreach (String s in Directory.GetFiles(Environment.CurrentDirectory, "*.dll"))
{
int pos = s.LastIndexOf('\\') + 1;
Assembly dll = pluginAppDomain.Load(s.Substring(pos, s.Length - pos - 4));
// Elided... Load types from DLL, etc, etc
}
Generally you need to unload the AppDomain for the communication.
If you want to prevent the mentioned error you simply can load your dll by using Assembly.Load(Byte[]).
You can also use the Managed Extensibility Framework which will save you a lot of work.
Assembly.Load issue solution
Assembly loaded using Assembly.LoadFrom() on remote machine causes SecurityException
Loading plugin DLLs into another AppDomain is the only solution - so you are on the right path. Watch out for leaking object from second app domain to main one. You need to have all communication with plugins to be happening in plugin's AppDomain.
I.e. returning plugin's object to main code will likely leak plugin's Assembly usage to main AppDomain.
Start with very simple code completely in plugin's AppDomain like "load assembly and create class, but don't return anything to main Domain". Than expand usage when you get more understanding on communication between AppDomains.
Note: unless you doing it for educational purposes using existing system (i.e. MEF) is better.
You could do something like this ...
if (pluginAppDomain != null)
{
AppDomain.Unload(pluginAppDomain);
}
//for each plugin
pluginAppDomain = AppDomain.CreateDomain("Plugins Domain");
x = pluginAppDomain.CreateInstanceFromAndUnwrap("Plugin1.dll", "Namespace.Type");
You should not reference the plugins in your main app directly. Put them in separate project/s and reference them through an interface.

Categories