While investigating a dependency
resolution error the other day I came across an interesting situation. Fuslogvw logs indicated that even though the proper native
image was present for a managed assembly, it wouldn't get loaded due to the
actual load context being a so called LoadFrom context. This was not the main
issue I started to investigate, but after figuring that out I decided to
conduct some research on load contexts.
After that I felt ready to reproduce the issue with a small console application and a class library it tries to load. I find it very useful to abstract away from the production environment and reproduce issues in a very simple, controlled one, so that you can focus only on the problem being investigated.
ConsoleApplication1.exe =>
After that I felt ready to reproduce the issue with a small console application and a class library it tries to load. I find it very useful to abstract away from the production environment and reproduce issues in a very simple, controlled one, so that you can focus only on the problem being investigated.
ConsoleApplication1.exe =>
static void Main(string[] args)
{
var asm = Assembly.LoadFrom(@"c:\temp\dllfolder\ClassLibrary1.dll");
}
|
ClassLibrary1.dll => Just an empty dll
After building the binaries for - let's say- Release x64, deploy them to separate folders and run the appropriate ngen.exe to generate the native image for the dll, i.e.
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ngen.exe
install c:\temp\dllfolder\ClassLibrary1.dll
|
After all these steps, the following relevant files / folders could be observed :
- c:\temp\exefolder\ConsoleApplication1.exe
- c:\temp\dllfolder\ClassLibrary1.dll
- c:\Windows\assembly\NativeImages_v4.0.30319_64\ClassLibrary1\b1086c3d9614cbfd539aa5cfb53ae3c9\ClassLibrary1.ni.dll
Note: ClassLibrary1.dll was not present
in the managed GAC
At this point I enabled fuslogvw logging then started ConsoleApplication1.exe. Understanding the logs takes some time, so don't be frustrated if you don't put all the pieces together within a few seconds. I highlighted the most important log entries.
At this point I enabled fuslogvw logging then started ConsoleApplication1.exe. Understanding the logs takes some time, so don't be frustrated if you don't put all the pieces together within a few seconds. I highlighted the most important log entries.
LOG: This bind starts
in LoadFrom load context.
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: Using application configuration file: c:\temp\exefolder\ConsoleApplication1.exe.Config LOG: Using host configuration file: LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config. LOG: Attempting download of new URL file:///c:/temp/dllfolder/ClassLibrary1.dll. LOG: Assembly download was successful. Attempting setup of file: c:\temp\dllfolder\ClassLibrary1.dll LOG: Entering run-from-source setup phase. LOG: Assembly Name is: ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b91bc0d0c196a74e LOG: Re-apply policy for where-ref bind. LOG: Post-policy reference: ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b91bc0d0c196a74e LOG: GAC Lookup was unsuccessful. LOG: Where-ref bind Codebase does not match what is found in default context. Keep the result in LoadFrom context. LOG: Binding succeeds. Returns assembly from file:///c:/temp/dllfolder/ClassLibrary1.dll. LOG: Assembly is loaded in LoadFrom load context. ... 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\dllfolder\ClassLibrary1.dll. |
See the warning about not probing the native image in case of a LoadFrom context? This can be a bit misleading, but first things first. As GAC lookup fails, the binder leaves the context as is and binds to the managed dll. However, things get changed as soon as you GAC your dll.
gacutil /i
c:\temp\dllfolder\ClassLibrary1.dll
|
That deploys the dll to the following folder:
c:\Windows\Microsoft.NET\assembly\GAC_64\ClassLibrary1\v4.0_1.0.0.0__b91bc0d0c196a74e\ClassLibrary1.dll
LOG: This bind starts in
LoadFrom load context.
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: Using application configuration file: c:\temp\exefolder\ConsoleApplication1.exe.Config LOG: Using host configuration file: LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config. LOG: Attempting download of new URL file:///c:/temp/dllfolder/ClassLibrary1.dll. LOG: Assembly download was successful. Attempting setup of file: c:\temp\dllfolder\ClassLibrary1.dll LOG: Entering run-from-source setup phase. LOG: Assembly Name is: ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b91bc0d0c196a74e LOG: Re-apply policy for where-ref bind. LOG: Post-policy reference: ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b91bc0d0c196a74e LOG: Found assembly by looking in the GAC. LOG: Switch from LoadFrom context to default context. LOG: Binding succeeds. Returns assembly from C:\windows\Microsoft.Net\assembly\GAC_64\ClassLibrary1\v4.0_1.0.0.0__b91bc0d0c196a74e\ClassLibrary1.dll. LOG: Assembly is loaded in default load context. ... 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_64\ClassLibrary1\b1086c3d9614cbfd539aa5cfb53ae3c9\ClassLibrary1.ni.dll. Native image successfully used. |
As you can see the load context was
"promoted" to default due to the assembly also residing in the GAC.
This causes the binder to look for the native image counterpart of the dll even
though several clear logs indicated otherwise. As a result, the native image
gets loaded into the address space of the process.
Uninstalling the native image by
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ngen.exe
uninstall ClassLibrary1
|
of course once again results in only
the managed dll being loaded.
...
WRN: Native image will not be probed in
LoadFrom context. Native image will only be probed in default load context,
like with Assembly.Load().
WRN: No matching native image found. LOG: IL assembly loaded from C:\windows\Microsoft.Net\assembly\GAC_64\ClassLibrary1\v4.0_1.0.0.0__b91bc0d0c196a74e\ClassLibrary1.dll. |
That speaks for itself.
In some cases however, the situation is not so obvious, i.e. some component may hide the details of assembly loading. A good example is MEF, as composing your CompositionContainer might lead to either the default or a LoadFrom load context. Typically, if the dll you're trying to load is not installed to GAC, then you'll have a LoadFrom load context resulting in the native images not getting loaded.
A nice tool to verify that your native images get loaded properly is VMMap from Sysinternals, but there are tons of others that can do the same.
Figure 1. VMMap showing the loaded images |
You might want to look for such issues before your release.
References
https://blogs.msdn.microsoft.com/abhinaba/2014/02/18/net-ngen-explicit-loads-and-load-context-promotion/
https://blogs.msdn.microsoft.com/junfeng/2005/05/31/assemblies-in-loadfrom-context-cant-be-domain-neutral-and-cant-use-ngen/
Best Practices for Assembly Loading
How the Runtime Locates Assemblies
https://blogs.msdn.microsoft.com/junfeng/2005/05/31/assemblies-in-loadfrom-context-cant-be-domain-neutral-and-cant-use-ngen/
Best Practices for Assembly Loading
How the Runtime Locates Assemblies
No comments:
Post a Comment