Assemblies can be isolated for use by a single application – not sharing an assembly is the default. When using shared assemblies there are specific requirements that must be followed.
This section explores the following:
- Strong names as a requirement for shared assemblies
- Global assembly cache
- Creating shared assemblies
- Installing shared assemblies in the GAC
- Delayed signing of shared assemblies.
The goal of a shared assembly name is that it must be globally unique, and it must be possible to protect the name. At no time can any other person create an assembly using the same name.
COM solved the first problem by using a globally unique identifier (GUID). The second problem, however, still existed because anyone could steal the GUID and create a different object with the same identifier. Both problems are solved with strong names of .NET assemblies.
A strong name is made of these items:
- The name of the assembly itself.
- A version number. This allows it to use different versions of the same asserribly at the same time. Different versions can also work side by side and can be loaded concurrently inside the same process.
- A public key guarantees that the strong name is unique. It also guarantees that a referenced assembly cannot be replaced from a different source.
- A culture.
A shared assembly must have a strong name to uniquely identify the assembly.
A strong name is a simple text name accompanied by a version number, a public key, and a culture. You wouldn’t create a new public key with every assembly, but you’d have one in your company, so the key uniquely identifies your company’s assemblies.
However, this key cannot be used as a trust key. Assemblies can carry Authenticode signatures to build up a trust. Thekey for the Authenticode signature can be a different one from the key used for the strong name.
For development purposes. a different public key can be used and later be exchanged easily with the real key. This feature is discussed later in the section “Delayed Signing of Assemblies.”
To uniquely identify the assemblies in your companies, a useful namespace hierarchy should be used to name your classes. Here is a simple example showing how to organize namespaces: Csharp can use the major namespace Csharp its classes and namespaces. In the hierarchy below the namespace,
the namespaces must be organized so that all classes are unique. Every chapter of this book uses a different namespace of the form CSharp. <Chapter>; this chapter uses CSharp. Assemblies. So, if there is a class Hello in two different chapters, there’s no conflict because of different namespaces. Utility classes that are used across different books can go into the namespace csharp.utilities.
A company name commonly used as the first part of the namespace is not necessarily unique, so something more must be used to build a strong name. For this the public key is used. Because of the public/private key principle in strong names, no one without access to your private key can destructively create an assembly that could be unintentionally called by the client.
Integrity Using Strong Names
A public/private key pair must be used to create a shared component. The compiler writes the public key to the manifest, creates a hash of all files that belong to the assembly, and signs the hash with the . private key, which is not stored within the assembly. It is then guaranteed that no one can change your assembly. The signature can be verified with the public key.
During development, the client assembly must reference the shared assembly. The compiler writes the public key of the referenced assembly to the manifest of the client assembly. To reduce storage, it is not the public key that is written to the manifest of the client assembly, but a public key token. The public key token consists of the last 8 bytes of a hash of the public key and is unique.
At runtime, during loading of the shared assembly (or at install time if the client is installed using the native image generator), the hash of the shared component assembly can be verified by using the public key stored inside the client assembly. Only the owner of the private key can change the shared component assembly. There is no way a component Math that was created by vendor A and referenced from a client can be replaced by a component from a hacker. Only the owner of the private key can replace the shared component with a new version. Integrity is guaranteed insofar as the shared assembly comes from the expected publisher.
It shows a shared component with a public key referenced by a client assembly that has a public key token of the shared assembly inside the manifest.
Global Assembly Cache
The global assembly cache (GAC) is, as the name implies, a cache for globally available assemblies. Most shared assemblies are installed inside this cache; otherwise a shared directory (also on a server) can be used.
The GAC can be displayectusingshfusion. dll, which is a Windows shell extension to view and manipulate the contents of the cache. A Windows shell extension is a COM DLL that integrates with Windows Explorer. You just have to start Explorer and go to the <windir>/assembly directory.
With the Assembly Cache Viewer ,you can see the global assembly name, type, version, culture, and the public key token. Under Type you can see if the assembly was installed using the native image generator. When you select an assembly using the context menu, it’s possible to delete an assembly and to view its properties.
You can see the ‘x;ealfiles and directories behind the assembly cache by checking the directory from the command line. Inside the <windir> \assembly directory, you can find multiple GACxxxdirectories and a Nati veImages_ <runtime version> directory, The GACxxxdirectories contain shared assemblies. GAC_MSILcontains the assemblies with pure .NET code; GAC_32contains the assemblies rhat are specific to a 32-bit platform. On a 64-bit system, you can also find the directory GAC_64with assemblies specific for 64 bit. The directory GACis for .NET 1.0and 1.1. In the directory NativeImages_.<runtime version>, you can find the assemblies compiled to native code. If you go deeper in the directory structure, you will find directory names that are similar to the assembly names, and below that a version directory and the assemblies themselves. This allows the installation of different versions of the same assembly.
The assembly viewer can be used to view and delete assemblies with Windows Explorer. gacutil. exe is a utility to install, uninstall, and list assemblies using the command line.
The following list explains some of the gacutil options:
- gacutil / 1 lists all assemblies from the assembly cache.
- gacutil / i mydll installs the shared assembly mydll into the assembly cache.
- gacutil /u mydll uninstalls the assembly mydll.
Creating a Shared Assembly
In the next example, you create a shared assembly and a client that uses it.
Creating shared assemblies is not much different from creating private assemblies. Create a simple Visual C# class library project with the name SharedDemo. Change the namespace to CSharp . Assemblies . Sharing and the class name to SharedDemo. Enter the following code. In the constructor of the class, all lines of a file are read into a collection. The name of the file is passed as an argument to the constructor. The method GetQuoteOfTheDay ( ) just returns a random string of the collection.
Create a Strong Name
A strong name is needed to share this assembly. You can create such a name with the strong name tool (sn):
sn -k mykey.snk
The strong name utility generates and writes a public/private key pair, and writes this pair to a file; here the file is mykey . snk.
With Visual Studio 2008,you can sign the assembly with the project properties by selecting the Signing tab, as shown in Figure 17-13.You can also create keys with this tool. However, you should not create a key file for every project. Just a few keys for the complete company can be used instead. It is useful to create different keys depending on security requirements.
Setting the signing openwith Visual Studio adds the /keyfile option to the compiler setting. Visual Studio also allows you to create a keyfile that is secured with a password. Such a file has the file extension .pfx
After rebuilding, the public key can be found inside the manifest. You can verify this using ildasm, as shown.
Install the Shared Assembly
With a public key in the assembly, you can now install it in the global assembly cache using the global assembly cache tool gacutil with the / i option:
gacutil /i SharedDemo.dll
By configuring a post-build event command line with Visual Studio the assembly can be installed in the GAC with each successful build.
If you’re using Windows Vista to install an assembly to the GAC from Visual Studio, Visual Studio must be started with elevated rights. Installing assemblies to the GAC requires admin privileges.\
Then you can use the Global Assembly Cache Viewer to check the version of the shared assembly and see if it is successfully installed.
Using the Shared Assembly
To use the shared assembly, create a C# console application called Client. Change the name of the namespace to CSharp. Assemblies .Sharing. The shared assembly can be referenced in the same way as a private assembly: by using the Project Q Add Reference menu.
With shared assemblies the reference property Copy Local can be set to -false to’ This way’ the assembly is not copied to the directory of the output files but will be loaded from the GAC instead.
Here’s the code for the Client application:
Looking at the manifest in the client assembly using ildasm (see Figure 17-16), you can see the reference to the shared assembly SharedDemo: . assembly extern SharedDemo. Part of this referenced information is the version number, discussed next, and the token of the public key.
The token of the public key can also be seen within the shared assembly using the strong name utility: sn -T shows the token of the public key in the assembly, and sn -Tp shows the token and the public key. P.ay attention to the use of the uppercase T!
The result of your program with a sample quotes file is shown here:
Delayed Signing of Assemblies
The private key of a company should be safely stored. Most companies don’t give all developers access to the private key; only a few security people have it. That’s why the signature if an assembly can be added at a later date, such as before distribution. When the assembly attribute AssemblyDelaySign is set to true, no signature is stored in the assembly, but enough free space is reserved so that it can be added later. Without using a key, you cannot test the assembly and install it in the GAC; however, you can use a temporary key for testing purposes, and replace this key with the real company key later.
The following steps are required to delay signing of assemblies:
- Create a public/private key pair with the strong name utility sn. The generated file mykey . snk includes both the public and private key. sn -k mykey.snk
- Extract the public key to make it available to developers. The option -p extracts the public key of the keyfile. The file mykeypub. snk only holds the public key. sn -p mykey.snk rnykeypub.snk
All developers in the company can use this keyfile mykeypub. snk and compile the assembly with the /delaysign+ option. This way the signature is not added to the assembly, but it can be added afterward.In Visual Studio 2008, the delay sign option can be set with a check box in the Signing settings.
- Turn off the verification of the signature, because the assembly doesn’t have a signature:
sn -Vr SharedDemo.dll
- Before distribution the assembly can be re-signed with the sn utility. Use the -R option to re-sign previously signed or delayed signed assemblies. Resigning of the assembly can be done by the person doing the deployment package for the application and having access to the private key that is used for distribution.
sn -R MyAssembly.dll mykey.snk.
The signature verification should be turned off only during the development process. Never distribute an assembly without verification, because it would be possible for this assembly to be replaced by a malicious one.
Re-signing of assemblies can pe automated by defining the tasks in an MSBuild file.
Properties li;ts a reference count. This reference count is responsible for the fact that a cached assembly cannot be deleted if it is still needed by an application. For example, if a shared assembly is installed by a Microsoft installer package (. msi file), it can only be deleted by uninstalling the application, but not by deleting it from the GAC. Trying to delete the assembly from the GAC results in the error message “Assembly <name> could not be uninstalled because it is required by other applications.”
A reference to the assembly can be set using the gacutil utility with the option r x, The option fr requires a reference type, a reference lD, and a description. The type of the reference can be one of three
options: UNINSTALL_KEY,FILEPATH, or OPAQUE.UNINSTALL_KEYis used by MSI where a registry key is defined that is also needed with the uninstallation. A directory can be specified with FILE PATH.A useful directory would be the root directory of the application. The OPAQUEreference type allows you to set any type of reference.
The command line
gacutil Ii shareddemo.dll Ir FILEPATH c:\ProCSharp\Assemblies\Client ·Shared Demo*
installs the assembly shareddemo in the GAC with a reference to the directory of the client application. Another installation of the same assembly can happen with a different path, or an OPAQUEill like in this command line:
gacutil ‘Ii shareddemo.dll Ir OPAQUE4711 ·Opaque installation”
Now, the assembly is in the GAC only once, but it has two references. To delete the assembly from the GAC, both references must be removed:
gacutil lu shareddemo Ir OPAQUE4711 ·Opaque installation·
gacutil lu shareddemo Ir FILEPATH c:\ProCSharp\Assemblies\Client ·Shared Demo.
To remove a shared assembly, the option /u requires the assembly name without the file extension DLL. On the contrary, the option I i to install a shared assembly requires the complete file name including the file extension.
Native Image Generator
With the native image generator, Ngen. exe, you can compile the IL code to native code at installation time. This way the program can start faster because the compilation during runtime is no longer
necessary. Comparing precompiled assemblies to assemblies where the JIT compiler needs to run is not different from a performance view after the IL code is compiled. The only improvement you get with the native image generator is that the application starts faster because there’s no need to run JIT. Reducing the startup time of the application might be enough reason for using the native image generator. In case you create a native image from the executable, you should also create native images from all the DLLs that are loaded by the executable. Otherwise the JIT compiler still needs to run.
The ngen utility installs. the native image in the native image cache. The physical directory of the native image cache is <windows>\assembly\NativeImages<RuntimeVers ion>.
With ngen install my assembly, you can compile the MSlL code to native code and install it into the native image cache. This should be done from an installation program if you would like to put the assembly in the native image cache.
With ngen you can also display all assemblies from the native image cache with the option display. If you add an assembly name to the.display option you get the information about all installed versions of this assembly and the assemblies that are dependent on the native assembly:
If the security of the system changes, it’s not sure if the native image has the security requirements itneeds for running the application. This is why the native images become invalid with a system configuration change. With the command ngen update all native images are rebuilt to include the new configurations.
Installing CLR 2.0 runtime also installs the Native Image Service (or the Window Service CLR Optimization Service), with the name Microsoft .NET Framework NGEN v2.0.50727 _X86. This service can be \lsed to defer compilation of native images and regenerates native images that have been invalidated.
The command ngen install myassernbly / queue can be used by an installation program to defer compilation of myassembly to a native image using the Natree Image Service. ngen update /queue regenerates all native images that have been invalidated. With the ngen queue options pause, continue, .~d status you can control the service and get status information.
You might ask why the native images cannot be created on the developer system, and you just distribute the native image to the production system. The reason is that the native image generator takes care of the CPU that is installed with the target system and compiles the code optimized for the CPU type. During installation of the application, the.CPU is known.