Before implementing runtime assembly resolution, it's important to understand how methods are loaded at runtime.
When a method is first called, that method is JITted, or "Just-In-Time Compiled". Essentially, all the IL code for that method is loaded and then compiled into CPU instructions. Any types that are used within that method are also resolved when the method is JIT compiled, in order to ensure that the entire method is "safe" for execution. Essentially, what this means to Assembly Resolution using the AppDomain.AssemblyResolve event is that the attachment to the event handler cannot reside within the same method that calls the type that will have to be resolved using the event handler. The reason for this is simple... the event handler must be attached before the type is resolved. If the event handler is attached within the same method, then the JIT compiler will attempt to resolve the type before executing the first instruction in the method, including the instruction to attach the event handler to the AssemblyResolve event, causing the whole application to fail.
Thus, a good example of attaching an assembly at runtime would be the following. The Widget class is the class to load, and it does not reside within the same directory as the executable. Instead, it resides in a directory that is only identified with a relative path. This relative path can be searched at runtime to find the specific assembly that is needed to load.
using System;
using System.IO;
using System.Reflection;
using ClassLibrary1;
namespace ConsoleApplication6
{
internal class Program
{
private const string ResolutionPath = @"..\..\..\ClassLibrary1\bin\debug\";
private static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainAssemblyResolve;
RunWidget();
}
private static void RunWidget()
{
var widget = new WidgetInOtherAssembly();
Console.WriteLine(widget);
}
private static Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
{
Assembly[] currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < currentAssemblies.Length; i++)
{
if (currentAssembliesi.FullName == args.Name)
{
return currentAssembliesi;
}
}
return FindAssembliesInDirectory(args.Name, ResolutionPath);
}
private static Assembly FindAssembliesInDirectory(string assemblyName, string directory)
{
foreach (string file in Directory.GetFiles(directory))
{
Assembly assm;
if (TryLoadAssemblyFromFile(file, assemblyName, out assm))
return assm;
}
return null;
}
private static bool TryLoadAssemblyFromFile(string file, string assemblyName, out Assembly assm)
{
try
{
// Convert the filename into an absolute file name for
// use with LoadFile.
file = new FileInfo(file).FullName;
if (AssemblyName.GetAssemblyName(file).FullName == assemblyName)
{
assm = Assembly.LoadFile(file);
return true;
}
}
catch
{
/* Do Nothing */
}
assm = null;
return false;
}
}
}
Changes To ResolveEventArgs in .NET 4.0
In .NET 4.0, the ResolveEventArgs class has a new property called "RequestingAssembly", which returns the name of the assembly requesting resolution. This has been done in order to enable the ability to execute specific code based on which assembly was requesting the resolution.
You can see more information on these changes here.


4 comments:
Hmm..good one..
Ultimately, your code loads the assembly into the current AppDomain. What happens if you load from a secondary AppDomain? I can't seem to get the event to plumb back to the current AppDomain, where the eventhandler code resides.
Something like:
Byte[] assm = my assembly that is not a file;
AppDomain second = AppDomain.CreateDomain("second");
second.AssemblyResolve += MyStaticResolveEventHandler;
\\ cross a method boundary by calling something like TryLoad(AppDomain second, Byte[] assm)
second.Load(assm);
\\ catch the FileNotFoundException when trying to load a dependent assembly
The MyResolveEventHandler never gets invoked.
gd
After couple hours of search regarding the safe issue as the person above,
Problem loading byte[] to a new appdomain
I came up with a solution.
First, I made a new assembly and it has one class with the following code
public sealed class AppDomainHelper : MarshalByRefObject
{
public void LoadAssembly(byte[] assembly)
{
Assembly.Load(assembly);
}
}
Next thing I did was on the primary appdomain
_newAppDomain.Load("AppDomainHelper");
ObjectHandle handle = _newAppDomain.CreateInstance("AppDomainHelper", "AppDomainHelper.AppDomainHelper");
_appDomainHelper = handle.Unwrap() as AppDomainHelper.AppDomainHelper;
_appDomainHelper .Load(somebytes)
BAM!
Thanks mate, helped me out, saved writing code for reading assemblies from another directory.
thanks!
Post a Comment