I'm experimenting with loading an assembly using just byte arrays, but I can't figure out how to get it to work properly. Here is the setup:
public static void Main()
{
PermissionSet permissions = new PermissionSet(PermissionState.None);
AppDomainSetup setup = new AppDomainSetup { ApplicationBase = Environment.CurrentDirectory };
AppDomain friendlyDomain = AppDomain.CreateDomain("Friendly", null, setup, permissions);
Byte[] primary = File.ReadAllBytes("Primary.dll_");
Byte[] dependency = File.ReadAllBytes("Dependency.dll_");
// Crashes here saying it can't find the file.
friendlyDomain.Load(dependency);
AppDomain.Unload(friendlyDomain);
Console.WriteLine("Stand successful");
Console.ReadLine();
}
I created two mock dlls, and renamed their extension to '.dll_' intentionally so the system wouldn't be able to find the physical files. Both primary
and dependency
fill correctly, but when I try to call the AppDomain.Load
method with the binary data, it comes back with:
Could not load file or assembly 'Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
Why would it be searching the system for a file?
UPDATE
This on the other hand seems to work:
public class Program {
public static void Main() {
PermissionSet permissions = new PermissionSet(PermissionState.Unrestricted);
AppDomainSetup setup = new AppDomainSetup { ApplicationBase = Environment.CurrentDirectory };
AppDomain friendlyDomain = AppDomain.CreateDomain("Friendly", null, setup, permissions);
Byte[] primary = File.ReadAllBytes("Primary.dll_");
Byte[] dependency = File.ReadAllBytes("Dependency.dll_");
// Crashes here saying it can't find the file.
// friendlyDomain.Load(primary);
Stage stage = (Stage)friendlyDomain.CreateInstanceAndUnwrap(typeof(Stage).Assembly.FullName, typeof(Stage).FullName);
stage.LoadAssembly(dependency);
Console.WriteLine("Stand successful");
Console.ReadLine();
}
}
public class Stage : MarshalByRefObject {
public void LoadAssembly(Byte[] data) {
Assembly.Load(data);
}
}
So it appears there is a difference between AppDomain.Load
and Assembly.Load
.
This is normal, the CLR doesn't consider the "dependency" you loaded to be a suitable assembly when it searches for the assembly that "primary" needs. A problem associated with "loading context", there isn't one for assemblies loaded like this. This is intentional, the CLR cannot ensure that DLL Hell won't be an issue since it has no idea where the assembly came from. Since you opened the door to DLL Hell, you also have to avoid hell yourself.
You'll need to implement the AppDomain.AssemblyResolve event. It will fire when the CLR cannot find "dependency", you can return the assembly you get from Assembly.Load(byte[]). You will however have to do so consistently when it fires more than once for the same assembly, in other words return the exact same Assembly, or you'll have more problems induced by .NET type identity. Producing hard to understand casting exceptions, "can't cast Foo to Foo" style.
There are other problems, it is rather inefficient. The virtual memory for the assembly cannot be backed by a file on disk so it is backed by the paging file. Which increases the commit size for your process.
It is certainly better to not do this.
There is no difference between these two methods (you can check the official source code if you want).
In the MSDN page for AppDomain.Load Method (Byte[]) it is remarked that this method is loading the assembly in the current application domain:
This method 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.
the line:
friendlyDomain.Load(dependency);
behaves exactly the same with:
Assembly.Load(dependency);
The reason it works in your updated sample code, is because the Stage
object is actually calling Assembly.Load
inside the child AppDomain.
Note: This answer complements the answers by Hans Passant and colinsmith.
If you use FusionLogViewer
you can see more details about the particular problem the CLR is having in loading an assembly .... it can show you which locations it's trying to probe to give you a clue, etc.
- http://msdn.microsoft.com/en-us/library/e74a18c4(v=vs.71).aspx
- http://www.shujaat.net/2012/04/fusion-log-viewer-fuslogvw-for-assembly.html
You could also handle the AssemblyLoad / AssemblyResolve / ResourceResolve events on your AppDomain
in your code, to trace through the sequence.
- http://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve.aspx
This is a handy example that uses a custom MSBuild step to embed any of your project dependent assemblies as Resources into your EXE program, and then use AssemblyResolve
to load them from a ResourceStream
(doing Assembly.Load()
on the byte[] array).
- http://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application