A few days ago while developing an application I faced a very interesting issue, where I was unable to unload an assembly loaded dynamically using reflection. I am using Visual Studio 2008 and .Net 3.5. I searched the internet and somewhere I got the hint that I should load the assembly in another appdomain and create an instance there, use it and then unload the appdomain. I have done that but surprisingly even after that the issue was not resolved and more surprisingly after the appdomain was unloaded, when I tried to invoke the function from a loaded assembly, the function has been called successfully. Now it's more annoying for me. I was hitting my head. After moving around a lot on the internet I found a way out of this.
Now here I want to give the solution for such issues and will tell you how to unload the assembly that has been loaded dynamically using reflection using appdomains, with the simple code snippets.
Major obstacles:
If the developer simply creates a new appdomain, as in:
AppDomain newDomain = AppDomain.CreateDomain("NewDomain");]
Then create an instance and get an assembly object as in:
System.Runtime.Remoting.ObjectHandle obj = newDomain.CreateInstanceFrom({Path}, {Type});
object instance = obj.Unwrap();
Now when the developers use the newdomain.CreateIntance method, it returns a wrapped instance of the assembly from the new appdomain. This not only loads the assembly into a new appdomain but also loads the assembly into the current appdomain. After that when the user calls the appdomain.unload method it unloads the appdomain but the assembly that is loaded into the current appdomain remains as it is.
Now there is a solution for this problem. The solution is creating the object in a new appdomain only so that the scope of the object should be in the new appdomain only and does not let the object touch the current appdomain.
To implement this we will be following the following steps: (please read carefully, it might be confusing for new developers)
-
Create a class say class "B" derived by MarshalByRefObject
-
Write a method LoadAssembly({AssemblyPath}) in class "B" that will take the assembly path as a parameter. This function will only load that assembly and return void.
-
Write another method ExecuteMethod({MethodName, Params}) in class "B". This method will take MethodName and Params as Parameters.
This will execute the method (name passed in the parameter) of the loaded assembly.
-
Create a new class say class "A". This will be our main class from where we will start and execute the code.
-
In class A write a main method which will load class B in the new appdomain, and then call class B methods to load the assembly by passing the loadassembly method by passing assembly path and then by calling ExecuteMethod by passing method name and parameters.
Ok friends enough theory; now let's get our hands on the code.
//Creating a new appdomain
AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
AppDomain newDomain = AppDomain.CreateDomain("newDomain", AppDomain.CurrentDomain.Evidence, setup); //Create an instance of loader class in new appdomain
System.Runtime.Remoting.ObjectHandle obj = newDomain.CreateInstance(typeof(LoadMyAssembly).Assembly.FullName, typeof(LoadMyAssembly).FullName);
LoadMyAssembly loader = (LoadMyAssembly)obj.Unwrap();//As the object we are creating is from another appdomain hence we will get that object in wrapped format and hence in next step we have unwrappped it
//Call loadassembly method so that the assembly will be loaded into the new appdomain amd the object will also remain in new appdomain only.
loader.LoadAssembly(StrPath);
//Call exceuteMethod and pass the name of the method from assembly and the parameters.
loader.ExecuteStaticMethod(strModule, "MyMethod", new object[] {"girish", "girish });
AppDomain.Unload(newDomain); //After the method has been executed call unload method of the appdomain.
//Wow you have unloaded the new appdomain and also unloaded the loaded assembly from memory.
Here we complete the code in our main class and now here is the LoadeMyAssembly class.
class LoadeMyAssembly : MarshalByRefObject
{
private Assembly _assembly;
System.Type MyType = null;
object inst = null;
public override object InitializeLifetimeService()
{
return null;
}
public void LoadAssembly(string path)
{
_assembly = Assembly.Load(AssemblyName.GetAssemblyName(path));
}
public object ExecuteStaticMethod(string strModule, string methodName, params object[] parameters)
{
foreach (System.Type type in _assembly.GetTypes())
{
if (String.Compare(type.Name, "MyClass", true) == 0)
{
MyType = type;
inst = _assembly.CreateInstance(type.FullName);
break;
}
}
MethodInfo MyMethod = MyType.GetMethod(methodName, new Type[] { typeof(int), typeof(string), typeof(string), typeof(string) });
MyMethod.Invoke(inst, BindingFlags.InvokeMethod, null, parameters, null);
return null;
}
}