Beruflich Dokumente
Kultur Dokumente
Versioning
Chapter 14:
Versioning
Assemblies
and
Objectives
This chapter explains in detail about
What is an Assembly and How assembly is the solution for Versioning Problems
What is the Format of .Net Assembly
What is Metadata, Manifest and how CLR will use them
What are Private and Shared Assemblies
How to make a Private Assembly as Shared Assembly
What are Single-file and Multi-file Assemblies
Page 1
Assemblies and
Versioning
Introduction
Assemblies are the building blocks of .NET Framework applications; they form
the fundamental unit of deployment, version control, reuse, activation scoping, and
security permissions. An assembly is a collection of types and resources that are built to
work together and form a logical unit of functionality. An assembly provides the common
language runtime with the information it needs to be aware of type implementations.
Defining Custom Namespaces
The namespace keyword is used to declare a scope. This namespace scope lets
you organize code and gives you a way to create globally unique types.
Before diving into the details of assembly deployment and configuration, it is
very important to examine the topic of creating custom .NET namespaces. Up to this
point in the text, you have been building small test programs leveraging existing
namespaces in the .NET universe (System in particular). However, when you build your
own custom applications, it can be very helpful to group your related types into custom
namespaces. In C#, this is accomplished using the namespace keyword. This is even
more important when creating .NET *.dll assemblies, as other developers will need to
import your custom namespaces to make use of your types.
Assume you are developing a collection of geometric classes named Square,
Circle, and Hexagon. Given their similarities, you would like to group them all together
into a common custom namespace. You have two basic approaches. First, you may
choose to define each class within a single file (ShapesLib.cs) as follows:
// shapeslib.cs
using System;
namespace MyShapes
{
// Circle class
class Circle{ /* Interesting methods... */ }
// Hexagon class
class Hexagon{ /* More interesting methods... */ }
// Square class
class Square{ /* Even more interesting methods... */ }
}
Notice how the MyShapes namespace acts as the conceptual container of these
types. Alternatively, you can split a single namespace into multiple C# files. To do so,
simply wrap the given class definitions in the same namespace:
Page 2
Assemblies and
Versioning
// circle.cs
using System;
namespace MyShapes
{
// Circle class
class Circle{ }
}
// hexagon.cs
using System;
namespace MyShapes
{
// Hexagon class
class Hexagon{ }
}
// square.cs
using System;
namespace MyShapes
{
// Square class
class Square{ }
}
When another namespace wishes to use objects within a distinct namespace, the using
keyword can be used as follows:
// Make use of types defined the MyShape namespace.
using System;
using MyShapes;
namespace MyApp
{
class ShapeTester
{
static void Main(string[] args)
{
Hexagon h = new Hexagon();
Circle c = new Circle();
Square s = new Square();
}
Page 3
Assemblies and
Versioning
}
}
A Types Fully Qualified Name
Technically speaking, you are not required to make use of the C# using keyword
when declaring a type defined in an external namespace. You could make use of the fully
qualified name of the type, which as you recall from Chapter 1 is the types name
prefixed with the defining namespace:
// Note we are not "using" MyShapes anymore.
using System;
namespace MyApp
{
class ShapeTester
{
static void Main(string[] args)
{
MyShapes.Hexagon h = new MyShapes.Hexagon();
MyShapes.Circle c = new MyShapes.Circle();
MyShapes.Square s = new MyShapes.Square();
}
}
}
Typically there is no need to use a fully qualified name. Not only does it require a
greater number of keystrokes, but also it makes no difference whatsoever in terms of code
size or execution speed. In fact, in CIL code, types are always defined with the fully
qualified name. In this light, the C# using keyword is simply a typing time-saver.
However, fully qualified names can be very helpful (and sometimes necessary) to
avoid name clashes that may occur when using multiple namespaces that contain
identically named types. Assume you have a new namespace termed My3DShapes, which
defines three classes capable of rendering a shape in stunning 3D:
// Another shapes namespace...
using System;
namespace My3DShapes
{
// 3D Circle class
Page 4
Assemblies and
Versioning
class Circle{ }
// 3D Hexagon class
class Hexagon{ }
// 3D Square class
class Square{ }
}
If you update ShapeTester as was done here, you are issued a number of compile-time
errors, because both namespaces define identically named types:
// Ambiguities abound!
using System;
using MyShapes;
using My3DShapes;
namespace MyApp
{
class ShapeTester
{
static void Main(string[] args)
{
// Which namespace do I reference?
Hexagon h = new Hexagon(); // Compiler error!
Circle c = new Circle(); // Compiler error!
Square s = new Square(); // Compiler error!
}
}
}
The ambiguity can be resolved using the types fully qualified name:
// We have now resolved the ambiguity.
static void Main(string[] args)
{
My3DShapes.Hexagon h = new My3DShapes.Hexagon();
My3DShapes.Circle c = new My3DShapes.Circle();
MyShapes.Square s = new MyShapes.Square();
}
Defining using Aliases
Page 5
Assemblies and
Versioning
The C# using keyword can also be used to create an alias to a types fully
qualified name. When you do so, you are able to define a token that is substituted with
the types full name at compile time, for example:
using System;
using MyShapes;
using My3DShapes;
// Resolve the ambiguity using a custom alias.
using The3DHexagon = My3DShapes.Hexagon;
namespace MyApp
{
class ShapeTester
{
static void Main(string[] args)
{
// This is really creating a My3DShapes.Hexagon type.
The3DHexagon h2 = new The3DHexagon();
...
}
}
}
This alternative using syntax can also be used to create an alias to a lengthy namespace.
Versioning Problems
Currently two versioning problems occur with Win32 applications:
The first one is that versioning rules are enforced by the operating system not
between the pieces of an application. Backward compatibility between the new
piece of code and the old one is the current approach of versioning and this is hard
to maintain in most applications. Beside that only a single version of an
application is allowed to be present and executing on a computer at any given
time.
There is no way to maintain consistency between sets of components that are built
together and the set that is present at run time.
Page 6
Assemblies and
Versioning
These two versioning problems combine to create DLL conflicts, where installing one
application can inadvertently break an existing application because a certain software
component or DLL was installed that was not fully backward compatible with a previous
version. Once this situation occurs, there is no support in the system for diagnosing and
fixing the problem.
The Assembly Solution
To solve versioning problems, as well as the remaining problems that lead to DLL
conflicts, the runtime uses assemblies to do the following:
Assemblies and
Versioning
utility (via a Visual Studio 2008 command prompt) and specify the /headers flag, you can
view an assemblys Win32 header information.
The CLR header is a block of data that all .NET files must support (and do
support, courtesy of the C# compiler) in order to be hosted by the CLR. In a nutshell, this
header defines numerous flags that enable the runtime to understand the layout of the
managed file. For example, flags exist that identify the location of the metadata and
resources within the file, the version of the runtime the assembly was built against, the
value of the (optional) public key, and so forth. If you supply the /clr header flag to
dumpbin.exe, you are presented with the internal CLR header information for a given
.NET assembly.
Again, as a .NET developer you will not need to concern yourself with the gory details of
Win32 or CLR header information (unless perhaps you are building a compiler for a new
managed language!). Just understand that every .NET assembly contains this data, which
is used behind the scenes by the .NET runtime and Win32 operating system.
CIL Code, Type Metadata, and the Assembly Manifest
At its core, an assembly contains CIL code, which as you recall is a platform- and
CPU-agnostic intermediate language. At runtime, the internal CIL is compiled on the fly
(using a just-in-time [JIT] compiler) to platform- and CPU-specific instructions. Given
this architecture, .NET assemblies can indeed execute on a variety of architectures,
devices, and operating systems.
An assembly also contains metadata that completely describes the format of the
contained types as well as the format of external types referenced by this assembly.
The .NET runtime uses this metadata to resolve the location of types (and their members)
within the binary, lay out types in memory, and facilitate remote method invocations.
Youll check out the details of the .NET metadata format during our examination of
reflection services.
An assembly must also contain an associated manifest (also referred to as
assembly metadata). The manifest documents each module within the assembly,
establishes the version of the assembly, and also documents any external assemblies
referenced by the current assembly. As you will see over the course of this chapter, the
CLR makes extensive use of an assemblys manifest during the process of locating
external assembly references.
An assembly can have two types of versions. The first one which we call "Version
Number" consists of a four-part string with the following format:
Page 8
Assemblies and
Versioning
<Major Version>.<Minor Version>.<Build Number>.<Revision Number>
For example a version number of 3.5.20.1 indicates 3 as the major version, 5 as the minor
version, 20 as the build number, and 1 as the revision number.
Note that the version number is stored in the assembly's manifest.
Single-File and Multifile Assemblies
An assembly can be composed of multiple modules. A module is really a generic
term for a valid .NET binary file. In most situations, an assembly is in fact composed of a
single module. In this case, there is a one-to-one correspondence between the (logical)
assembly and the underlying (physical) binary (hence the term single-file assembly).
Single-file assemblies contain all of the necessary elements (header information, CIL
code, type metadata, manifest, and required resources) in a single *.exe or *.dll package.
A multifile assembly, on the other hand, is a set of .NET *.dlls that are deployed
and versioned as a single logic unit. Formally speaking, one of these *.dlls is termed the
primary module and contains the assembly-level manifest (as well as any necessary CIL
code, metadata, header information, and optional resources).
Page 9
Assemblies and
Versioning
Assemblies and
Versioning
}
Building a C# Client Application
Because each of the IIBCAssembly types has been declared using the public
keyword, other assemblies are able to make use of them. Recall that you may also define
types using the C# internal keyword.
To consume these types, create a new C# Console Application project (IIBCClient). Once
you have done so, set a reference to IIBCAssembly.dll using the Browse tab of the Add
Reference dialog box. At this point you can build your client application to make use of
the external types. Update your initial C# file as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IIBCAssembly;
namespace IIBCClient
{
class Program
{
static void Main(string[] args)
{
Class1 Obj1 = new Class1();
Obj1.Display();
Class2 Obj2 = new Class2();
Obj2.Details();
}
}
}
This code looks just like the code of the other applications developed thus far in
the text. The only point of interest is that the C# client application is now making use of
types defined within a separate custom assembly.
It is also important to point out that Visual Studio 2008 has also placed a copy of
IIBCAssembly.dll into the \bin\Debug folder of the IIBCClient project folder. This can be
verified by clicking the Show All Files button of the Solution Explorer.
Page 11
Assemblies and
Versioning
Global Assembly Cache:
Each computer where the common language runtime is installed has a machinewide code cache called the global assembly cache. The global assembly cache stores
assemblies specifically designated to be shared by several applications on the computer.
You should share assemblies by installing them into the global assembly cache
only when you need to. As a general guideline, keep assembly dependencies private, and
locate assemblies in the application directory unless sharing an assembly is explicitly
required.
Assemblies deployed in the global assembly cache must have a strong name.
When an assembly is added to the global assembly cache, integrity checks are performed
on all files that make up the assembly. The cache performs these integrity checks to
ensure that an assembly has not been tampered with, for example, when a file has
changed but the manifest does not reflect the change.
Strong-Named Assemblies
A strong name consists of the assembly's identity its simple text name, version
number, and culture information (if provided) plus a public key and a digital signature.
When you reference a strong-named assembly, you expect to get certain benefits, such as
versioning and naming protection. If the strong-named assembly then references an
assembly with a simple name, which does not have these benefits, you lose the benefits
you would derive from using a strong-named assembly and revert to DLL conflicts.
Therefore, strong-named assemblies can only reference other strong-named assemblies.
There are several reasons why you might want to install an assembly into the global
assembly cache:
Shared location.
Assemblies that should be used by applications can be put in the global assembly
cache. For example, if all applications should use an assembly located in the
global assembly cache, a version policy statement can be added to the
Machine.config file that redirects references to the assembly.
File security.
Administrators often protect the WINNT directory using an Access Control List
(ACL) to control write and execute access. Because the global assembly cache is
installed in the WINNT directory, it inherits that directory's ACL. It is
Page 12
Assemblies and
Versioning
recommended that only users with Administrator privileges be allowed to delete
files from the global assembly cache.
Side-by-side versioning.
Multiple copies of assemblies with the same name but different version
information can be maintained in the global assembly cache.
Strong-naming an Assembly
In Visual Studio 2008 you can easily do this with fewer easy steps :
1- Right click on the project which needs to be signed and choose properties.
2- Choose Signing from the left panel.
3- Check the Sign the assembly checkbox and choose new if you want to generate a
new key.
4- A dialog will pop up type the key name and whether you need to protect it with
password or not.
5- Save the settings and build your solution again.
To sign an assembly with a strong name using attributes
In solution Explorer under properties, in AssemblyInfo.cs file add the
AssemblyKeyFileAttribute or the AssemblyKeyNameAttribute, specifying the
name of the file or container that contains the key pair to use when signing the
assembly with a strong name.
[assembly:AssemblyKeyFileAttribute(@"sgKey.snk")]
Once if this step is completed, go to the project folder and open \bin\Debug. Drag and
drop the IIBCAssembly.dll into C:\Windows\Assembly folder. Now we can reference this
assembly in our applications from GAC.
Page 13
Assemblies and
Versioning
You can use Gacutil.exe to add strong-named assemblies to the global assembly cache
and to view the contents of the global assembly cache.
To install a strong-named assembly into the global assembly cache
Assemblies and
Versioning
can be used by several applications on a single machine. Consider all the applications
created in this text that required you to set a reference to system. Windows.Forms.dll. If
you were to look in the application directory of each of these clients, you would not find
a private copy of this .NET assembly. The reason is that System.Windows.Forms.dll has
been deployed as a shared assembly. Clearly, if you need to create a machine-wide class
library, this is the way to go.
As suggested in the previous paragraph, a shared assembly is not deployed within the
same directory as the application making use of it. Rather, shared assemblies are installed
into the GAC.
The GAC is located under a subdirectory of your Windows directory named
Assembly.
Assemblies and
Versioning
Metadata describes the format of the contained types as well as the format of
external types referenced by the assembly.
The manifest documents each module within the assembly, establishes the
version of the assembly, and also documents any external assemblies referenced
by the current assembly.
Single-file assemblies contain all of the necessary elements in a single *.exe or
*.dll package.
A multifile assembly, is a set of .NET *.dlls that are deployed and versioned as a
single logic unit.
The global assembly cache stores assemblies specifically designated to be shared
by several applications on the computer.
Check YourSelf
Exercise
Explain about Metadata and How CLR will use Assembly during the execution
time of Project.
Explain about Manifest, its structure and components in it.
Explain about Strong named assemblies and its advantages.
Explain about Single file and Multi file assemblies.
Create an assembly, assign strong name to it using GACutil and load it into GAC.
Create two versions for the assembly you created above and store those two
version inside GAC.
Page 16