Assemblies
An assembly is the
logical unit that contains compiled code targeted at the .NET
Framework. Assemblies are not covered in great detail in this
chapter because they are covered in detail in Chapter
16, “Assemblies,” but we summarize the main points
here.
An assembly is completely self-describing and is a
logical rather than a physical unit, which means that it can be
stored across more than one file (indeed dynamic assemblies are
stored in memory, not on file at all). If an assembly is stored in
more than one file, there will be one main file that contains the
entry point and describes the other files in the assembly.
Note that the same assembly structure is used for
both executable code and library code. The only real difference is
that an executable assembly contains a main program entry point,
whereas a library assembly doesn’t.
An important characteristic of assemblies is that
they contain metadata that describes the types and methods defined
in the corresponding code. An assembly, however, also contains
assembly metadata that describes the assembly itself. This assembly
metadata, contained in an area known as the manifest, allows checks to be made on the version of
the assembly, and on its integrity.
|
|
Tip |
ildasm, a Windows-based utility, can be used
to inspect the contents of an assembly, including the manifest and
metadata. ildasm is discussed in Chapter 16, “Assemblies.”
|
The fact that an assembly contains program metadata
means that applications or other assemblies that call up code in a
given assembly do not need to refer to the registry, or to any
other data source, in order to find out how to use that assembly.
This is a significant break from the old COM way of doing things,
in which the GUIDs of the components and interfaces had to be
obtained from the registry, and in some cases, the details of the
methods and properties exposed would need to be read from a type
library.
Having data spread out in up to three different
locations meant there was the obvious risk of something getting out
of synchronization, which would prevent other software from being
able to use the component successfully. With assemblies, there is
no risk of this happening, because all the metadata is stored with
the program executable instructions. Note that even though
assemblies are stored across several files, there are still no
problems with data going out of synchronization. This is because
the file that contains the assembly entry point also stores details
of, and a hash of, the contents of the other files, which means
that if one of the files gets replaced, or in any way tampered
with, this will almost certainly be detected and the assembly will
refuse to load.
Assemblies come in two types: shared and private
assemblies.
Private Assemblies
Private assemblies are the simplest type.
They normally ship with software and are intended to be used only
with that software. The usual scenario in which you will ship
private assemblies is when you are supplying an application in the
form of an executable and a number of libraries, where the
libraries contain code that should only be used with that
application.
The system guarantees that private assemblies will
not be used by other software, because an application may only load
private assemblies that are located in the same folder that the
main executable is loaded in, or in a subfolder of it.
Because you would normally expect that commercial
software would always be installed in its own directory, this means
that there is no risk of one software package overwriting,
modifying, or accidentally loading private assemblies intended for
another package. Because private assemblies can be used only by the
software package that they are intended for, this means that you
have much more control over
what software uses them. There is, therefore, less need to take
security precautions because there is no risk, for example, of some
other commercial software overwriting one of your assemblies with
some new version of it (apart from the case where software is
designed specifically to perform malicious damage). There are also
no problems with name collisions. If classes in your private
assembly happen to have the same name as classes in someone else’s
private assembly, that doesn’t matter, because any given
application will only be able to see the one set of private
assemblies.
Because a private assembly is entirely
self-contained, the process of deploying it is simple. You simply
place the appropriate file(s) in the appropriate folder in the file
system (no registry entries need to be made). This process is known
as zero impact (xcopy) installation.
Shared Assemblies
Shared assemblies are intended to be common
libraries that any other application can use. Because any other
software can access a shared assembly, more precautions need to be
taken against the following risks:
-
Name collisions, where another company’s
shared assembly implements types that have the same names as those
in your shared assembly. Because client code can theoretically have
access to both assemblies simultaneously, this could be a serious
problem.
-
The risk of an assembly being overwritten by
a different version of the same assembly - the new version being
incompatible with some existing client code.
The solution to these problems involves placing
shared assemblies in a special directory subtree in the file
system, known as the global assembly cache
(GAC). Unlike with private assemblies, this cannot be done by
simply copying the assembly into the appropriate folder - it needs
to be specifically installed into the cache. This process can be
performed by a number of .NET utilities and involves carrying out
certain checks on the assembly, as well as setting up a small
folder hierarchy within the assembly cache that is used to ensure
assembly integrity.
To avoid the risk of name collisions, shared
assemblies are given a name based on private key cryptography
(private assemblies are simply given the same name as their main
file name). This name is known as a strong
name, is guaranteed to be unique, and must be quoted by
applications that reference a shared assembly.
Problems associated with the risk of
overwriting an assembly are addressed by specifying version
information in the assembly manifest and by allowing side-by-side
installations.
Reflection
Because assemblies store metadata, including
details of all the types and members of these types that are
defined in the assembly, it is possible to access this metadata
programmatically. Full details of this are given in Chapter
12, “Reflection.” This technique, known as reflection, raises interesting possibilities,
because it means that managed code can actually examine other
managed code, or can even examine itself, to determine information
about that code. This is most commonly used to obtain the details
of attributes, although you can also use reflection, among other
purposes, as an indirect way of instantiating classes or calling
methods, given the names of those classes on methods as strings. In
this way, you could select classes to instantiate methods to call
at runtime, rather than compile time, based on user input (dynamic
binding).