.NET Fusion
Home About Workshops Articles Writing Talks Books Contact

1. Loading Library Assemblies

.NET assemblies can load library assemblies statically or dynamically. In most cases libraries are loaded statically, that is, information about the library to be loaded is stored in the referencing assembly's manifest and when the referencing library loads a type from the library it will use the name in its manifest to determine which library to load. The runtime will attempt to load exactly the library that is mentioned in the referencing assembly.

1.1 Assembly Metadata

Create a library assembly with the following code:

using System;
using System.Reflection;
[assembly:AssemblyVersion("1.0.0.0")]
public class LibraryCode
{
   public string GetVersion()
   {
      Assembly a = Assembly.GetExecutingAssembly();
      return String.Format("version: {0} codebase: {1}",
            a.GetName().Version.ToString(), a.CodeBase);
   }
}

Compile this code with the following command line:

csc /t:library lib.cs

Now view the library with ILDASM (either GUI or on the command line with ILDASM /text /item=lib lib.dll). This will show the following:

.assembly lib
{
   .hash algorithm 0x00008004
   .ver 1:0:0:0
}


.NET Version 3.0
You will find that with version 3.0/2.0 of the runtime the compiler will add two extra entries. These are custom attributes equivalent to the C# [CompilationRelaxations(CompilationRelaxations.NoStringInterning)], which means that each use of a single string literal will have a separate string object and [RuntimeCompatibility(WrapNonExceptionThrows = true)] which indicates that if an exception is thrown that does not derive from Exception the runtime will wrap it in a RuntimeWrappedException.

This shows that the version has been added to the metadata of the library.

Next, create a process assembly to use the library:

using System;

class App
{
   static void Main()
   {
      LibraryCode code = new LibraryCode();
      Console.WriteLine("library {0}", code.GetVersion());
   }
}

and compile it with:

csc app.cs /r:lib.dll

You can then run the process to get the result:

C:\TestFolder>app
library version: 1.0.0.0 codebase: file:///C:/TestFolder/lib.dll

This shows that the library was loaded from the current folder. The path given by the CodeBase property will be important in later tests. It is interesting to take a look at the metadata of the assembly that uses the library, ILDASM (ILDASM /text /item=app app.exe) gives this:

.assembly extern mscorlib
{
   .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
   .ver 1:0:5000:0
}
.assembly extern lib
{
   .ver 1:0:0:0
}

.NET Version 3.0
Clearly for .NET version 3.0/2.0 the version of mscorlib will be 2.0.0.0

This shows that the compiler stores the complete name of the library assembly in the assembly that uses the library. The name of our assembly includes the short name (the PE file name without the extension, lib) and the version. The complete name looks like this:

lib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

The information in the process uniquely identifies this assembly. Although this would appear to indicate that only this version of the library can be used, this is not the case as well see later.

1.2 Loading Libraries and JIT Compilation

In the last example the library is loaded when an instance of the LibraryCode type is created. The following example will investigate this further. You can obtain the list of assemblies loaded into the current application domain with the AppDomain.GetAssemblies method. Edit the Main function so that it prints the assemblies in the application domain through a private static function called DumpAssemblies:

static void Main()
{
   DumpAssemblies("Before creating type");
   LibraryCode code = new LibraryCode();
   Console.WriteLine("library {0}", code.GetVersion());
   DumpAssemblies("After creating type");
}
static void DumpAssemblies(string str)
{
   Console.WriteLine(str);
   foreach(Assembly a in AppDomain.CurrentDomain.GetAssemblies())
   {
      Console.WriteLine(a.FullName);
   }
}

Also add the following to the top of the file so that you can reference the Assembly type without a fully qualified name.

using System.Reflection;

Compile and run this code, the results will look something like this:

Before creating type
mscorlib, Version=1.0.5000.0, Culture=neutral, 
   PublicKeyToken=b77a5c561934e089
app, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
lib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
library version=1.0.0.0 codebase=file:///C:/TestFolder/1.1/lib.DLL
After creating type
mscorlib, Version=1.0.5000.0, Culture=neutral, 
   PublicKeyToken=b77a5c561934e089
app, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
lib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

I will explain the details of these names later in the workshop, but for now just look at the first name on each line. These results show that your assembly uses mscorlib and lib. But notice that lib is loaded before you use the type. This contradicts when I earlier said that the assembly is loaded when a type is first used. The reason for this is Just In Time (JIT) compilation. 

In this code we have just one method, when the method is called the runtime will JIT compile the method, but just this method (the JIT compiler can optimise the code by 'inlining' small methods called by the current method, I will return to this in a moment). Once the method has been JIT compiled it is cached in memory (so that it does not have to be JIT compiled again) and executed. To successfully JIT compile a method the runtime must have access to the types used by the method, so the runtime will load the assemblies that contain those types. Main uses LibraryCode and Console and so when the method is JIT compiled the runtime ensures that the libraries containing these types are loaded. This explains why the lib library is loaded in the application domain when Main starts.

To remove the effects of JIT compiling change the code so that LibraryCode object is created in a different method:

static void Main()
{
   DumpAssemblies("Before creating type");
   GetVersion();
  
DumpAssemblies("After creating type");
}
static void GetVersion()
{
   LibraryCode code = new LibraryCode();
   Console.WriteLine("library {0}", code.GetVersion());
}

The idea is that when Main is called the runtime will only JIT compile the code in Main and this method only calls its own methods so the runtime should not need to load any other assemblies. Compile and run this new code. You'll find that you get the same results as before, the lib library is loaded when the Main is JIT compiled. The reason for this is because the runtime is inlining the GetVersion method, in effect, it copies the code for GetVersion into Main. There are two ways to prevent this. The first way is to compile the process for debug mode (/debug) which adds metadata ([Debuggable]) to the assembly to tell the runtime not to inline code. The other way is to add an initialisation file. This has the name of the assembly and has an extension of ini. Create a file called app.ini and add the following:

[.NET Framework Debugging Control]
GenerateTrackingInfo=1
AllowOptimize=0

The last line indicates that the runtime should not inline code. Save this file and run the process. This time you'll find that the lib library is not in the application domain before GetVersion is called, but it is in the application domain after the method is called, and hence you can deduce that the library is loaded for this method.

Now clean up the example: delete app.ini, delete DumpAssemblies and GetVersion and change Main to look like this (the same as Example 1.1):

static void Main()
{
   LibraryCode code = new LibraryCode();
   Console.WriteLine("library {0}", code.GetVersion());
}

1.3 Fusion and PATH

By default, Fusion will look in the current folder for the libraries that the process uses. Such library assemblies are known as private assemblies. Fusion will not use the system PATH variable nor the other folders that are searched by the Win32 function LoadLibrary. To confirm this move (do not copy) the library to the Windows system folder:

move lib.dll %systemroot%

Now run the process and you'll find that .NET will throw a FileNotFoundException exception. Now you can move the library back to the current folder (move %systemroot%\lib.dll .).

As you can see the search path used by Fusion is different to the paths used by LoadLibrary, let's investigate this further.

1.4 Filenames

Fusion assumes that the library has an extension of either .dll or .exe. Rename the library to have an extension of .exe and run the process. You will find that the library will still be loaded, Fusion does not care that the library has an extension of .exe.

Rename the library to have an extension of .dll.

1.5 Subfolders

If Fusion cannot find the specified library in the current folder it will look for the assembly in a subfolder which has the short name of the library assembly. Create a folder called lib and move the library to that folder. Run the process to confirm that Fusion can still find the library.

The library assembly has a version and the assembly that uses the library records this version, so it will appear that Fusion is performing versioning on the library. This is not the case, as the following example will illustrate.

1.6 Versioning Without a Strong Name

First, rename the lib folder to 1.0 for housekeeping reasons (rename lib 1.0). Next, move the first version of the application into the 1.0 folder to keep it safe (move app.exe 1.0). Edit the library file so that it will compile as version 2.0:

[assembly:AssemblyVersion("2.0.0.0")]

Now compile all of the code (library and process) and run the application to show that the second version of the library (in the same folder as the application) is loaded. Confirm that the manifest of the application references version 2.0 of the library (ILDASM /text /item=app app.exe).

Next, create a new folder as a temporary store for the second version of the application (md 2.0). Move the second version of the library to the 2.0 folder (move lib.dll 2.0) and copy the first version of the library into the application folder (copy 1.0\lib.dll). You now have a version of the application that requires version 2.0 of the library, but has version 1.0 of the library. Run the application again. You will find that Fusion will load version 1.0 of the library and run it. For completeness, delete the application process (del app.exe) and copy the first version of the process into the application folder (copy 1.0\app.exe). The application folder should have the first version of the process and the library (you can confirm this if you wish). To complete the test, delete the library (del lib.dll) and copy the 2.0 version from the 2.0 folder (copy 2.0\lib.dll). You now have a version of the application that requires version 1.0 of the library and version 2.0 of the library. Run the application to see that the application will pick up the later version.

In effect Fusion will pick up the library with the correct DOS name and it ignores any versioning that you have done. There is another ingredient that is needed to make versioning work. This is called a strong name which you will learn about later. Clean up the code, edit the library so that the library is version 1.0.0.0 then delete the files in the subfolders (del 1.0\*.* and del 2.0\*.*) and remove the subfolders (rmdir 1.0 and rmdir 2.0).

I hope that you enjoy this tutorial and value the knowledge that you will gain from it. I am always pleased to hear from people who use this tutorial (contact me). If you find this tutorial useful then please also email your comments to mvpga@microsoft.com.

Errata

If you see an error on this page, please contact me and I will fix the problem.

Page Two

This page is (c) 2007 Richard Grimes, all rights reserved