Structure of Assemblies


The parts of an assembly provide the means for .NET programs to find out about one another and resolve the references between programs and components.

An assembly contains the executable code for a program or class library, along with metadata (data describing other data), which enables other programs to look up classes, methods, and properties of the objects defined within the assembly. The metadata acts in two ways: as a table of contents, describing what is contained inside the assembly and as a bibliography describing references to data outside the assembly. Let's look at this in more detail.

Single-file .NET assemblies have the following general format, shown in Figure 26-2.

image from book
Figure 26-2

Each assembly contains a manifest, which describes the contents of the assembly (like a manifest for a shipment of goods). The manifest is also called the assembly metadata, because it describes the assembly itself — what modules it contains, what other assemblies it references, and so on. You examine this in more detail later on in this chapter when you view the contents of an assembly you create. Previous component technologies such as COM have no in-built concept like the manifest; the manifest is the heart of the self-description built into .NET assemblies.

the .NET runtime uses the manifest in the program's assembly when executing the program for resolving references to other assemblies, such as System, that contains the Console.WriteLine() method for printing out Hello, World!.

The manifest is followed by the type metadata — the description of the classes, properties, methods, and so on contained within the assembly along with the data types for their parameters and return values. This is followed by the actual binary code for each of the types, stored as machine-independent MSIL code. Finally, there are any resources that form part of the assembly. Resources are nonexecutable parts of your program (specified in .Resources files) such as images, icons, or message files.

Although an assembly often consists of only one file, it can also be composed of several files, as shown in Figure 26-3.

image from book
Figure 26-3

From the .NET runtime's point of view, a multiple-file assembly is a single logical DLL or EXE that just happens to consist of multiple files. Only one file contains the manifest. The manifest points to the other files that make up the multiple-file assembly. The files that contain executable code are called modules; these contain type metadata and MSIL code. There may also be resource files containing no executable code.

Multiple-file assemblies are usually needed only in certain advanced applications. A module or resource is loaded only when it is actually executed or brought into use, so you can save download time and memory when modules and resources that are rarely used are stored in separate files. For example, an application delivered internationally may have modules or resources written in different languages. You would separate these so that only the module for the language actually in use was loaded into memory.

Now that you've seen what assemblies are, you'll create one and look at its properties. First, you need to create a simple class library in C# that you refer to in the rest of this chapter. In the following Try It Out, you make a simple version of the Shapes component imagined in the first part of the chapter.

Try It Out – Creating the Shapes Component

image from book

To create the Shapes class library component that you will use as an example assembly in the rest of this chapter, follow these steps:

  1. Create a new class library project called Shapes in the directory C:\BegVCSharp\Chapter26.

  2. Rename the Class1.cs file created by default to Shapes.cs, and type in the source code shown below. The binary file built from Shapes.cs will be Shapes.dll, and you will use that as the example of an assembly.

  3. Enter the following code into the Shapes namespace:

     namespace Shapes { public class Circle { double Radius; public Circle() { Radius = 0; } public Circle(double givenRadius) { Radius = givenRadius; } public double Area()  { // area = pi r squared return System.Math.PI * (Radius * Radius); } } public class Triangle { double Base; double Height; public Triangle() { Base = 0; Height = 0; } public Triangle(double givenBase, double givenHeight) { Base = givenBase; Height = givenHeight; } public double Area()  { return 0.5F * Base * Height;  // area = 1/2 base * height } } } 

    The code is very simple and obviously not a complete implementation of everything you might want to do with a set of shapes, but it will do for your purposes. The one bit of complexity is that the Circle and Triangle classes each have two constructors, one that takes no parameters and another that takes parameters to initialize the instance variables. You see this again later on when you examine the contents of the assembly that is produced.

  4. Before moving on, build the Shapes project with Build Build Solution (Ctrl-Shift-B).

image from book

Viewing the Contents of an Assembly

Let's view the contents of the assembly you just created using the self-description in the shapes.dll assembly. The tool you can use to view the contents of an assembly is Ildasm, the .NET Framework Intermediate Language Disassembler tool. This is a handy tool for viewing and understanding the internal structure of assemblies and can also be used by the curious to view the contents of the System assemblies. However, it is not something you will need to use in day-to-day development of C# programs; don't feel that you have to memorize the details of using it.

Executing Ildasm from the Visual Studio Command Line

The quickest way to execute Ildasm is to go the Start All Programs Microsoft Visual Studio 2005 Visual Studio Tools Visual Studio Command Prompt and simply enter Ildasm at the command prompt. The Visual Studio 2005 command prompt has the PATH set to the Visual Studio 2005 tools.

Adding Ildasm as an External Tool to Visual Studio 2005

Another way to execute Ildasm is to add it as an external tool to the Visual Studio 2005 development environment; this makes it easier to go back and execute it again without having to leave Visual Studio 2005. To do this, go to the Tools External Tools... menu in Visual Studio 2005. Click the Add button in this dialog. You will see [New Tool 1] in the Menu Contents list and the Title entry box; type in Ildasm in the Title entry box, then click on the browse button (...) to the right of the Command entry box. In the Open dialog that appears, navigate to this path:

C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin

Click on Ildasm.exe in the Bin directory, and then click Open. Ildasm will now appear in the Menu Contents list, as shown in Figure 26-4.

image from book
Figure 26-4

Click OK, and Ildasm will appear as a choice in the Tools menu of Visual Studio 2005.

Now that you have this tool easily available on the Tools menu, you can try it out!

Try It Out – Viewing the Contents of an Assembly with Ildasm

image from book

To try out the Ildasm tool, follow these steps:

  1. Now that Ildasm has been added to Visual Studio 2005, open it by selecting Tools Ildasm. Ildasm will appear in a separate window, as shown in Figure 26-5.

    image from book
    Figure 26-5

  2. Use the File Open menu to find the directory containing Shapes.dll and open it. Shapes.dll will be located in the bin subdirectory of your project (C:\BegVCSharp\Chapter26\Shapes\ Shapes\bin), probably in the Debug subdirectory if you built the default configuration.

  3. Once you have located Shapes.dll, click on the Open button. The view of the assembly then appears in the Ildasm main window, as shown in Figure 26-6.

    image from book
    Figure 26-6

    You can see the manifest and the Shapes class in the display. The manifest is the assembly information; you look at that in a bit. The Shapes shield icon represents the Shapes namespace; it comes from the type metadata for this assembly.

  4. Expand the tree view by clicking on the + sign in front of the Shapes shield icon. You now see the two classes defined in your source file, Circle and Triangle, as shown in Figure 26-7.

    image from book
    Figure 26-7

  5. Expand the view of each class by clicking on the + signs in front of Circle and Triangle, as shown in Figure 26-8.

    image from book
    Figure 26-8

How It Works

Now you begin to see something that corresponds to the source code you created. You can see the class instance variables Radius for the Circle class and Base and Height for the Triangle class, as well as the Area method for both. Since private is the default access modifier for class instance variables such as Radius and Base, this explains why you can see these variables marked with private in the screen- shot in Figure 26-8. You can also see some funny-looking lines of text that seem to be additional to the source code, such as the lines beginning with .ctor and .class.

If you look closely, you notice that the .ctor lines actually correspond to the constructors defined for Circle and Triangle. There are two constructors for each, one that takes no parameters and another that takes parameters to initialize the instance variables. In Circle's case, the constructor with parameters takes one float value, which corresponds to the line

.ctor void(float64)

in the Ildasm display of the assembly. For the Triangle class, the parameterized constructor takes two float parameters. This corresponds to the line:

.ctor void(float64, float64)

The lines beginning with the period are directives in MSIL, which is the language C# code is compiled into for execution in the .NET environment. The the .ctor directive is the MSIL instruction to make a class constructor. You don't need to understand MSIL completely to examine the contents of assemblies; I'll just point out interesting aspects of it as you come across them.

You also see the line at the top that is labeled MANIFEST; let's talk a little bit about that now.

image from book

Manifests

The manifest of an assembly was discussed earlier — it describes each file or module within the assembly (remember that an assembly could consist of multiple files though typically it is just one file).

More importantly, it also describes the external assemblies that are referenced by this assembly. For example, if your program uses System.Data.dll, that fact is reflected in the manifest. This makes it much easier to keep track of the dependencies between assemblies, making deployment and verification of a program's correct installation much easier. The manifest also tracks the version number of the assembly, making upgrading to new versions of programs easier. Let's take a look at the manifest of the assembly you just created.

Double-click on the line labeled MANIFEST at the top of the Ildasm listing for shapes.dll. This opens a new window with the manifest details, as shown in Figure 26-9.

image from book
Figure 26-9

The manifest for Shapes.dll contains three .assembly directives and a number of other directives, including a .module directive. Don't worry about the contents of the .assembly blocks; that is, ignore everything inside the curly braces {} for now. The first line you can see is:

.assembly extern mscorlib 

This is an external assembly reference to the mscorlib.dll, which is the core library for the Microsoft .NET Framework. The most basic System classes in the .NET Framework are defined here. This external reference is also needed for every .NET program. The next assembly directive is:

.assembly extern System

This is an external assembly reference to the System.dll, which is where some (but not all) of the base System classes in the .NET Framework are defined. This external reference is needed for every C# and .NET program that uses the classes in the the System namespace.

The .assembly Shapes line is the declaration of the Shapes assembly itself. This is followed by a .module declaration for the shapes.dll file. Your assembly has just one file, so there is a single .module declaration for that file.

Let's see what happens when an additional reference is added to the source file. Suppose that you wanted to draw a shape using some of the methods from the System.Drawing namespace. Close Ildasm and go back to the Shapes project in Visual Studio 2005. Modify Shapes.cs as follows. First, expand the Using directives region at the top of the Shapes.cs source file add a using directive referencing the System.Drawing namespace:

#region Using directive  using System; using System.Collections.Generic; using System.Text; using System.Drawing; #endregion

Then, add a Draw() method for Circle following the Area() method:

 public void Draw() { Pen p = new Pen(Color.Red); } 

This isn't enough code to actually draw anything, but you can see where it's going!

Now, you need to add a reference to the System.Drawing.dll assembly to the project. If you don't add this reference, you will see the following error:

error CS0234: The type or namespace name 'Drawing' does not exist in the namespace 'System' (are you missing an assembly reference?)

To add a reference in VS2005, select Project Add Reference... from the menu. The Add Reference dialog will appear as shown in Figure 26-10. the .NET tab shows the .NET system assemblies that are available. Scroll down the list, then select the the System.Drawing.dll assembly, as shown in Figure 26-6.

image from book
Figure 26-10

Now press OK to add the reference. This adds the System.Drawing.dll reference to the Shapes.dll file. Save your source file changes, close Ildasm if you did not do so already (otherwise, you'll get a compiler error because Ildasm has Shapes.dll open), and then recompile Shapes.cs with the above changes.

Start Ildasm again and open Shapes.dll. You'll notice the Draw() method under the Circles object, as shown in Figure 26-11.

image from book
Figure 26-11

Double-click on MANIFEST again to see the changes you have made to it. There is now an external assembly reference to the System.Drawing assembly, as shown in Figure 26-12.

image from book
Figure 26-12

The Shapes assembly will inform the system that it requires the System.Drawing assembly whenever Shapes itself is referenced. You look at how another program makes use of the Shapes assembly in just a bit, but after looking at the screen in Figure 26-12, your curiosity about the stuff inside the .assembly directives (that I told you to ignore earlier) is probably getting to be too great. Let's put you out of your misery and discuss that information now.

Assembly Attributes

Besides the external assembly references, the manifest of an assembly contains information that pertains to the assembly itself. These are called assembly attributes.

AssemblyInfo.cs

When building your project, you may have noticed that Visual Studio 2005 creates a second C# source file as part of your project, inside the Projects folder of the Solution Explorer. Expand the Projects folder to see this file, called AssemblyInfo.cs, as shown in Figure 26-13.

image from book
Figure 26-13

AssemblyInfo.cs is used to set properties of the assembly in the manifest. Double-click on the file to open it, and look at the contents (some comments have been removed to save space):

// // General Information about an assembly is controlled through the following  // set of attributes. Change these attribute values to modify the information // associated with an assembly. // [assembly: AssemblyTitle("Shapes")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("My Company, Inc.")] [assembly: AssemblyProduct("Shapes")] [assembly: AssemblyCopyright("Copyright @ My Company, Inc. 2005")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")]     // // Version information for an assembly consists of the following four values: // //      Major Version //      Minor Version  //      Build Number //      Revision // // You can specify all the values or you can default the Revision and  // Build Numbers by using the '*' as      [assembly: AssemblyVersion("1.0.*")] 

Each of the statements in square brackets that looks like [assembly: Assembly...] is an attribute, a special syntax in C# that is covered in more depth in Chapter 27. For our purposes here, it's enough for you to know that each of these statements sets a particular property of the assembly. The word assembly: at the beginning of each attribute tells the system that the directive following is targeted at the assembly itself, not a class, method, or other part of the program.

Visual Studio 2005 supplies this file containing the assembly attributes as a template for you to fill in with the properties you want your assembly to have. Most of the attributes such as title, company, and so on are purely informational and can be filled in with any descriptive value you want associated with your component, such as:

 [assembly: AssemblyTitle("MyCompany Shapes Class Library")] [assembly: AssemblyDescription("Classes for Manipulation of Shapes")] [assembly: AssemblyConfiguration("Enterprise Version")] [assembly: AssemblyCompany("MyCompany, Inc.")] [assembly: AssemblyProduct("Shapes")] [assembly: AssemblyCopyright("Copyright 2005, MyCompany, Inc.")] [assembly: AssemblyTrademark("Shapes is a trademark of MyCompany, Inc.")] 

When a source file containing assembly attributes is built, the attributes are incorporated into the assem bly's manifest. For example, if you change the AssemblyTitle attribute, build, and look again at the manifest for Shapes.dll with Ildasm, you'll see this line within the .assembly Shapes section:

.custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute:: .ctor(string) =     ( 01 00 1E 4D 79 43 6F 6D 70 61 6E 79 20 53 68 61   // ...MyCompany Sha      70 65 73 20 43 6C 61 73 73 20 4C 69 62 72 61 72   // pes Class Librar      79 00 00 )                                        // y..

This line indicates that the assembly contains an AssemblyTitle attribute, and it indicates the value of this attribute.

Assembly Culture

The line following the company and trademark attributes is the AssemblyCulture attribute:

[assembly: AssemblyCulture("")]

This sets the national language used for this assembly (English, French, Chinese, and so on) and if it is specified, it is a special abbreviation following an international standard. For more information, see the System.Globalization namespace and culture topics in the .NET Framework online documentation.

You don't need to set the culture unless you're distributing different language versions of a component; if you are doing this then the .NET runtime will automatically search for the version of your assembly that matches the current culture, so that, for example, in France you display the French messages (using your French message resources and/or code) and in Britain you display English messages (using your English message resources and/or code). You mark the appropriate assembly with the correct culture attribute for this to happen.

Version Numbers

The line following the culture attribute is the AssemblyVersion attribute:

[assembly: AssemblyVersion("1.0.*")]

Before I explain this assembly version attribute specifically, let's examine how assembly version numbers work in .NET. The version for a .NET assembly has four parts, as shown here:

 Major version . Minor version . Build Number . Revision 

The first two parts are probably familiar to you if you are a user of consumer software — they are a major version number and a minor version number, as in Shapes version 1.0 (where 1 is the major version and 0 the minor version).

The next two parts take the versioning to a finer level of detail. The build number indicates which build of the assembly this is; the build number would typically change every time the assembly is rebuilt.

The revision number goes one level deeper and is designed to be used for a patch or a "hot fix" to an assembly that is exactly the same as its predecessor, except for this one bug fix.

Version Attributes

You can see an assembly's version attributes in Ildasm. Look in the manifest of the shapes.dll file, and you will see that each referenced assembly has a .ver directive inside the .assembly block for that assembly.

You may also notice that the version information for the external assembly references, such as the references to the mscorlib and System assemblies, are all the same. This is because the .NET runtime uses assembly version numbers for compatibility checking. I'll talk more about this when I discuss version compatibility.

When looking at an assembly created with Visual Studio 2005, you will see a version number that probably looks something like this:

.ver 1:0:486:7484

Visual Studio 2005 assigns version numbers automatically as projects are built, depending on what information is set in the AssemblyVersion attribute, which you look at next.

Note

Note: If you were to look at the version number of an assembly created outside of VS2005 with the .NET Framework command-line C# compiler (csc), the .ver directive for the shapes assembly would have all zeros in it, like this: .ver 0:0:0:0. This is because the command-line compiler does not automatically create an assemblyinfo.cs file with an AssemblyVersion attribute, as described in the next section. Just another little detail VS2005 takes care of for you!

AssemblyVersion attribute

Within the AssemblyInfo.cs file created by Visual Studio 2005, the version number is set with the AssemblyVersion attribute:

[assembly: AssemblyVersion("1.0.*")] 

The AssemblyVersion attribute allows an asterisk (*) to be specified for the last two parts of the version number. This directs Visual Studio 2005 to set the build and revision numbers automatically. You can also specify the asterisk just for the revision number (as in 1.1.1.*) but not the major and minor version numbers (1.* is not allowed). If you look at the assembly version number with Ildasm, you'll see that the actual version number Visual Studio 2005 sets will be something like:

.ver 1:0:585:24784

If you change some code in your classes and build again; you'll see the number change automatically to something like:

.ver 1:0:585:25005

You can directly set all the parts of the version by specifying a specific number instead of the asterisk:

[assembly: AssemblyVersion("1.0.1.2")]

This will force Visual Studio 2005 to produce an assembly with this specific major, minor, build, and revision number.

If you are just developing a program for your own use, you won't need to set or care about version numbers. However, if you are releasing software to other end users, you will want to change major and minor version numbers as you add significant new functionality to your releases: 1.0 for the first production release, 1.1 for a release introducing minor enhancements, 2.0 for major changes, and so on. It's okay to let Visual Studio 2005 set the build/revision numbers automatically, and you won't even need to be aware of them most of the time.

It is handy to be able to use Ildasm to check the full version number. For example, if an end user reports a bug, you can compare the version of the assembly on your computer and the one installed on the end- user's computer. You can tell exactly which version that user has; with the revision and build numbers you can even distinguish between the build made this morning and the one made just before noon.

Version Compatibility

the .NET runtime checks version numbers when loading assemblies to determine version compatibility. This is done only for shared assemblies, which you look at later in this chapter. However, the way version checking works with version numbers is described here.

You'll recall that the manifest of an assembly contains the version number of the current assembly as well as the version numbers of referenced external assemblies. When the .NET runtime loads a referenced assembly, it checks the version in that assembly's manifest and compares it to the version stored in the reference to make sure the versions are compatible.

If the assemblies have different major or minor version numbers, they are assumed to be incompatible, and the referenced assembly will not load — for example, Shapes version 1.1 is not compatible with a program referencing Shapes version 1.0, 1.2, or 2.0.

What if you have program A that uses Shapes 1.0 and program B that uses Shapes 1.1 on the same system? This is actually taken care of in the .NET runtime; there is a feature called side-by-side execution, which enables Shapes 1.0 and Shapes 1.1 to both be installed on the same computer and each to be available to the programs that need that version.

The next two parts of the version number take the versioning to a finer level of detail. The build number indicates which build of the assembly this is; this typically changes every time the assembly is rebuilt. Two assemblies with the same major/minor version number and a differing build number may or may not be compatible; the runtime assumes that they are compatible so that they are allowed to load. It is up to the developer to make sure to change the major or minor version if incompatible changes are introduced.

You'll notice in earlier examples that the version number of the mscorlib assembly and the System .Drawing assembly were both version 2.0 with a build number of 3600; that is the build number of the .NET System assemblies currently on my system as I write this. If one of these libraries were updated to build number 3302 or even 9999 the .NET runtime would try to use the new build; however, the build number is not guaranteed to be compatible.

The revision number goes one more level, enabling you to specify a very specific patch or fix to a particular build number. 2.0.3600.0 and 2.0.3600.1 are assumed to be totally compatible. In the side-by-side scenario, if the major and minor versions of two coexisting assemblies match and differ only by build number and/or revision, the system will execute the newer assembly (namely, the one with a higher build/revision number).




Beginning Visual C# 2005
Beginning Visual C#supAND#174;/sup 2005
ISBN: B000N7ETVG
EAN: N/A
Year: 2005
Pages: 278

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net