Sie sind auf Seite 1von 16

Assemblies and

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:

Enable developers to specify version rules between different software


components.

Provide the infrastructure to enforce versioning rules.

Provide the infrastructure to allow multiple versions of a component to be run


simultaneously (called side-by-side execution).

Understanding the Format of a .NET Assembly


We have learned about several benefits provided by the .NET assembly, lets shift
gears and get a better idea of how an assembly is composed under the hood. Structurally
speaking, a .NET assembly (*.dll or *.exe) consists of the following elements:
AWin32 file header
A CLR file header
CIL code
Type metadata
An assembly manifest
Optional embedded resources
While the first two elements (the Win32 and CLR headers) are blocks of data that you
can typically ignore, they do deserve some brief consideration. This being said, an
overview of each element follows.
The Win32 file header establishes the fact that the assembly can be loaded and
manipulated by the Windows family of operating systems. This header data also identifies
the kind of application (console- based, GUI-based, or *.dll code library) to be hosted by
the Windows operating system. If you open a .NET assembly using the dumpbin.exe
Page 7

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

As a naming convention, the secondary modules in a multifile assembly take a


*.netmodule file extension; however, this is not a requirement of the CLR. Secondary
*.netmodules also contain CIL code and type metadata, as well as a module-level
manifest, which simply records the externally required assemblies of that specific
module.
The major benefit of constructing multifile assemblies is that they provide a very efficient
way to download content. To begin exploring the world of .NET assemblies, youll first
create a single-file *.dll assembly that contains a small set of public types. To build a
code library using Visual Studio 2008, simply select the Class Library project workspace.
namespace IIBCAssembly
{
public class Class1
{
public void Display()
{
Console.WriteLine("Hi Good Morning from Class1");
}
}
public class Class2
{
public void Details()
{
Console.WriteLine("Hi Good Morning from Class2");
}
}
Page 10

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.

Additional search location.


The common language runtime checks the global assembly cache for an assembly
that matches the assembly request before probing or using the codebase
information in a configuration file.

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

At the command prompt, type the following command:

gacutil I <assembly name>


In this command, assembly name is the name of the assembly to install in the
global assembly cache.
The following example installs an assembly with the file name IIBCAssembly.dll into the
global assembly cache.
gacutil -i IIBCAssembly.dll
Understanding Private Assemblies:
Technically speaking, the assemblies youve created thus far in this chapter have
been deployed as private assemblies. Private assemblies are required to be located within
the same directory as the client application (termed the application directory) or a
subdirectory thereof. Recall that when you set a reference to CarLibrary.dll while
building the IIBC.exe applications, Visual Studio 2008 responded by placing a copy of
IIBCAssembly.dll within the clients application directory (at least, after the first
compilation).
The full identity of a private assembly consists of the friendly name and
numerical version, both of which are recorded in the assembly manifest. The friendly
name simply is the name of the module that contains the assemblys manifest minus the
file extension. Given the isolated nature of a private assembly, it should make sense that
the CLR does not bother to make use of the version number when resolving its location.
The assumption is that private assemblies do not need to have any elaborate version
checking, as the client application is the only entity that knows of its existence. Given
this, it is (very) possible for a single machine to have multiple copies of the same private
assembly in various application directories.
Understanding Shared Assemblies
Now that you understand how to deploy and configure a private assembly, you
can begin to examine the role of a shared assembly. Like a private assembly, a shared
assembly is a collection of types and (optional) resources. The most obvious difference
between shared and private assemblies is the fact that a single copy of a shared assembly
Page 14

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.

Consuming a Shared Assembly


When you are building applications that make use of a shared assembly, the only
difference from consuming a private assembly is in how you reference the library using
Visual Studio 2008. In reality, there is no difference as far as the tool is concerned (you
still make use of the Add Reference dialog box). What you must understand is that this
dialog box will not allow you to reference the assembly by browsing to the
C:\Windows\Assembly folder. Any efforts to do so will be in vain, as you cannot
reference the assembly you have highlighted.
When you need to reference an assembly that has been deployed to the GAC, you will
need to browse to the \bin\Debug directory of the original project via the Browse tab.
Points to Remember
An assembly is a collection of types and resources that are built to work together
and form a logical unit of functionality.
Namespace scope lets you organize code and gives you a way to create globally
unique types.
When another namespace wishes to use objects within a distinct namespace, the
using keyword can be used.
When organizing your types, you are free to define namespaces within other
namespaces(Nesting of Namespaces).
Win32 file header establishes the fact that the assembly can be loaded and
manipulated by the Windows family of operating systems.
The CLR header is a block of data that all .NET files must support in order to be
hosted by the CLR.
Page 15

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

Das könnte Ihnen auch gefallen