Monday, August 10, 2009

Create objects in remote/other AppDomains + unload assemblies

Hi there...
Today I wanted know how can I unload an assembly. After some Google/Bing/MSDN search, I realized that unloading an assembly is possible only and if only you create a new AppDomain, load the assembly in it and then Unload the AppDomain.

Some articles were about domain.CreateInstanceAndUnwrap(assemblyName, typeName) but the result was always the same: the assembly was loaded in the primary application domain. If you want to rebuild the assembly/project you will find out that it's not possible because the assembly was loaded in the main application domain and you will get and Error 2: file is being used by another process. But there's a trick.

In .NET Framework you can comunicate with others AppDomains using .NET Remoting. The comunication can be done using IPC Remoting (if you have a server and a client) or using the CrossAppDomainDelegate.

This blog entry will conver the case when you want to create a new AppDomain and create some objects in it without stoping the process that has the main application domain.

First of all, you will have to inherit your cross appdomain objects from MarshalByRefObject.
namespace ClassLibrary1
{
public class Class1 : MarshalByRefObject
{
public object MyProperty
{
set
{
object a = value;
Console.WriteLine(string.Format("Remote AppDomain ID: {0}", AppDomain.CurrentDomain.Id));
}
}
}
}

After that, you will have to create a new AppDomain next to your main AppDomain.
static void Main(string[] args)
{
Console.WriteLine(string.Format("Local AppDomain ID: {0}{1}", AppDomain.CurrentDomain.Id, Environment.NewLine));

while (Console.ReadKey().Key != ConsoleKey.Escape)
{
Console.WriteLine("Load in other AppDomain... ");
LoadInOtherAppDomain();
Console.WriteLine("Done... Press ESC to exit!");
}
}

private static void LoadInOtherAppDomain()
{
try
{
//create AppDomain
AppDomain domain = AppDomain.CreateDomain("MyTestDomain");
domain.DoCallBack(new CrossAppDomainDelegate(LoadAssembly));

//unload domain
AppDomain.Unload(domain);
domain = null;
}
catch
{
throw;
}
}

In the LoadAssembly method, you will have to create a new assembly, load the assembly in the newly created AppDomain, find the type of the object that you want to instantiate find methods/properties and call them.

static void LoadAssembly()
{
Assembly assembly = Assembly.LoadFrom("ClassLibrary1.dll");
AppDomain.CurrentDomain.Load(assembly.FullName);

Type t = Array.Find(assembly.GetTypes(), ts => ts.FullName == "ClassLibrary1.Class1");

if(t == null)
throw new NullReferenceException("Unable to get type!");

//create instance
object instance = Activator.CreateInstance(t);

if(instance == null)
throw new NullReferenceException("Unable to create an instance!");
//get MyProperty property
PropertyInfo p = instance.GetType().GetProperty("MyProperty");

if (p == null)
throw new NullReferenceException("Unable to get MyProperty property!");

//Set value to the remote property
p.SetValue(instance, new List<object>, null);n
}

You will see that the result is:
Local AppDomain ID: 1

Load in other AppDomain...
Remote AppDomain ID: 2
Done... Press ESC to exit!
Load in other AppDomain...
Remote AppDomain ID: 3
Done... Press ESC to exit!
Load in other AppDomain...
Remote AppDomain ID: 4
Done... Press ESC to exit!

Well, that's about it...

The nice thing here is that you won't have to stop the process to debug/rebuild the library/assembly that is dynamically loaded! :)

Nice, right?

Happy coding...

No comments: