AppDomain shadow copy - Loading /Unloading a dynamically loaded Dll - c#

The Code as below which i'm trying to load a dll dynamically is not working.
AppDomain appDomain = AppDomain.CreateDomain("DllDomain");
Assembly a = appDomain.Load(fileName);
//Assembly a = Assembly.LoadFrom(fileName);
objType = a.GetType(className);
obj = a.CreateInstance(className);
object[] args = new object[1];
args[0]=(object) "test";
object ret = objType.InvokeMember("Perform", BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, args);
string output = ret.ToString();
obj = null;
AppDomain.Unload(appDomain);
this is the code i am using inside a WCF service but still it does not work.
Heard that we can acheive using 'Shadow Copying' in AppDomain. But i dont know anything about 'Shadow Copying' and how to implement the same in the above code.
Please provide working code as example for 'Shadow Copying'.
-B.S.

You can load assemblies into an application domain but you cannot unload them from that domain.
However, in one application domain you can create a second application domain and load an assembly into the second application domain. Later you can then choose to unload the second application domain which in turn unloads the assembly that you loaded into the second application domain.
This is the basic principle. In practice you will find a number of obstacles (they changed through the versions of .NET) to be resolved in particular when you set up some form of communication between the application domains.
Providing working code here would probably be too big in size.

Related

How to unload assembly created by CSharpCodeProvider?

Problem
CSharpCodeProvider can be used to compile source .cs files into an assembly.
However, the assembly is automatically loaded into the AppDomain.CurrentDomain by default. In my case, this is a problem because I need to be able to re-compile the assembly again during runtime, and since it's already loaded in the CurrentDomain, I can't unload that, so I'm stuck.
I have looked through the docs and there seems to be no way to set the target app domain. I have also tried searching it on Google and only found answers where Assembly.Load was used, which I don't think I can use because I need to compile from raw source code, not a .dll
How would one go about doing this? Are there any alternatives or workarounds?
Main program
using (var provider = new CSharpCodeProvider())
{
param.OutputAssembly = "myCompiledMod"
var classFileNames = new DirectoryInfo("C:/sourceCode").GetFiles("*.cs", SearchOption.AllDirectories).Select(fi => fi.FullName).ToArray();
CompilerResults result = provider.CompileAssemblyFromFile(param, classFileNames);
Assembly newAssembly = result.CompiledAssembly // The assembly is already in AppDomain.CurrentDomain!
// If you try compile again, you'll get an error; that class Test already exists
}
C:/sourceCode/test.cs
public class Test {}
What I tried already
I already tried creating a new AppDomain and loading it in there. What happens is the assembly ends up being loaded in both domains.
// <snip>compile code</snip>
Evidence ev = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("NewDomain", ev);
domain.Load(newAssembly);
The answer was to use CSharpCodeProvider().CreateCompiler() instead of just CSharpCodeProvider, and to set param.GenerateInMemory to false. Now I'm able to see line numbers and no visible assembly .dll files are being created, and especially not being locked. This allows for keeping an assembly in memory and reloading it when needed.

Load assembly to temporary Appdomain from another directory

As a part of BaaS project, I need to dynamically execute a code from "guest" assembly. I've tried all the examples and answers in other similar questions including AppDomainToolKit approach, but got no luck.
Similar with the plugin approach, I got positive result by copying related assemblies into host application's bin path. However, this is not possible for the current scenario:
permissions and restrictions needs to be applied per request
each request should be evaluated in a temporary appdomain
load the required assembles and referenced types from a path to temporary domain
So far, my latest piece of code is below
// ClassLibrary1.dll and ClassLibrary2.dll are in the same directory, both are marked as Serializable
var binPath = #"C:\AssemblyDemo\ClassLibrary1\bin\Debug\";
var lib1Path = binPath + "ClassLibrary1.dll";
var lib2Path = binPath + "ClassLibrary2.dll";
var setup = new AppDomainSetup();
setup.ApplicationBase = binPath;
AppDomain domain = AppDomain.CreateDomain("domainname", null, setup);
ObjectHandle handle = domain.CreateInstanceFrom(lib1Path, "ClassLibrary1.Class1");
var unwrap = handle.Unwrap();
var m1 = unwrap.GetType().GetMethod("Method1");
var result = m1.Invoke(unwrap, null);
handle.Unwrap() throws exception Type is not resolved for member 'ClassLibrary1.Class1,ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
If you want to load the assembly only in the temporary AppDomain you cannot call Unwrap in the main domain. Unwrap loads the type there as well. You must move all access to objects from the temporary assembly to the temporary AppDomain. You can do this using AppDomain.DoCallBack or a custom MarshalByRef class that you instantiate in the child domain and call from the parent domain.

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