Zurück

Using Assemblies in Microsoft .NET and C#


Overview and implementation of Dynamic Link Libraries (DLL)
as Private and Global Assemblies


Contents

1.   Introduction

Summary
Scope

2.   Setup of the .NET Framework

Microsoft .NET Framework Software Development Kit

3.   Assemblies

Direct Use of DLLs
Private Assemblies
Global Assemblies
Compile/Link Cycle
View Assemblies - The Intermediate Language Disassembler (ILDASM)

4.   Sample Application

Steps
App.cs
Hello.cs
GoodBye.cs
HowDoYouDo.cs
Compile Classes to DLLs - The CSharp Compiler (CSC)
Group DLLs in a Private Assembly - The Assembly Linker (AL)

5.   Create Global Assembly

Generate Key File - The Strong Name Utility (SN)
Version Control and Linking
Load into Assembly Cache - The Global Assembly Cache Utility (GACUTIL)

6.   Compile and Run Executable (EXE)

Reference Private and Global Assemblies
Add DLLs Directly and Reference Global Assemblies
Run it

7.   Loading DLLs on Demand

Start Debugger
List Modules

8.   Step into Private Assemblies

Private Assemblies are Referencing DLLs
Version Control and Signing
Correct Version Required
Recognition of Tampered Files

9.   Step into Global Assemblies

Delete Locally Compiled Global Assemblies
Remove Assembly from GAC
Check Public Key Token

10.   Version Global Assemblies

Attributes
Compile/Link and Load into GAC
Same Name but Different Versions
Correct Version Required

11.    Appendix

Commandfile to Build Sample Application
Download Sources


 
1.  Introduction

Summary

In any programming environment it is of particular importance to be able to encapsulate dedicated functionality in libraries. We like to build modules and must be in the position to use the same code in different locations. Additionally, we want to save resources and have binaries loaded only if required by the functionality a program is using at a given time (dynamic linking). Libraries can be part of a single application or shared for the use by other programs, implemented by our own or even by other companies.

In this context we need answers to the two questions:

How libraries are located ?

How they are identified ?

In the past Dynamic Link Libraries (DLL) were located by the system environment's PATH setting and the Windows Registry. Globally and shared DLLs were stored in the Windows system folder. The identification was specified by the DLL's file name. These mechanisms do have certain drawbacks. One is that we can't move applications in our file system because the Windows Registry does have paths stored. This is especially on bigger systems a significant problem, if more disk storage has to be added, data has to be moved, disk layouts reorganised and so on. Another thing is that DLLs can be overwritten by mistake or on purpose, for instance by other applications' installation programs. These newer DLLs may not be compatible anymore and other programs using them may crash.

With Microsoft.NET the Windows Registry is not used anymore and the libraries are stored either together with an application as Private Assemblies or in a Global Assembly Cache (GAC) as Global Assemblies to be shared among applications. Assemblies are signed and verified to recognise content modifications. They are identified by name and version to resolve incompatibility issues.

To conclude, the former DLL problems could be improved with the introduction of assemblies and all the related concepts. Developers are now in the position to use DLLs safely as libraries for single programs or to share functionality among other applications.

Scope

This documents describes the basic concepts of DLLs, Private and Global Assemblies. A sample application is used to explain an implementation in detail. Key aspects like signing and version control are worked out in different chapters. Version control with configuration files is not part of this document. The command line debugger is introduced as a tool to verify program code and to learn more about DLLs and assemblies.

2.   Setup of the .NET Framework

The Microsoft® .NET Framework is the infrastructure for the overall .NET Platform. The common language runtime and class libraries (including Microsoft Windows® Forms, ADO.NET, and ASP.NET) combine to provide services and solutions that can be easily integrated within and across a variety of systems.

The .NET Framework provides a fully managed, protected, and feature-rich application execution environment, simplified development and deployment, and seamless integration with a wide variety of languages.

Microsoft .NET Framework Software Development Kit

The Microsoft® .NET Framework Software Development Kit (SDK) includes the .NET Framework, as well as everything you need to write, build, test, and deploy .NET Framework applications - documentation, samples, and command-line tools and compilers.

Download and Installation

Goto:

http://msdn.microsoft.com/downloads/

Select:

-> Software Development Kits
-> Microsoft .NET Framework SDK

You'll get the whole framework and a C# command line compiler. Run the downloaded setup.exe if you haven't installed yet Microsoft .NET and follow the installation steps. You will be asked for Server Components which you don't need. If the installation asks for Microsoft Data Access Components MDAC you may continue or quit the installation. Anyway, you need to install MDAC 2.7 or higher prior ODBC data access is going to work.

Test your Installation using C#

We will now, per tradition, build a very simple command-line application—an executable program that writes the "Hello World" string. You will use C# to build this application.

Here is how Hello World looks in C#:

// Allow easy reference to the System namespace classes.
using System;

// This class exists only to house the entry point.
class MainApp {
   // The static method, Main, is the application's entry point.
   public static void Main() {

      // Write text to the console.
      Console.WriteLine("Hello World using C#!");
   }
}

using System;

The syntax for C#  to access the core library is new for us; it specifies the namespace rather than the name of the file in which it is found:

class MainApp {

In C#, all code must be contained in methods of a class. So, to house the entry-point code, you must first create a class. (The name of the class does not matter here). Next, you specify the entry point itself:

public static void Main () {

The compiler requires this to be called Main. The entry point must also be marked with both public and static. In addition, the entry point takes no arguments and does not return anything.

Console.WriteLine("Hello World using C#!");

Again, this line writes a string using the runtime Console type. In C#, however, you are able to use a period (.) to indicate the scope. Also, you do not have to place an L before the string because, in C#, all strings are Unicode.

The Build.bat file contains the single line that is necessary to build this program:

csc /debug+ /out:.\HelloCS.exe helloCS.cs

In this admittedly simple case, you do not have to specify anything other than the file to compile. In particular, C# does not use the additional step of linking that is required by C++:

DotNet\HelloCS> build.bat

c:\Users\Zahn\dotnet>csc /debug+ /out:.\HelloCS.exe helloCS.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.

DotNet\HelloCS> hellocs.exe

Hello World using C#!

3.   Assemblies

The new basic entity is called an Assembly. This is a collection of class modules presented as a single DLL or EXE file. Even though EXE files can also be called assemblies, most of the time we talk about libraries if we use this word. Assemblies are very similar to Java Archives (JAR).

Physically, assemblies are files located somewhere on disk and are per definition so-called Portable Executable (PE). Assemblies are loaded on demand. They are not loaded if not needed.

Assemblies have Metadata stored to provide version information along with a complete description of methods and types. Part of this metadata is a Manifest. The manifest includes identification information, public types and a list of other used assemblies.

We distinguish between the typical DLLs, we've already used in the times before .NET, Private Assemblies, used for single programs, and Global Assemblies shared among several applications.

Assemblies are still DLLs even if they differ from the former DLLs. In our context, there is no difference between the direct use of DLLs and Private Assemblies. Thus the direct use of DLLs is still an issue and not outdated.

Direct Use of DLLs

Source files may be compiled into DLLs instead of EXEs. Even if we link them later on to assemblies, we first compile them to normal DLLs. During EXE file compilation DLLs may be added directly. There is no obligation to create assemblies first.

  • Location is specified at compile time. Usually in the same folder like the application's EXE file or in any of the sub folders.

  • PATH is not checked while looking up files, neither set by Control Panel 'System' configuration nor set in a Console Window.

  • Identified by name only.

  • Get smaller EXE files.

  • Dynamic linking, i.e. loading on demand.

Private Assemblies

Intended use by single applications. Building modules to group common functionality.

  • Location is specified at compile time. Usually in the same folder like the application's EXE file or in any of the sub folders.

  • PATH is not checked while looking up files, neither set by Control Panel 'System' configuration nor set in a Console Window.

  • Identified by name and version if required. But only one version at a time.

  • Digital signature possible to ensure that it can't be tampered.

  • Get smaller EXE files.

  • Dynamic linking, i.e. loading on demand.

Global Assemblies

Publicly sharing functionality among different application.

  • Located in Global Assembly Cache (GAC).

  • Identified by globally unique name and version.

  • Digital signature to ensure that it can't be tampered.

  • Get smaller EXE files.

  • Dynamic linking, i.e. loading on demand.

Compile/Link Cycle

To create an application we have to compile our source files and to end up in an Executable (EXE). There are different ways to do this:

(1) We can directly compile all our source files into an EXE (see Test your Installation using C#)
(2) DLLs can be created from source files, which are either added directly to an EXE (5) or linked to a Private or Global Assembly (3).
(4) The resulting Assemblies can be referenced during EXE compilation.

View Assemblies - The Intermediate Language Disassembler (ILDASM)

Display metadata of one of your .NET programs or libraries by the use of the ILDASM tool:

zahn@ARKUM> ildasm app1.exe

We can see the assembly's metadata with all the methods and types in a tree representation. If we click on ' M A N I F E S T ' we get a window, which shows the manifest information. Even if there are a lot of non well readable lines, we are able to identify version information and all the referenced assemblies used to run the displayed executable or library.

Why does the tool we have used right now call ' Intermediate Language Disassembler ' ? Just bring back into our minds that if we compile sources, for instance with the CSharp Compiler (CSC) or any other .NET language compiler, we won't get machine code executable on a computer. Instead we get Microsoft Intermediate Language (MSIL) or shortened to Intermediate Language (IL) stored as an EXE file. To get machine code and to run the EXE we need the Common Language Runtime (CLR) part of the .NET installation somewhere on our computer.

4.   Sample Application

Our sample application is somehow similar to the typical 'Hello world' programs everywhere around. The program meets somebody and says 'Hello', 'How do you do' and 'Good bye'. All the three greetings printed by another library.

Steps

1.  Create the Private Assembly GreetAssembly.dll  from Hello.dll and GoodBye.dll
2.  Create the Global Assembly HowDoYouDoSharedAssembly.dll from HowDoYouDo.dll
3. 
Reference Private and Global Assemblies within App1.exe
4. 
Call DLLs directly and reference Global Assemblies within App2.exe

After we have all the three libraries ready, we may compile two EXE main programs. The first one with a reference to GreetAssembly.dll and the second adding the Hello.dll and GoodBye.dll files directly.

App.cs

The main application App.cs program looks like this:

using csharp.test.app.greet;
namespace csharp.test.app {
  public class Application {
    public static void Main() {
      Application a = new Application();

      Hello h = new Hello();
      h.SayHello();

      HowDoYouDo d = new HowDoYouDo();
      d.SayHowDoYouDo();

      a.Leave();
    }
    private void Leave() {
      GoodBye g = new GoodBye();
      g.SayGoodBye();
    }
  }
}

The Main() method creates an Application object to invoke the Leave() method at the end to say 'Good bye'. Inside Main(), just after the Application object creation, the Hello and HowDoYouDo classes are instantiated to say 'Hello' and 'How do you do'.

The Leave() method is created to verify that, while the program runs, not all the DLLs are loaded. Instead, the DLL with GoodBye stays apart until we enter the Leave() method's scope. We are going to investigate this later on in details.

We need a 'using' declaration because Hello and GoodBye are in another namespace as the main program.

Hello.cs

There is a simple class providing a method to print out a 'Hello':

namespace csharp.test.app.greet {
  public class Hello {
    public void SayHello() {
      System.Console.WriteLine("Hello my friend, I am a DLL");
    }
  }
}

The namespace is declared and equals to GoodBye.cs. Hello.cs and GoodBye.cs will be put into a single Private Assembly. They must be in the same namespace.

GoodBye.cs

Similar to Hello.cs but prints a 'Good bye':

namespace csharp.test.app.greet {
  public class GoodBye {
    public void SayGoodBye() {
      System.Console.WriteLine("Good bye, I am a DLL too");
    }
  }
}

Uses the same namespace like Hello.cs.

HowDoYouDo.cs

We are going to implement this source file as a Global Assembly:

using System.Reflection;
[assembly:AssemblyKeyFile("app.snk")]
[assembly:AssemblyVersion("1.0.0.0")]
namespace csharp.test.app {
  public class HowDoYouDo {
    public void SayHowDoYouDo() {
      System.Console.WriteLine("How do you do, I am a Global Assembly");
    }
  }
}

With the Attributes at the top we specify the key file used to generate a hash code and to declare the version. For what this hash code is used and how the versioning exactly works will be discussed later on. Like in the other classes there is a method to print out a text.

Compile Classes to DLLs - The CSharp Compiler (CSC)

To compile our source files we use the C# Compiler (csc):

DotNet> csc /debug /t:module /out:bin\Hello.dll Hello.cs
DotNet> csc /debug /t:module /out:bin\GoodBye.dll GoodBye.cs
DotNet> csc /debug /t:module /out:bin\HowDoYouDo.dll HowDoYouDo.cs

While /debug includes debug information, we are going to use later on, the /t (target) switch lets us create a DLL. We are writing all our DLLs into a bin folder.

Group DLLs in a Private Assembly - The Assembly Linker (AL)

We like to combine Hello.dll with GoodBye.dll and put them into a Private Assembly we call GreetAssembly.dll, this is Step 1 in our Sample Application.

DotNet> al /t:library /out:bin\GreetAssembly.dll bin\Hello.dll bin\GoodBye.dll

For this purpose we use the Assembly Linker. As /t (target) we generate here a library referencing the two other DLLs. This is also called a Multi-Module Assembly. Again, we store all the binaries in a bin folder.

5.   Create Global Assembly

Global Assemblies, also called Shared Assemblies, are used to provide globally accessible libraries for different applications. These applications may be used by a single vendor or may be publicly available to other vendors and companies. We do not have control who is using our Global Assemblies. However, this was and is still the case if we store ordinary DLLs somewhere else on disk.

Global Assemblies are presented in the Global Assembly Cache (GAC) which can be display by Windows Explorer:

The GAC can be found always in the 'assembly' sub folder inside %SystemRoot%, i.e. WINNT for Windows 2000 and Windows NT.

Generate Key File - The Strong Name Utility (SN)

A tool is used to generate the key file incorporating a Public/Private Key pair. We need this key pair to create a hash key for every file. The hash key is called Public Key Token. This token allows the Common Language Runtime (CLR) to recognise if a file has been tampered or overwritten. If the token doesn't match anymore the file was modified.

By the time we pass the public/private key file, the private key is used to create the public key token by encrypting the assembly name and content. The private key is our secret and stays with us. The public key token is stored along with the public key inside the Global Assembly's manifest. This process is called Signing the assembly. The CLR needs the public key to decrypt the token to verify the file but not other way around. Only with the private key we are able to create the token and nobody else can create it.

A hash token which encrypts the name and content along with the public key is called a Strong Name.

DotNet> sn -k app.snk

Microsoft (R) .NET Framework Strong Name Utility Version 1.0.3705.0
Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.

Key pair written to app.snk

Version Control and Linking

The full version description consists of different parts, e.g.:

1:0:1504:18

1:0 Major and minor version
1504 Build
18 Revision

At the top of a source file we can define attributes for the key file and the version we state:

[assembly:AssemblyKeyFile("app.snk")]
[assembly:AssemblyVersion("1.0.0.0")]

While introducing the sample application we have seen how to compile our assembly to HowDoYouDo.dll.

We are now going to link the DLL with the Assembly Linker (AL) to a Global Assembly incorporating the key file and version number:

DotNet> al /t:library /out:bin\HowDoYouDoSharedAssembly.dll bin\HowDoYouDo.dll

As /t (target) we have a library. It is stored, like all other binaries, in a sub folder, this is Step 2 in our Sample Application. Instead of using attributes in the source file we may also pass the key file and version directly to the linker:

DotNet> al /t:library /keyfile:app.snk /v:1.0.0.0
        /out:bin\HowDoYouDoSharedAssembly.dll bin\HowDoYouDo.dll

Load into Assembly Cache - The Global Assembly Cache Utility (GACUTIL)

We load the Global Assembly into GAC by the use of the Global Assembly Cache Utility:

gacutil /i bin\HowDoYouDoSharedAssembly.dll

Microsoft (R) .NET Global Assembly Cache Utility. Version 1.0.3705.0
Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.

Assembly successfully added to the cache

The /i switch installs an assembly in the global assembly cache by passing the name. It is also possible to simply copy the file directly into the GAC folder by the use of Windows Explorer or by a command shell. Open Windows Explorer an go to C:\WINNT\assembly an you will find the HowDoYouDoSharedAssembly entry there.

6.   Compile and Run Executable (EXE)

To get an executable EXE file we need to compile our main program App.cs and pass all the required libraries.

Reference Private and Global Assemblies

For the first EXE file we are going to reference our Private and Global Assemblies (write all in one line), this is Step 3 in our Sample Application.

DotNet> csc /debug /t:exe /r:bin\GreetAssembly.dll;bin\
        HowDoYouDoSharedAssembly.dll /out:bin\App1.exe App.cs

Add DLLs Directly and Reference Global Assemblies

For the second executable file we are adding the DLLs directly without prior collecting them in a Private Assembly. The Global Assembly is referenced in the same way like above, this is Step 4 in our Sample Application.

DotNet> csc /debug /t:exe /lib:bin /addmodule:Hello.dll;GoodBye.dll
        /r:bin\HowDoYouDoSharedAssembly.dll /out:bin\App2.exe App.cs

Run it

Change into the sub folder and run the programs:

DotNet> cd bin
DotNet\Bin> app1

Hello my friend, I am a DLL
How do you do, I am a Global Assembly
Good bye, I am a DLL too

DotNet\Bin> app2

Hello my friend, I am a DLL
How do you do, I am a Global Assembly
Good bye, I am a DLL too

7.   Loading DLLs on Demand

We have compiled everything with the debug option to be able to do some test with the debugger. The compiler created a couple of files ending with PDB. These files are not needed to run our program, they're for debugger's use only.

Start Debugger

We are now going to verify loading on demand of DLLs. For this purpose we use the debugger's shell version.

A list of the most commonly used commands:

h

help

n

step over the next source line

s

step into the next source line

l mod

list of modules

pa

show source files path

pa ...

set source files path, e.g. one folder up

q

quit debugger

Before we can use the debugger add the following code as first line in your Main() method:

System.Diagnostics.Debugger.Launch();

Make sure you have compiled with the debug switch as shown in all the samples. If you run the program now, the debugger will be invoked and a shell window appears.

Enter 'n' until you get ahead of the following line:

(cordbg) n
009:       System.Diagnostics.Debugger.Launch();

We are now at the begin of our program. If you've never got your source code displayed, set your source files path and start again if necessary.

List Modules

If you can see your source code and your are at the begin of the program, list all the loaded modules:

(cordbg) l mod

Module 0x0008af0c, c:\users\zahn\dotnet\bin\app1.exe  -- AD #1
Module 0x00091f84, c:\winnt\assembly\gac\howdoyoudosharedassembly\
                    1.0.0.0__06fe90da7fe1c89a\howdoyoudo.dll -- AD #1
Module 0x0008e244, c:\users\zahn\dotnet\bin\bin\hello.dll -- AD #1
Module 0x000877b4, c:\winnt\microsoft.net\framework\v1.0.3705\mscorlib.dll -- AD #1
                                                             --Symbols not loaded
Module 0x0008c524, c:\users\zahn\dotnet\bin\greetassembly.dll -- AD #1
                                                             -- Symbols not loaded
Module 0x000900cc, c:\winnt\assembly\gac\howdoyoudosharedassembly\
                    1.0.0.0__06fe90da7fe1c89a\howdoyoudosharedassembly.dll -- AD #1
                                                             -- Symbols not loaded

The Hello.dll has been loaded already because we are in the scope where the 'new Hello()' will be executed. In contrary, the GoodBye.dll is not in memory.

Continue with 'n' until:

(cordbg) n
019:       a.Leave();

Now step into the Leave() method with 's' and list the loaded modules again:

(cordbg) s
024:       GoodBye g = new GoodBye();

(cordbg) l mod

Module 0x0008af0c, c:\users\zahn\dotnet\bin\App1.exe  -- AD #1
Module 0x00091f84, c:\winnt\assembly\gac\howdoyoudosharedassembly\
                    1.0.0.0__06fe90da7fe1c89a\howdoyoudo.dll -- AD #1
Module 0x0009aecc, c:\users\zahn\dotnet\bin\goodbye.dll -- AD #1
Module 0x0008e244, c:\users\zahn\dotnet\bin\hello.dll -- AD #1
Module 0x000877b4, c:\winnt\microsoft.net\framework\v1.0.3705\mscorlib.dll -- AD #1
                                                             -- Symbols not loaded
Module 0x0008c524, c:\users\zahn\dotnet\bin\greetassembly.dll -- AD #1
                                                             -- Symbols not loaded
Module 0x000900cc, c:\winnt\assembly\gac\howdoyoudosharedassembly\
                    1.0.0.0__06fe90da7fe1c89a\howdoyoudosharedassembly.dll -- AD #1
                                                             -- Symbols not loaded

The GoodBye.dll has been loaded now, you may quit the debugger with q.

As we have seen only the DLLs required are loaded. The previous test was made with a Private Assembly. However, you may also do the same with direct use of DLLs by starting app2. You'll see the same behaviour.

8.   Step into Private Assemblies

This chapter talks about selected topics that might be of interest while working with Private Assemblies.

Private Assemblies are Referencing DLLs

We may use Private Assemblies to group compiled DLLs into fewer library files. These Private Assemblies do have references stored to the DLLs they group. They do not incorporate any of them.

In our sample we have the GreetAssembly.dll referencing Hello.dll and GoodBye.dll. If we're going to delete for instance Hello.dll, we get an error message while trying to run the program:

Unhandled Exception: System.IO.FileNotFoundException:
File or assembly name Hello.dll, or one of its dependencies, was not found.
File name: "Hello.dll" at csharp.test.app.Application.Main()

Version Control and Signing

It is also possible to use version control and signing for Private Assemblies. In contrary to Global Assemblies we can't have multiple version at the same time. This possibility is provided by the Global Assembly Cache (GAC) only. Private Assemblies are stored in a normal file system folder inside our application's home directory.

Pass a key file and version to the assembly linker:

DotNet> al /nologo /t:library /keyfile:app.snk /v:2.2.0.0
        /out:bin\GreetAssembly.dll bin\Hello.dll bin\GoodBye.dll

Even if we do not explicitly specify a version, the assembly stores 0.0.0.0 and passes this information during EXE compilation. The Explorer's file properties dialog shows Assembly versions, comments, languages etc. Do not forget to refresh the panel to get up to date information:

Compile the executable App1.exe and run it:

DotNet> csc /nologo /debug /t:exe /r:bin\GreetAssembly.dll;bin\
        HowDoYouDoSharedAssembly.dll /out:bin\App1.exe App.cs

DotNet> cd bin
DotNet\Bin> app1

Hello my friend, I am a DLL
How do you do, I am a Global Assembly
Good bye, I am a DLL too

Correct Version Required

Change the version and run it again:

DotNet\Bin> cd ..
DotNet> al /nologo /t:library /keyfile:app.snk /v:2.3.0.0
        /out:bin\GreetAssembly.dll bin\Hello.dll bin\GoodBye.dll
DotNet> cd bin
DotNet\Bin> app1

Unhandled Exception: System.IO.FileLoadException: The located assembly's
manifest definition with name 'GreetAssembly' does not match the assembly reference.
File name: "GreetAssembly"
at csharp.test.app.Application.Main()

Fusion log follows:
=== Pre-bind state information ===
LOG: DisplayName = GreetAssembly, Version=2.2.0.0, Culture=neutral
(Fully-specified)
LOG: Appbase = C:\Users\Zahn\DotNet\Bin\
LOG: Initial PrivatePath = NULL
Calling assembly : App1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null.

LOG: Application configuration file does not exist.
LOG: Publisher policy file is not found.
LOG: Host configuration file not found.
LOG: Using machine configuration file from C:\WINNT\Microsoft.NET\Framework\v1.0.3705\config\machine.config.
LOG: Post-policy reference: GreetAssembly, Version=2.2.0.0, Culture=neutral, PublicKeyToken=91634922574fc032
LOG: Attempting download of new URL file:///C:/Users/Zahn/DotNet/Bin/GreetAssembly.DLL.
WRN: Comparing the assembly name resulted in the mismatch: Minor Version

The new version 2.3.0.0 was not presented to the executable at compilation them. Therefore  the CLR does still expect version 2.2.0.0.

Recognition of Tampered Files

Compile the executable again with GreetAssembly.dll version 2.3.0.0 to get a running program:

DotNet> csc /nologo /debug /t:exe /r:bin\GreetAssembly.dll;bin\
        HowDoYouDoSharedAssembly.dll /out:bin\App1.exe App.cs

DotNet> cd bin
DotNet\Bin> app1

Hello my friend, I am a DLL
How do you do, I am a Global Assembly
Good bye, I am a DLL too

Now we are going to modify Hello.cs, one of the source files used in our Assembly.
Add some text to the following line:

System.Console.WriteLine("Hello my friend, I am a DLL, modified");

Compile the source file referenced by GreetAssembly.dll. The version is still the same: 2.3.0.0. However, the content changed. The two libraries are different even if the version numbers are equal:

DotNet> csc /nologo /debug /t:module /out:bin\Hello.dll Hello.cs
DotNet> cd bin
DotNet\Bin> app1

Running the application fails:

Unhandled Exception: System.IO.FileLoadException:
The check of the module's hash failed for file 'Hello.dll'.
File name: "Hello.dll" at csharp.test.app.Application.Main()

The EXE files remembers the Assemblies' hash key stored at the last compilation. This hash key does not match with the one returned by the new DLL file at the time of loading.

8.    Step into Global Assemblies

Similarly to the previous chapter, selected topics are listed here about Global Assemblies. Due to the scope of versioning a separate chapter is used and follows these explanations.

Delete Locally Compiled Global Assemblies

While compiling a Global Assembly the output is written locally into your application folder or one of the sub folders. Afterwards you're going to load this file into the GAC. There is no use to store it locally anymore.

Remove Assembly from GAC

To remove all versions of a given Assembly issue:

DotNet> gacutil /u HowDoYouDoSharedAssembly

Microsoft (R) .NET Global Assembly Cache Utility. Version 1.0.3705.0
Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.


Assembly: HowDoYouDoSharedAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=91634922574fc032, Custom=null
Uninstalled: HowDoYouDoSharedAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=91634922574fc032, Custom=null

Number of items uninstalled = 1
Number of failures = 0

A specific version of a given Assembly can be removed like this:

DotNet> gacutil /u HowDoYouDoSharedAssembly,Version=1.0.0.0

Check Public Key Token

You may run the strong name utility to get the key displayed:

DotNet\Bin> sn -T HowDoYouDoSharedAssembly.dll

Microsoft (R) .NET Framework Strong Name Utility Version 1.0.3705.0
Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.

Public key token is 91634922574fc032

You may compare this key with the value displayed by Microsoft Windows Explorer pointing
to the WINNT\assembly folder.

10.  Version Global Assemblies

Attributes

Specify the version of a Global Assembly DLL as attribute at the top of your source file:

[assembly:AssemblyVersion("1.0.0.0")]

Compile/Link and Load into GAC

We are going to compile the source file into a DLL, link it to a Global Assembly, load it into the GAC and reference it during EXE compilation.

For the sample file HowDoYouDo.cs this looks like this:

DotNet> csc /debug /t:module /out:bin\Hello.dll Hello.cs
DotNet> csc /debug /t:module /out:bin\GoodBye.dll GoodBye.cs
DotNet> csc /debug /t:module /out:bin\HowDoYouDo.dll HowDoYouDo.cs
DotNet> al /t:library /out:bin\GreetAssembly.dll bin\Hello.dll bin\GoodBye.dll
DotNet> al /t:library /out:bin\HowDoYouDoSharedAssembly.dll bin\HowDoYouDo.dll
DotNet> gacutil /i bin\HowDoYouDoSharedAssembly.dll
DotNet> csc /debug /t:exe /r:bin\GreetAssembly.dll;bin\
        HowDoYouDoSharedAssembly.dll /out:bin\App1.exe App.cs

The last line, which is referencing HowDoYouDoSharedAssembly.dll, remembers the presented version during compilation. By the time we run the EXE file, the Common Language Runtime (CLR) checks if the version of all the referenced DLLs matches the one which was available at compilation time.

In our sample this is version 1.0.0.0:

Same Name but Different Versions

If we change the version Attribute to 1.1.0.0 for HowDoYouDo.cs we get two Assemblies with the same name but with different versions in the GAC:

using System.Reflection;
[assembly:AssemblyKeyFile("app.snk")]
[assembly:AssemblyVersion("1.1.0.0")]
namespace csharp.test.app {
  public class HowDoYouDo {
    public void SayHowDoYouDo() {
      System.Console.WriteLine("How do you do, I am a Global Assembly: 1.1");
    }
  }
}

To be able to distinguish the two libraries, we may also modify the written line and add the version at the end:

System.Console.WriteLine("How do you do, I am a Global Assembly: 1.1");

If we're going again through the whole compile and link cycle the EXE compilation is referencing the new library and thus, this one is loaded during execution:

DotNet> cd bin
DotNet\Bin> app1

Hello my friend, I am a DLL
How do you do, I am a Global Assembly: 1.1
Good bye, I am a DLL too

You may refresh the Explorer to see the two libraries:

We may now change the version Attribute again, and this time to 1.1.2.0. Don't forget to adapt the written line:

using System.Reflection;
[assembly:AssemblyKeyFile("app.snk")]
[assembly:AssemblyVersion("1.1.2.0")]
namespace csharp.test.app {
  public class HowDoYouDo {
    public void SayHowDoYouDo() {
      System.Console.WriteLine("How do you do, I am a Global Assembly: 1.1.2");
    }
  }
}

This time we won't compile the EXE file again. Instead, we just compile the source file to a DLL, link to a Global Assembly and load it into the GAC:

DotNet> csc /debug /t:module /out:bin\HowDoYouDo.dll HowDoYouDo.cs
DotNet> al /t:library /out:bin\HowDoYouDoSharedAssembly.dll bin\HowDoYouDo.dll
DotNet> gacutil /i bin\HowDoYouDoSharedAssembly.dll

Now we have three libraries in the GAC with the same name: 1.0.0.0, 1.1.0.0 and 1.1.2.0

While running the program again, version 1.1 is loaded:

DotNet> cd bin
DotNet\Bin> app1

Hello my friend, I am a DLL
How do you do, I am a Global Assembly: 1.1
Good bye, I am a DLL too

This is correct because 1.1.2.0 wasn't the library available at compile time.

Correct Version Needed

If we are going to remove version 1.1.0.0 from GAC we do not have a valid DLL any more:

DotNet> gacutil /u HowDoYouDoSharedAssembly,Version=1.1.0.0

Microsoft (R) .NET Global Assembly Cache Utility. Version 1.0.3705.0
Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.

Assembly: HowDoYouDoSharedAssembly, Version=1.1.0.0, Culture=neutral, PublicKeyToken=91634922574fc032, Custom=null
Uninstalled: HowDoYouDoSharedAssembly, Version=1.1.0.0, Culture=neutral, PublicKeyToken=91634922574fc032, Custom=null

Number of items uninstalled = 1
Number of failures = 0

DotNet> cd bin
DotNet\Bin> app1

Unhandled Exception: System.IO.FileLoadException: The located assembly's manifest definition with name 'HowDoYouDoSharedAssembly' does not match the assemb
File name: "HowDoYouDoSharedAssembly"
at csharp.test.app.Application.Main()

Fusion log follows:
=== Pre-bind state information ===
LOG: DisplayName = HowDoYouDoSharedAssembly, Version=1.1.0.0, Culture=neutral, PublicKeyToken=91634922574fc032
(Fully-specified)
LOG: Appbase = C:\Users\Zahn\DotNet\Bin\
LOG: Initial PrivatePath = NULL
Calling assembly : App1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null.
===

LOG: Publisher policy file is not found.
LOG: Host configuration file not found.
LOG: Using machine configuration file from C:\WINNT\Microsoft.NET\Framework\v1.0.3705\config\machine.config.
LOG: Post-policy reference: HowDoYouDoSharedAssembly, Version=1.1.0.0, Culture=neutral, PublicKeyToken=91634922574fc032
LOG: Attempting download of new URL file:///C:/Users/Zahn/DotNet/Bin/HowDoYouDoSharedAssembly.DLL.
WRN: Comparing the assembly name resulted in the mismatch: Build Number

11.  Appendix

Commandfile to Build Sample Application

clear

@del /q bin\*

@REM Create DLL modules 'Hello', 'HowDoYouDo' and 'GoodBye' into sub-folder bin
@REM (/nologo supresses compiler info banner)
@REM

csc /nologo /debug /t:module /out:bin\Hello.dll Hello.cs
csc /nologo /debug /t:module /out:bin\GoodBye.dll GoodBye.cs
csc /nologo /debug /t:module /out:bin\HowDoYouDo.dll HowDoYouDo.cs

@REM Create a private assembly DLL 'GreetAssembly' from 'Hello'
@REM and 'GoodBye' into sub-folder bin
@REM

al /nologo /t:library /out:bin\GreetAssembly.dll bin\Hello.dll bin\GoodBye.dll
sn -k app.snk

@REM Same with CSC
@REM csc /nologo /debug /t:library /lib:bin /addmodule:Hello.dll;GoodBye.dll
@REM /out:bin\GreetAssembly.dll

@REM Create a shared assembly DLL 'HowDoYouDoSharedAssembly' into sub-folder bin
@REM

al /nologo /t:library /out:bin\HowDoYouDoSharedAssembly.dll bin\HowDoYouDo.dll

@REM Same without attributes in source file
@REM al /nologo /t:library /keyfile:app.snk /v:1.0.0.0
@REM /out:bin\HowDoYouDoSharedAssembly.dll bin\HowDoYouDo.dll

@REM Load shared assembly DLL 'HowDoYouDoSharedAssembly' into GAC
@REM

gacutil /nologo /i bin\HowDoYouDoSharedAssembly.dll

@REM Main program 'app1' with reference to assembly in sub-folder bin
@REM

csc /nologo /debug /t:exe /r:bin\GreetAssembly.dll;bin\
HowDoYouDoSharedAssembly.dll /out:bin\App1.exe App.cs

@REM Main program 'app2' using DLLs directly in sub-folder bin
@REM

csc /nologo /debug /t:exe /lib:bin /addmodule:Hello.dll;GoodBye.dll
/r:bin\HowDoYouDoSharedAssembly.dll /out:bin\App2.exe App.cs

@REM Delete local shared assembly, it is available in GAC
@REM
@del bin\HowDoYouDoSharedAssembly.*

@ECHO ------
@ECHO Programs located in sub-folder bin:
@ECHO   app1 - with reference to private assembly
@ECHO   app2 - using DLLs directly
@ECHO ------