Sunset over the Pacific

If you want to know the conclusion and want to skip the details jump to the end for the climax :). If you care to see this feature in, please vote for this at http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/5194915-ngen-should-support-explicit-path-based-assembly-l

Load-Context

In my previous post on how NGEN loads Native images I mentioned that NGEN images are supported only in the default load context. Essentially there are 3 load contexts (excluding Reflection-only context) and based on how you load an assembly it lands in one of those 3 contexts. You can read more about the load contexts at http://blogs.msdn.com/b/suzcook/archive/2003/05/29/57143.aspx. However for our purposes
  1. Default context: This is the context where assembly loaded through implicit assembly references or Assembly.Load(…) call lands
  2. LoadFrom context is where assemblies loaded with Assembly.LoadFrom call is placed
  3. Null-context or neither context is where assemblies loaded with Assembly.LoadFile, reflection-emit (among other APIs) are placed.
Even though a lot of people view the contexts only in the light of how they impact searching of assembly dependencies, they have other critical impact. E.g. native images of an assembly (generated via NGEN) is only loaded if that assembly is loaded in the default context.

#1 and #3 are pretty simple to understand. If you use Assemby.Load or if your assembly has other implicit assembly dependency then for those assemblies NativeBinder will search for their native images. If you try to load an assembly through Assembly.LoadFile(“c:\foo\some.dll”) then it will be loaded in null-context and will definitely not get native image support. Things get weird for #2 (LoadFrom).

Experiments

Lets see an simple example where I have an executable loadfrom.exe which has the following call
Assembly assem = Assembly.LoadFrom(@"c:\temp\some.dll");
some.dll has been NGEN’d as

c:\temp>ngen install some.dll
Microsoft (R) CLR Native Image Generator - Version 4.0.30319.17929
Copyright (c) Microsoft Corporation. All rights reserved.
1> Compiling assembly c:\temp\some.dll (CLR v4.0.30319) ...

Now we run the loadfrom.exe as follows
c:\temp>loadfrom.exe
Starting
Got assembly
In the the fusion log I can see among others the following messages

WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load().
LOG: Start validating all the dependencies.
LOG: [Level 1]Start validating native image dependency mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.
Native image has correct version information.
LOG: Validation of dependencies succeeded.
LOG: Bind to native image succeeded.
Attempting to use native image C:\Windows\assembly\NativeImages_v4.0.30319_32\some\804627b300f73759069f96bac51811a0\some.ni.dll.
Native image successfully used.


Interestingly the native image was loaded for some.dll even though it was loaded using Assembly.LoadFrom. This was done in spite of loader clearly warning in the log that it will not attempt to load the native image.

Now lets trying running this same program just ensuring that the exe and dll is not in the same folder

c:\temp>copy loadfrom.exe ..
1 file(s) copied.

c:\temp>cd ..
c:\>loadfrom.exe
Starting
Got assembly
In this case the log says something different

WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load().
LOG: IL assembly loaded from c:\temp\some.dll.

As you can see the NI image was not loaded.

The reason is Load Context promotion. When LoadFrom is used on a path from which a Load would’ve anyway found an assembly the LoadFrom results in loading the assembly in the default context. Or the load-context is promoted to the default context. In our first example since c:\temp\some.dll was on the applications base path (APPBASE) the load landed in default-context and ni was loaded. The same didn’t happen in the second example.

Conclusion

  1. NGEN images is only supported on the default context. E.g. for Assemblies loaded for implicit references or through Assemb.Load() API call
  2. NGEN images is not supported on explicit-loads done via Assembly.LoadFile(path)
  3. NGEN images is not reliably supported on explicit-loads done via Assembly.LoadFrom(path)
Given the above there is no real way to load Assemblies from arbitrary paths and get NGEN native image support. In the modern programming world a lot of large applications are moving away from the traditional GAC based approach to a more plug-in based, loosely-coupled-components approach. These large application locate it’s plug-ins via its own proprietary probing logic and loads them using one of the explicit path based load mechanisms. For these there is no way to get the performance boost based on native images. I think this is a limitation which CLR needs to address in the future.