Sunday, May 22, 2005

Assembly Loading

When I load an assembly what happens?


Assembly.Load() calls into mscorwks.dll which in turn calls into fusion.dll (both unmanaged modules). Fusion locates and returns the assembly.
The loader then maps the assembly, its data structures etc and creates a managed instance of the assembly which is returned back to the caller.
If the assembly cannot be found the loader raises an AppDomain.AssemblyResolve event and Load() throws a FileNotFound exception.




Static and Dynamic Binding.

There are two sorts of assembly bind. Static and dynamic. A static bind is generated by the compiler. The assembly reference information is recorded in the metadata of the calling assembly at compile time. An example of a fully qualified reference would be "com.microsoft.crypto, Culture=en, PublicKeyToken=a5d015c7d5a0b012, Version=1.0.0.0".
A dynamic bind is a call to Load, LoadFrom or an implicit load from an attempt to create a type in the assembly.




Loading the assembly.

The Loader checks to see if the assembly is loaded, if so it uses the loaded assembly.

If the assembly reference includes a strong name the Loader determines the correct assembly version by checking the configuration files; app.config, any publisher configuration file and the machine.config.

The Loader then calls into Fusion which always looks in the GAC first if the assembly reference includes a strong name. If the assembly is there, this is the assembly that will be returned.

If a codebase is found, the runtime checks only this location. If this probe fails, the binding request fails at this point.

If the assembly bind has not been resolved at this point, the CLR uses the following set of heuristics to locate the assembly. This is known as Probing.

When using Assembly.Load() the calling assembly's codebase string is used as a hint about the location of the referenced assembly.

The AppDomain Application base then any subdirectories are searched.

When an assembly is located.
The culture attribute of the assembly reference is compared
The name is compared.
The privatePath attribute of the element in the app.config is examined.

If probing locates an assembly it is then returned.

At this point if no assembly has been located the runtime requests Windows Installer to provide the assembly.
Failing this the loader throws a FileNotFoundException.



Assembly Loading Hell During Development


The Visual Studio defaults are not there for nothing.

The AssemblyInfo.cs file has an automatically incrementing version number. Make it non-auto incrementing at your peril.

Visual Studio creates a static reference in the assembly metadata when you add a reference.

The reference within Visual Studio appears to include the path where the dependent assembly is at the point the reference is added. This path is not added to the static reference in the assembly metadata. Each time you compile the project for the calling assembly, Visual Studio copies the dependent assembly to the calling assembly application base.

Assemblies can be stored in the download cache by the runtime. It is not uncommon to make code changes only to discover that the changes do not appear in the behaviour of the code. A quick look at the loaded modules in Visual Studio will reveal that instead of using the assemblies with your code changes, the assemblies copied locally and loaded are in a location deep in a hierarchy of randomly named folders in the local settings application data of the user profile. What has happened is that the runtime has stored them in the download cache.

To force clear the download cache run gacutil /cdl from the VS.NET command prompt.
To view the contents of the download cache run gacutil /ldl
To stop it from happening again leave your AssemblyInfo.cs autoincrementing during Development. This ensures that only the new version can be loaded and the download cache is updated with it.

COM Interop

Having a regularly changing version number can break COM Interop. The version number is maintained as part of the reference added by regasm in the registry, so changing the version number and not subsequently running regasm will return the class not registered exception.

To ensure that the registry is updated, set the "Register for COM Interop" property in the project properties to true. This will run regasm after each build and update the version number.

When working with other Developers or another team ensure they are aware of the version numbers in your assemblies if they have changed.

Don't put any code in the constructor of a COM activated class that may fail. The same as with COM, the failure to create a class is harder to debug than a failing method call when the class has been instantiated. In a way it's worse. It seems that there is actually a binding failure so you waste time in a fruitless analysis of the Fusion Log when it is actually a failure in the Loader.

Use an Init() method and save yourself alot of time.

Useful References

Suzanne Cook - Debugging Assembly Loading Failures
MSDN - How the Runtime Locates Assemblies

Ted Pattison MSDN Magazine - Deploying Assemblies

No comments: