Assemblies and the Global Assembly Cache in C#

Assemblies in the .Net platform is a solution to the "DLL Hell" problem caused by one application overwriting a shared library of another.

2,157 words, estimated reading time 8 minutes.
Introduction to Programming with C#

This article is part of a series of articles. Please use the links below to navigate between the articles.

  1. Learn to Program in C# - Full Introduction to Programming Course
  2. Introdution to Programming - C# Programming Fundamentals
  3. Introduction to Object Oriented Programming for Beginners
  4. Introduction to C# Object-Oriented Programming Part 2
  5. Application Flow Control and Control Structures in C#
  6. Guide to C# Data Types, Variables and Object Casting
  7. C# Collection Types (Array,List,Dictionary,HashTable and More)
  8. C# Operators: Arithmetic, Comparison, Logical and more
  9. Using Entity Framework & ADO.Net Data in C# 7
  10. What is LINQ? The .NET Language Integrated Query
  11. Error and Exception Handling in C#
  12. Advanced C# Programming Topics
  13. All About Reflection in C# To Read Metadata and Find Assemblies
  14. What Are ASP.Net WebForms
  15. Introduction to ASP.Net MVC Web Applications and C#
  16. Windows Application Development Using .Net and Windows Forms
  17. Assemblies and the Global Assembly Cache in C#
  18. Working with Resources Files, Culture & Regions in .Net
  19. The Ultimate Guide to Regular Expressions: Everything You Need to Know
  20. Introduction to XML and XmlDocument with C#
  21. Complete Guide to File Handling in C# - Reading and Writing Files

Assemblies in the .Net platform are a solution to a problem that has been around as long as libraries. The problem, known as "DLL Hell", can be caused by one application overwriting a shared library of another application, usually with a different version.

Imagine you write an application with a shared library and then years later create a new application with an updated library. In order not to break old programs, what do you do with the new library? You could give it a different name, store it in a different location, or overwrite the old ones.

Problems can also arise when a DLL is registered on a computer and for some reason, the information is corrupted or modified so that the reference no longer links to the library. Registering a DLL in the system registry stores information about that library in such a way that other applications can access the data from a central store.

.Net assemblies overcome these issues by storing information about themselves, rather than having that information stored in a central location. The assembly will hold information about itself such as version and vendor.

Microsoft has defined an assembly and its role in .NET as:

In the .NET Framework an assembly is a physical unit that can be executed, deployed, versioned, and secured. All .NET Framework applications contain one or more assemblies

Versioning

The platform will assist you by providing several features to ensure proper versioning between components in an application:

  • Enforce versioning rules so that an application that needs version 1.1.1.1 will not end up getting version 1.0.0.0.
  • Shared assemblies are signed with a strong name (which includes a public key, a simple name, a version, and a culture) to make them unique. Assemblies that are not shared do not require strong names.

The information that the assembly holds about itself is called the metadata which is stored in the manifest. The manifest is like the unique fingerprint of the assembly.

Assembly Deployment

Assemblies can be deployed in a variety of methods, including direct file copy (xcopy), Windows Installer setup projects and Merge Modules setup projects.

To directly copy an assembly all you need to do is copy the file to the applications executable or bin folder and it is available to use, however, this can only be done with a private assembly. Shared assemblies need to be installed in the machine's Global Assembly Cache (GAC) using the gacutil tool or by copying the assembly to the GAC folder within Windows Explorer. Installation in the GAC results in a uniquely named folder being created and the assembly files being copied into that folder.

Windows installer and merge module projects will install for the user and are the recommended method for deployment.

What Is in the Assembly?

The assembly contains the intermediate code, resources, and metadata for itself. We can have a look inside the assembly using the ildasm (Intermediate Language Disassembler) tool that comes as part of Visual Studio. To access it you need to open the Visual Studio Command Prompt and type ildasm.exe. This will launch a Windows application that you can use to explore any .Net application.

Once launched, you can open any .Net application and view information within the file, including the manifest, types, classes, namespaces, methods and source code.

Visual Studio provides easy methods for creating and using a .Net assembly. In this tutorial, we look at creating an assembly and how we can use different methods for incorporating it into our projects.

Creating a Private Assembly

We shall first start with a blank Visual Studio C# solution. Add a new class library to the solution then build and run it. You will notice that although the project was built successfully, you cannot run the application. This is because a class library does not have any entry points, unlike a console application. Class libraries are built into assemblies which merely hold classes, code and resources.

In the class library add a method called TestMethod that will show a message box saying "Hello World!". In this example, I have changed the default namespace and class name to better reflect the purpose of the assembly.

C#
using System;
using System.Text;
using System.Windows.Forms;

namespace TestClassLibrary
{
  public static class TestClass
  {
    public static void TestMethod()
    {
      MessageBox.Show("Hello World");
    }
  }
}

If you look inside the bin/Debug folder for the project you will see a .dll file instead of the usual .exe. This is our new assembly.

To test our assembly, we need to create a project that is capable of running, for example, a console application, windows application or asp.net web application. Class libraries can also be used by other class libraries in the same method as I describe below, but then we still need to create a console application to call that class library!

Add a console application project to the current solution and set it as the default start-up project. This will cause the console application to run when we click on play or launch the project. Let's try and use the method we just created:

C#
using System;
using System.Text;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      TestClassLibrary.TestClass.TestMethod();
    }
  }
}

When we try and build the application you will notice that it failed with a message stating that

"The type or namespace name could not be found (are you missing a directive or an assembly reference?)"

We are seeing this message because we haven't told the console application about the class library (even though it is part of the same solution). To do this we need to add a reference to the class library. This can be done by right-clicking on the console application in solution explorer and selecting "Add Reference".

Add reference to projects
Add reference to projects

From this box, you can select an existing.Net assembly or browse a file or project. Click the Projects tab and select our class library.

Finally, we will need to add the namespace to the using statements or get refactor to do it for us.

C#
using System;
using System.Collections.Generic;
using System.Text;
using TestClassLibrary;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      TestClass.TestMethod();
    }
  }
}

Now when you build the project it should compile and run properly.

Using third-party Assemblies

To use a third-party assembly you can use the same method as above, simply add a reference to the assembly and add in a using clause (or explicitly reference classes and data types).

Creating a Shared Assembly

The procedure for creating a shared assembly is the same as for a private assembly, except that there are a few extra steps involved before you can use it. All shared assemblies need to be strongly named before they can be installed into the global assembly cache. Strongly-named assemblies ensure that the assembly has a unique manifest consisting of versioning information and a public key.

Satellite Assemblies

If you plan on localising your application (making your application customisable for different languages and cultures) you should use satellite assemblies and neutral code.

Neutral code does not contain any hard coding or language/culture-specific code. For example, there are no hardcoded strings or symbols, no hardcoded date formatting and no region-specific calculations e.g. VAT.

When creating a satellite assembly you should work with the naming convention suggested by Microsoft, The format of which is ..resource where the culture identifier consists of ISO language and culture strings. You can find a list of culture codes in this list List of .Net Culture Codes.

Satellite assemblies use a hub and spoke design, with your neutral code at the centre (hub) and various culture-specific satellite assemblies plugging into it (spokes). As a result, the runtime requires that you place resources in specific locations so that they can be easily located and used. If you do not compile and name resources as expected, or if you do not place them in the correct locations, the common language runtime will not be able to locate them. The compiler will first look in the global assembly cache for any localised satellite assemblies if it does not find any it will look in the following locations:

  1. The global assembly cache for an assembly matching the requested culture for the application
  2. The directory of the currently executing assembly for a directory matching the requested culture.
  3. The global assembly cache again, this time for the parent assembly of the requested resource.
  4. The runtime next checks the directory of the currently executing assembly to see if it contains a parent directory. If a parent directory exists, the runtime searches the directory for a valid satellite assembly for the parent culture.
  5. The runtime next searches parent assemblies, as in the previous step, through many potential levels.
  6. If a resource is not found then the default culture is used.

Creating Satellite Assemblies

By definition, satellite assemblies can only contain resources. They cannot contain any executable code. Satellite assemblies are compiled from resource files which can be added to a project from the add new item menu.

Resource files can be directly compiled into the executable, however, for scalability, a satellite assembly allows new cultures and languages to be added without recompilation or distribution. Satellite assemblies can be used so that the client downloads and installs a common executable and then just downloads and installs one satellite assembly for their location.

Satellite assemblies are created using the Assembly Linker (al.exe) command line tool.

Use the following command to create a satellite assembly for the application TestApp from the file strings.ja-JP.resources

al /t:lib /embed:strings.ja-JP.resources /culture:ja-JP /out:TestApp.ja-JP.resources.dll

The output filename can be anything since the runtime does not look at the filename to gather the culture, therefore you must specify the culture using the /culture switch.

For more details on how to localise an application please refer to the Resources and Localisation tutorials.

Using Satellite Assemblies

Satellite Assemblies are loaded by the runtime as specified above. To use a satellite assembly and load a culture-specific version use the ResourceManager class. Using this class will load the appropriate assembly for the current culture or a default culture if none are present.

C#
static ResourceManager rm = new ResourceManager("strings", Assembly.GetExecutingAssembly());

This line of code will load the strings resource file from the assembly created above, assuming that ja-JP is the current system locale.

You can access or modify the current culture using the CultureInfo class:

C#
CultureInfo ci = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentUICulture = en.US;

Creating Strong Named Assemblies in Microsoft .Net

Strong Names are a way of uniquely identifying assemblies written for the .Net platform. They are not, as commonly believed, a tool for enabling security.

Strong Names use cryptographic techniques (RSA, El Gamal etc...) together with a hashing algorithm (Md5, SHA, Blowfish etc...) to create a digital signature of an assembly. A strong name also consists of a public/private key pair used to encrypt/decrypt the assembly.

Strong Names allow assemblies to be versioned and authenticated. Signed Assemblies allow the assembly to have a unique identifier which allows multiple versions of an assembly to exist on a machine without colliding.

If you are developing assemblies that will be installed into the Global Assembly Cache (GAC) then the assembly must be strongly named.

The other benefit of creating strongly named assemblies is that the assembly can be authenticated. Because the strong name contains the private key of the author, you can be sure that the assembly was created by a particular author. A strongly named assembly can only reference other strongly named assemblies to ensure integrity.

Signing an Assembly

The first thing that you need to do is create a public/private key pair that will be used to encrypt the assembly.

To create a key/pair you can use the strong name utility:

sn -k <<em>file name</em>>

This will create the file containing the keys defaulting to RSA encryption. You should keep this file safe as you will need it for signing future assemblies if you wish to keep them authenticated.

Now, there are a few different methods for strongly naming an assembly, either from within Visual Studio or through the command line.

If you wish to give developers access to the private key you can simply add an attribute to the project. This code should go within the AssemblyInfo.cs file.

C#
[assembly: AssemblyDelaySign(false)] 
[assembly: AssemblyKeyFile("c:mykey.sn")]

If you do not wish to give your developers access to your private key you can delay signing the assembly.

C#
[assembly: AssemblyDelaySign(true)] 
[assembly: AssemblyKeyFile("c:mykey.sn")]

Then the assembly must have verification turned off otherwise the assembly will not load on the developer's machine:

c:\> sn -Vr myassembly.dll

Finally, when the code is ready to be released you can fully sign the assembly using the sn.exe tool.

c:\> sn -R myassembly.dll mykey.sn
Was this article helpful to you?
 

Related ArticlesThese articles may also be of interest to you

CommentsShare your thoughts in the comments below

If you enjoyed reading this article, or it helped you in some way, all I ask in return is you leave a comment below or share this page with your friends. Thank you.

This post has 3 comment(s). Why not join the discussion!

We respect your privacy, and will not make your email public. Learn how your comment data is processed.

  1. GB

    On Tuesday 13th of March 2012, Gaurav Balyan said

    very nicely and throughly explained

  2. AR

    On Tuesday 1st of November 2011, Arjunan said

    Hi,

    Clear Explanation.Thanks for giving this information with simple Examples...

    Thanks u very much !!!

  3. EX

    On Saturday 26th of March 2011, excellent said

    excellent