Creating instance of a type outside the current AppDomain

Howdy Ho! So I am came across a conundrum lately in my project where I had to create instances of a type via reflection. The scenario is, there is a Sample.dll which has a type defined in it MyType. The Sample.dll refers several other 3rd party dll’s like SomeOther1.dll and SomeCrap.dll. I have an executing assembly MyExecutingAssembly.exe residing in folder MyExecutingAssembly. The Sample.dll and its referenced files are located somewhere else in a folder called Sample.

Like a normal stupid developer, I wrote the following code to create the instance in MyExecutingAssembly.exe.

Assembly assembly = Assembly.LoadFrom(C:\Sample\Sample.dll);
Type appType = assembly.GetType(MyType);
object objClassInstance = Activator.CreateInstance(appType);

Boom! Error message for the 3rd line was something like, “Could not find assembly SomeCrap.dll, The assembly used while compiling might be different than that used while loading from …”. Blistering Barnacles, thundering typhoons… Now some straight talk,

The type MyType, resides in the Sample.dll, this dll references 2 other dlls, when the runtime tries so, it searches for the files in the folder where the MyExecutingAssembly.exe resides. This is so because the AppDomain in which we are trying to create the instance has the base directory path of the MyExecutingAssembly. Phew! Hope you get the issue here. There are several ways to tackle this,



Method 1



Create an appdomain, set its base directory to the folder where the Sample.dll and its referenced files reside. But this is some serious implication. Since we are in a separate AppDomain, we are creating the instance in a completely impenetrable environment. This asks for .NET remoting to communicate with the instance. The code would look as below,



Assembly assembly = Assembly.LoadFrom(@"C:\Sample\Sample.dll");
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = @"C:\Sample\";
AppDomain appDomain = AppDomain.CreateDomain("AppHostDomain", null, setup);
object obj = appDomain.CreateInstanceFromAndUnwrap(@"C:\Sample\Sample.dll", "SampleProxy");

Observe that the type I am creating is not the actual MyType but a class called SampleProxy which inherits from MarshalByRefObject. I create instance of the MyType inside this class. Sigh! It seems you did not like this. More information on Suzanne’s blog here.

Method 2 



Recursively check references of all the assemblies and load all of them manually in the current AppDomain. This a bit lengthy process, but, well, it works. Here is the code,



Assembly assembly = Assembly.LoadFrom(@"C:\Sample\Sample.dll");
AssemblyName[] arr = assembly.GetReferencedAssemblies();
LoadAssembly(arr);


private void LoadAssembly(AssemblyName[] arr)
{
Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
List<string> names = new List<string>();
foreach (Assembly assem in loadedAssemblies)
{
names.Add(assem.FullName);
}

foreach (AssemblyName aname in arr)
{
if (!names.Contains(aname.FullName))
{
try
{
Assembly loadedAssembly = Assembly.LoadFrom(@"C:\Sample\" + aname.Name + ".dll");
AssemblyName[] referencedAssemblies = loadedAssembly.GetReferencedAssemblies();
LoadAssembly(referencedAssemblies);
}
catch (Exception ex)
{
continue;
}
}
}
}

Tedious eh.

Method 3



Use the AppDomain’s AssemblyResolve event. Basically, whenever you are trying to load assemblies in an AppDomain, if a particular assembly is that the runtime is trying to load fails for some reason, this event is fired. We can tap into this event and load the right assembly instead. Here is the code in short,



Assembly assembly = Assembly.LoadFrom(@"C:\Sample\Sample.dll");
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
object obj = Activator.CreateInstanceFrom(@"C:\Sample\Sample.dll", "MyType");

Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string path = @"C:\Sample\Sample.dll";
string test = path.Substring(0,path.LastIndexOf(@"\"));
string[] arr = args.Name.Split(',');
string assemblyName = args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
string newPath = System.IO.Path.Combine(test, assemblyName);
return Assembly.LoadFrom(newPath);
}


Well, a little better I guess. More information on this here.

Happy Programming!

1 Comment:

Faizan Afzal said...

http://www.elisting.in/ business and personal webpages from india.

Post a Comment