DekGenius.com
[ Team LiB ] Previous Section Next Section

4.1 Deployment Options

For a simple program like hello.exe that we built in Chapter 2, deployment is easy: copy the assembly into a directory, and it's ready to run. When you want to uninstall it, remove the file from the directory. However, when you want to share components with other applications, you've got to do some work.

In COM, you must store activation and marshaling[2] information in the registry for components to interoperate; as a result, any COM developer can discuss at length the pain and suffering inherent in COM and the system registry. In .NET, the system registry is no longer necessary for component integration.

[2] Distributed applications require a communication layer to assemble and disassemble application data and network streams. This layer is formally known as a marshaler in Microsoft terminology. Assembling and disassembling an application-level protocol network buffer are formally known as marshaling and unmarshaling, respectively.

In the .NET environment, components can be private, meaning that they are unpublished and used by known clients, or shared, meaning that they are published and can be used by any clients. This section discusses several options for deploying private and shared components.

4.1.1 Private Components

If you have private components that are used only by specific clients, you have two deployment options. You can store the private components and the clients that use these components in the same directory, or you can store the components in a component-specific directory that the client can access. Since these clients use the exact private components that they referenced at build time, the CLR doesn't support version checking or enforce version policies on private components.

To install your applications in either of these cases, perform a simple xcopy of your application files from the source installation directory to the destination directory. When you want to remove the application, remove these directories. You don't have to write code to store information into the registry, so there's no worrying about whether you've missed inserting a registry setting for correct application execution. In addition, because nothing is stored in the registry, you don't have to worry about registry residues.

4.1.1.1 One-directory deployment

For simplicity, you can place supporting assemblies in the same directory as the client application. For example, in Chapter 3, we placed the vehicle, car, and plane assemblies in the same directory as the client application, drive.exe. Since both the client application and supporting assemblies are stored within the same directory, the CLR has no problem resolving this reference at runtime (i.e., find and load plane.dll and activate the Plane class). If you move any of the DLLs to a different directory (e.g., put vehicle.dll in c:\temp), you will get an exception when you execute drive.exe. This is because the CLR looks for the vehicle assembly in the following order:

  1. It looks for a file called vehicle.dll within the same directory as drive.exe.

  2. Assuming that the CLR hasn't found vehicle.dll, it looks for vehicle.dll in a subdirectory with the same name as the assembly name (i.e., vehicle).

  3. Assuming that the CLR hasn't found the vehicle assembly, it looks for a file called vehicle.exe in the same directory as drive.exe.

  4. Assuming that the CLR hasn't found vehicle.exe, it looks for vehicle.exe in a subdirectory with the same name as the assembly name (i.e., vehicle).

  5. At this point, the CLR throws and exception, indicating that it has failed to find the vehicle assembly.

The search for other supporting assemblies, such as car and plane, follows the same order. In steps 2 and 4, the CLR looks for the supporting assemblies in specific subdirectories. Let's investigate an example of this in the following section, which we've called multiple-directory deployment.

4.1.1.2 Multiple-directory deployment

Instead of storing all assemblies in the same directory as your client application, you can also use multiple, private subdirectories to segregate your assemblies so that they are easier to find and manage. For example, we will separate the vehicle, car, and plane assemblies into their own private directories, as shown in Figure 4-1. We will leave the drive.exe application in the top directory, MultiDirectories.

Figure 4-1. Multiple-directory tree of components
figs/nfe3_0401.gif

When you build the vehicle assembly, you don't have to do anything special, as it doesn't reference or use any third-party assemblies. However, when you build the car or plane assembly, you must refer to the correct vehicle component (i.e., the one in the vehicle directory). For example, to build the plane assembly successfully, you must explicitly refer to vehicle.dll using a specific or relative path, as shown in the following command (cd to the plane directory):

csc /r:..\vehicle\vehicle.dll /t:library /out:plane.dll plane.cs

You can build the car assembly the same way you build the plane assembly. To compile your client application, you must also refer to your dependencies using the correct paths (cd to the main directory, MultiDirectories, before you type this command all on one line):

vjc /r:vehicle\vehicle.dll;car\car.dll;plane\plane.dll 
    /t:exe /out:drive.exe drive.jsl

Based on the previously discussed search algorithm, the CLR can find the supporting assemblies within the appropriate subdirectories.

4.1.2 Shared Components

Unlike application-private assemblies, shared assemblies—ones that can be used by any client application—must be published or registered in the system Global Assembly Cache (GAC). When you register your assemblies against the GAC, they act as system components, such as a system DLL that every process in the system can use. A prerequisite for GAC registration is that the component must possess originator and version information. In addition to other metadata, these two items allow multiple versions of the same component to be registered and executed on the same machine. Again, unlike COM, you don't have to store any information in the system registry for clients to use these shared assemblies.

There are three general steps to registering your shared assemblies against the GAC:

  1. Use the shared named (sn.exe) utility to obtain a public/private key pair. This utility generates a random key pair for you and saves the key information in an output file—for example, originator.key.

  2. Build your assembly with an assembly version number and the key information from originator.key.

  3. Use the .NET Global Assembly Cache Utility (gacutil.exe) to register your assembly in the GAC. This assembly is now a shared assembly and can be used by any client.

The commands that we use in this section refer to relative paths, so if you're following along, make sure that you create the directory structure, as shown in Figure 4-2. The vehicle, plane, and car directories hold their appropriate assemblies, and the key directory holds the public/private key pair that we will generate in a moment. The car-build directory holds a car assembly with a modified build number, and the car-revision directory holds a car assembly with a modified revision number.

Figure 4-2. Directory structure for examples in this section
figs/nfe3_0402.gif
4.1.2.1 Generating a random key pair

We will perform the first step once and reuse the key pair for all shared assemblies that we build in this section. We're doing this for brevity only because you can use different key information for each assembly, or even each version, that you build. Here's how to generate a random key pair (be sure to issue this command in the key directory):

sn -k originator.key

The -k option generates a random key pair and saves the key information into the originator.key file. We will use this file as input when we build our shared assemblies. Let's now examine steps 2 and 3 of registering your shared assemblies against the GAC.

4.1.2.2 Making the vehicle component a shared assembly

In order to add version and key information into the vehicle component (developed using Managed C++), we need to make some minor modifications to vehicle.cpp, as follows:

#using<mscorlib.dll>
using namespace System;

using namespace System::Reflection;
[assembly:AssemblyVersion("1.0.0.0")];
[assembly:AssemblyKeyFile("..\\key\\originator.key")]; 

public _  _gc _  _interface ISteering
{
  void TurnLeft(  );
  void TurnRight(  );
};

public _  _gc class Vehicle : public ISteering  
{
  public:

    virtual void TurnLeft(  )
    {
      Console::WriteLine("Vehicle turns left."); 
    }

    virtual void TurnRight(  )
    {
      Console::WriteLine("Vehicle turn right."); 
    }

    virtual void ApplyBrakes(  ) = 0; 
};

The first boldface line indicates that we're using the Reflection namespace, which defines the attributes that the compiler will intercept to inject the correct information into our assembly manifest. (For a discussion of attributes, see Section 4.3.1 later in this chapter.) We use the AssemblyVersion attribute to indicate the version of this assembly, and we use the AssemblyKeyFile attribute to indicate the file containing the key information that the compiler should use to derive the public-key-token value, to be revealed in a moment.

Once you've done this, you can build this assembly using the following commands, which you've seen before:

cl /CLR /c vehicle.cpp
link -dll /out:vehicle.dll vehicle.obj

After you've built the assembly, you can use the .NET GAC Utility to register this assembly into the GAC, as follows:

gacutil.exe /i vehicle.dll

Successful registration against the cache turns this component into a shared assembly. A version of this component is copied into the GAC so that even if you delete this file locally, you will still be able to run your client program.[3]

[3] However, don't delete the file now because we need it to build the car and plane assemblies.

4.1.2.3 Making the car component a shared assembly

In order to add version and key information into the car component, we need to make some minor modifications to car.vb, as follows:

Imports System

Imports System.Reflection
<Assembly:AssemblyVersion("1.0.0.0")>
<assembly:AssemblyKeyFile("..\\key\\originator.key")>

Public Class Car
  Inherits Vehicle

  Overrides Public Sub TurnLeft(  )
    Console.WriteLine("Car turns left.")
  End Sub

  Overrides Public Sub TurnRight(  )
    Console.WriteLine("Car turns right.")
  End Sub

  Overrides Public Sub ApplyBrakes(  )
    Console.WriteLine("Car trying to stop.")

    Console.WriteLine("ORIGINAL VERSION - 1.0.0.0.")

    throw new Exception("Brake failure!")
  End Sub

End Class

Having done this, you can now build it with the following command:

vbc /r:..\vehicle\vehicle.dll /t:library /out:car.dll car.vb

Once you've built this component, you can register it against the GAC:

gacutil /i car.dll

At this point, you can delete car.dll in the local directory because it has been registered in the GAC.

4.1.2.4 Making the plane component a shared assembly

In order to add version and key information into the plane component, we need to make some minor modifications to plane.cs, as follows:

using System;

using System.Reflection;
[assembly:AssemblyVersion("1.0.0.0")]
[assembly:AssemblyKeyFile("..\\key\\originator.key")]

public class Plane : Vehicle 
{
  override public void TurnLeft(  ) 
  {
    Console.WriteLine("Plane turns left.");
  }

  override public void TurnRight(  )
  {
    Console.WriteLine("Plane turns right.");
  }

  override public void ApplyBrakes(  )
  {
    Console.WriteLine("Air brakes being used.");
  }
}

Having done this, you can build the assembly with the following commands:

csc /r:..\vehicle\vehicle.dll /t:library /out:plane.dll plane.cs 
gacutil /i plane.dll

Of course, the last line in this snippet simply registers the component into the GAC.

4.1.2.5 Viewing the GAC

Now that we've registered all our components into the GAC, let's see what the GAC looks like. Microsoft has shipped a shell extension, the Shell Cache Viewer, to make it easier for you to view the GAC. On our machines, the Shell Cache Viewer appears when we navigate to C:\WINDOWS\Assembly, as shown in Figure 4-3.[4]

[4] This path is entirely dependent upon the %windir% setting on your machine.

Figure 4-3. Our shared assemblies in the GAC
figs/nfe3_0403.gif

As you can see, the Shell Cache Viewer shows that all our components have the same version number because we used 1.0.0.0 as the version number when we built our components. Additionally, it shows that all our components have the same public-key-token value because we used the same key file, originator.key.

4.1.2.6 Building and testing the drive.exe

You should copy the previous drive.jsl source-code file into the Shared Assemblies directory, the root of the directory structure (shown in Figure 4-2) we are working with in this section. Having done this, you can build this component as follows (remember to type everything on one line):

vjc /r:vehicle\vehicle.dll;car\car.dll;plane\plane.dll 
    /t:exe /out:drive.exe drive.jsl

Once you've done this, you can execute the drive.exe component, which will use the vehicle.dll, car.dll, and plane.dll assemblies registered in the GAC. You should see the following as part of your output:

ORIGINAL VERSION - 1.0.0.0.

To uninstall these shared components (assuming that you have administrative privileges), select the appropriate assemblies and press the Delete key (but if you do this now, you must reregister these assemblies because we'll need them in the upcoming examples). When you do this, you've taken all the residues of these components out of the GAC. All that's left is to delete any files that you've copied over from your installation diskette—typically, all you really have to do is recursively remove the application directory.

4.1.2.7 Adding new versions

Unlike private assemblies, shared assemblies can take advantage of the rich versioning policies that the CLR supports. Unlike earlier OS-level infrastructures, the CLR enforces versioning policies during the loading of all shared assemblies. By default, the CLR loads the assembly with which your application was built, but by providing an application configuration file, you can command the CLR to load the specific assembly version that your application needs. Inside an application configuration file, you can specify the rules or policies that the CLR should use when loading shared assemblies on which your application depends.

Let's make some code changes to our car component to demonstrate the default versioning support. Remember that Version 1.0.0.0 of our car component's ApplyBrakes( ) method throws an exception, as follows:

Overrides Public Sub ApplyBrakes(  )
  Console.WriteLine("Car trying to stop.")

  Console.WriteLine("ORIGINAL VERSION - 1.0.0.0.")

  throw new Exception("Brake failure!")
End Sub

Let's create a different build to remove this exception. To do this, make the following changes to the ApplyBrakes( ) method (store this source file in the car-build directory):

Overrides Public Sub ApplyBrakes(  )
  Console.WriteLine("Car trying to stop.")

  Console.WriteLine("BUILD NUMBER change - 1.0.1.0.")

End Sub

In addition, you need to change the build number in your code as follows:

<Assembly:AssemblyVersion("1.0.1.0")>

Now build this component, and register it using the following commands:

vbc /r:..\vehicle\vehicle.dll 
    /t:library /out:car.dll car.vb 
gacutil /i car.dll

Notice that we've specified that this version is 1.0.1.0, meaning that it's compatible with Version 1.0.0.0. After registering this assembly with the GAC, execute your drive.exe application, and you will see the following statement as part of the output:

ORIGINAL VERSION - 1.0.0.0.

This is the default behavior—the CLR will load the version of the assembly with which your application was built. And just to prove this statement further, suppose that you provide Version 1.0.1.1 by making the following code changes (store this version in the car-revision directory):

Overrides Public Sub ApplyBrakes(  )
  Console.WriteLine("Car trying to stop.")

  Console.WriteLine("REVISION NUMBER change - 1.0.1.1.")

End Sub

<Assembly:AssemblyVersion("1.0.1.1")>

This time, instead of changing the build number, you're changing the revision number, which should still be compatible with the previous two versions. If you build this assembly, register it against the GAC and execute drive.exe again; you will get the following statement as part of your output:

ORIGINAL VERSION - 1.0.0.0.

Again, the CLR chooses the version with which your application was built.

As shown in Figure 4-4, you can use the Shell Cache Viewer to verify that all three versions exist on the system simultaneously. This implies that the support exists for side-by-side execution—which terminates DLL Hell in .NET.

Figure 4-4. Multiple versions of the same shared assembly
figs/nfe3_0404.gif

If you want your program to use a different, compatible version of the car assembly, you have to provide an application configuration file. The name of an application configuration file is composed of the physical executable name and ".config" appended to it. For example, since our client program is named drive.exe, its configuration file must be named drive.exe.config.

Here's a drive.exe.config file that allows you to tell the CLR to load Version 1.0.1.0 of the car assembly for you (instead of loading the default Version, 1.0.0.0). The two boldface attributes say that although we built our client with Version 1.0.0.0 (oldVersion) of the car assembly, load 1.0.1.0 (newVersion) for us when we run drive.exe.

<?xml version ="1.0"?>
<configuration>   
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="car" 
           publicKeyToken="D730D98B6BDE2BBA"
           culture="" />
        <bindingRedirect oldVersion="1.0.0.0" 
                         newVersion="1.0.1.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

In this configuration file, the name attribute of the assemblyIdentity tag indicates the shared assembly's human-readable name that is stored in the GAC. Although the name value can be anything, you must replace the publicKeyToken value appropriately in order to execute drive.exe. The publicKeyToken attribute records the public-key-token value, which is an 8-byte hash of the public key used to build this component. There are several ways to get this 8-byte hash: you can copy it from the Shell Cache Viewer, you can copy it from the IL dump of your component, or you can use the Shared Name utility to get it, as follows:

sn -T car.dll

Once you create the previously shown configuration file (stored in the same directory as the drive.exe executable) and execute drive.exe, you will see the following as part of your output:

BUILD NUMBER change - 1.0.1.0.

If you change the configuration file to newVersion=1.0.1.1 and execute drive.exe again, you will see the following as part of your output:

REVISION NUMBER change - 1.0.1.1.

Having gone over all these examples, you should realize that you have full control over which dependent assembly versions the CLR should load for your applications. It doesn't matter which version was built with your application: you can choose different versions at runtime merely by changing a few attributes in the application configuration file.

    [ Team LiB ] Previous Section Next Section