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.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:
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:
{
.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:
class App
{
static void Main()
{
LibraryCode code = new LibraryCode();
Console.WriteLine("library {0}", code.GetVersion());
}
}
and compile it with:
You can then run the process to get the result:
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:
{
.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:
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:
{
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.
Compile and run this code, the results will look something like this:
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:
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):
{
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:
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:
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.