Shared Assemblies


Up until now you have been developing only private assemblies. In other words, you have been developing assemblies that are local to the application and that can be accessed only by the application. In most cases, private assemblies will be all you really need to develop. But what happens if you have multiple applications that share a common assembly? You could make a copy of the assembly and copy it to each application's directory. Or you could use the second type of assembly, a shared assembly.

Shared assemblies are accessible to any program that is run on the same machine where the assembly resides. By the way, you work with shared assemblies whenever you use any of the classes or any other data type of the .NET Framework. This seems logical, as every .NET application shares these assemblies.

The Global Assembly Cache

Unlike private assemblies, shared assemblies are placed in a common directory structure known as the global assembly cache (GAC). If and when you go looking for the GAC, you will find it off of your <WINDIR> (Windows or Windows NT) directory, in a subdirectory aptly called assembly.

When you open the assembly directory in Windows Explorer, it has the appearance of being one big directory made up of many different assemblies (see Figure 17-4). In reality, the assembly directory has a complex directory structure that gets hidden (thankfully) by Windows Explorer.

click to expand
Figure 17-4: The GAC

In fact, the assembly directory itself only contains subdirectories. One subdirectory is called GAC, which in turn contains subdirectories for each assembly. Each of these subdirectories contains one or more subdirectories, one for each version of the assembly in the GAC. This directory finally contains the assembly's .dll file that your own assemblies reference.

Off of the <WINDIR>\assembly directory you will also find other subdirectories. You will find directories for each version of natively compiled code used by your system (i.e., any code that is precompiled in the machine language of the host machine). Normally, you work with MSIL code, but because this needs the additional step of compiling to machine code, the .NET Framework precompiles some of its more frequently used assemblies to save the time of performing this compile step. You will find that the native code directory structure is similar to that of the GAC.

There is also the possibility of finding another directory structure off of <WinDIR>\assembly. This one contains assemblies downloaded by ASP.NET, so that they can be used by Web Forms.

Adding Assemblies to the GAC

Fortunately, you can and probably should have remained ignorant of the complex nature of the GAC. (But I'm pretty sure most of you at one point or another will look into the GAC, so I decided to give you a heads up.) The reason you can be ignorant is because adding an assembly to the GAC requires you to simply drop and drag it from your development directory to the Windows Explorer assembly directory. If you want to perform this process in a batch routine, you can use a utility called gacutil.exe to install and uninstall your assembly. To install your assembly, use

 > gacutil /i <assembly name>.dll 

To uninstall the assembly, use

 > gacutil /u <assembly name>.dll, Version=<version number> 

It is even easier to install assemblies using a setup project because the copying to the GAC is handled for you.

The Shared Assembly's Strong Name

There is a catch to global assemblies. They require that they be signed by what is called a strong name. A strong name provides three necessary enhancements to assemblies:

  • It makes the name of the assembly globally unique.

  • It makes it so that no one else can steal and use the name (generally known as spoofing).

  • It provides a means to verify that an assembly has not been tampered with.

The strong name provides these enhancements by adding three things to the assembly: a simple text name, a public key, and a digital signature. The combination of the first two items guarantees the name is unique, as the public key is unique to the party creating the assembly and it is assumed that the party will make the simple text assembly name unique within their own development environment.

The combination of the second and third items guarantees the second and third enhancements. It does this by adding public/private key encryption to the assembly.

Note

Public/private key encryption uses two keys as its name suggests. The private key is used to encrypt something, and the public key is used to decrypt it. What make this combination secure is that only a corresponding public key can be used to decrypt something encrypted by the private key.

So how does public/private key encryption apply to global assemblies? Before you get all excited, you should know that an assembly is not encrypted. Instead, at compile time the compiler creates a hash signature based on the contents of the assembly and then uses the private key (of public/private encryption) to encrypt the hash signature into a digital signature. Finally, the digital signature is added to the assembly. Later, when the assembly is loaded by the CLR, the digital signature is decrypted using the public key back into the hash signature, and the hash signature is verified to make sure that the assembly is unchanged.

The reason this all works is that only the owner of the private key can create a valid digital signature that can be decrypted by the public key.

Like most things in .NET application development, what actually happens is a lot more complex than what you need to do to get it the happen. In this case, to add a strong name to an assembly requires two very simple steps. First, you create a strong name key file by typing the following statement at the command prompt:

 > sn -k StrongNameFileName.snk 

Then you update [AssemblyKeyFileAttribute] in the AssemblyInfo.cpp file, which incidentally is found in all projects:

 [assembly:AssemblyKeyFileAttribute("StrongNameFileName.snk ")]; 

You can place the key in the project directory as the preceding example shows, or you can place it anywhere on your computer and provide a full path to the attribute.

Resigning an Assembly

If you are security conscious, you may have seen a big problem in the preceding strong name system. If you are developing software in a team environment, everyone who needs to update the assembly must have access to the private key so that the assembly can be accessed using the same public key. This means there are a lot of potential areas for security leaks.

To remedy this, the strong name utility sn.exe has an additional option. It provides the capability for an assembly to be re-signed. This allows privileged developers a chance to sign the assembly with the company's private key before releasing it to the public. The command you need to type at the command line is

 > sn -R <assembly name> <strong key file name> 

Notice this time instead of the -k option you use the -R option, stating you want to replace the key instead of create one. You also provide the utility a completed assembly and a previously created strong key file.

Signcoded Digital Signature

Nowhere in the preceding strong name process is the user of the assembly guaranteed that the creator of the strong key is a trusted source, only that it is unchanged from the time it was created.

To remedy this, you need to execute the signcode.exe wizard on your assembly to add an authentic digital certificate created by a third party. Once you have done this, the user of the assembly can find out who created the assembly and decide if he or she wants to trust it.

Caution

You need to compile the assembly with the "final" strong name before you signcode it. The signcode.exe wizard only works with strong named assemblies. Also, re-signing a signcoded assembly invalidates its authentic digital certificate.

Versioning

Anyone who has worked with Windows for any length of time will probably be hit at least once with DLL Hell, the reason being that versioning was not very well supported in previous Windows developing environments. It was possible to swap different versions of .dlls in and out of the registry, which caused all sorts of compatibility issues. Well, with .NET this is no longer the case, as versioning is well supported.

That being said, a word of caution: The CLR ignores versioning in private assemblies. If you include a private assembly in your application's directory structure, the CLR assumes you know what you are doing and will use that version, even if the correct version, based on version number, is in the GAC.

The .NET Framework supports a four-part version: major, minor, build, and revision. You will most frequently see version numbers written out like this: 1.2.3.4. On occasion, however, you will see them like this: 1:2:3:4. By convention, a change in the major and minor numbers means that an incompatibility has been introduced, whereas a change in the build and revision numbers means compatibility has been retained. How you actually use version numbers, on the other hand, is up to you.

Here is how the .NET Framework handles versioning in a nutshell: Only the global assembly version that was referenced at compile time will work in the application. That is, all four version parts need to match. (Well, that is not quite true. You will see a way to overrule which version number to use later in this chapter.) This should not cause a problem even if there is more than one version of a shared assembly available, because multiple versions of a shared assembly can be placed without conflict into the GAC (see Figure 17-5). Okay, there might be a problem if the shared assembly with the corresponding version number is not in the GAC, as this throws a System::IO::FileNotFoundException exception.

click to expand
Figure 17-5: Multiple versions of an assembly in the GAC

Setting the Version Number

Version numbers are stored as metadata within the assembly, and to set the version number requires that you update the AssemblyVersionAttribute attribute. To make things easier for you, the Visual Studio .NET project template wizard automatically provides a default AssemblyVersionAttribute attribute within the AssemblyInfo.cpp file.

You set the version number by simply changing the dummy value

 [assembly:AssemblyVersionAttribute("1.0.*")]; 

to a value that makes sense in your development environment, for example:

 [assembly:AssemblyVersionAttribute("3.1.2.45")]; 

Notice the asterisk (*) in the default version number value provided by Visual Studio .NET. This asterisk signifies that the compiler will automatically create the build and revision numbers for you. When the compiler does this, it places the number of days since January 1, 2000, in the build and the number of seconds since midnight divided by two in the revision.

Personally, I think it's a mistake to use the autogenerated method, as the version numbers then provide no meaning. Plus, using autogenerated numbers forces you to recompile the application referencing the assembly every time you recompile the shared assembly. Autogenerated numbers aren't so bad if the application and the shared reference share into the same solution, but they aren't so good if the application and the shared reference share into different solutions, and even worse if different developers are developing the application and shared assembly.

Getting the Version Number

It took me a while to figure out how to get the version number out of the assembly (but that might just be me). As I found out, though, it's really easy to do, because it's just a property of the name of the assembly. I think the code is easier to understand than the explanation:

 Assembly *assembly = Assembly::GetExecutingAssembly(); Version *version = assembly->GetName()->Version; 

The only tricky part is getting the currently executing assembly, which isn't too tricky because the .NET Framework provides you with a static member to retrieve it for you.

No DLL Hell Example

Now that you've covered everything you need to create a shared assembly, you'll create one. Listing 17-6 shows the source code of a very simple class library assembly containing one class and one property. The property contains the version of the assembly.

Listing 17-6: A Shared Assembly That Knows Its Version

start example
 using namespace System; using namespace System::Reflection; namespace SharedAssembly {     public __gc class SharedClass     {     public:         __property Version *get_Version()         {             Assembly *assembly = Assembly::GetExecutingAssembly();             return assembly->GetName()->Version;         }     }; } 
end example

The code is short, sweet, and offers no surprises. Listing 17-7 contains a filled-in AssemblyInfo.cpp file. To save space, all the comments have been removed.

Listing 17-7: A standard AssemblyInfo.cpp File

start example
 using namespace System::Reflection; using namespace System::Runtime::CompilerServices; [assembly:AssemblyTitleAttribute(S"A Shared Assembly")]; [assembly:AssemblyDescriptionAttribute(S"An assembly that knows its version")]; [assembly:AssemblyConfigurationAttribute(S"Release Version")]; [assembly:AssemblyCompanyAttribute(S"FraserTraining")]; [assembly:AssemblyProductAttribute(S"Managed C++ Series")]; [assembly:AssemblyCopyrightAttribute(S"Copyright (C) by Stephen Fraser 2003")]; [assembly:AssemblyTrademarkAttribute(S"FraserTraining is a Trademark of blah")]; [assembly:AssemblyCultureAttribute(S"en-US")]; [assembly:AssemblyVersionAttribute("1.0.0.0")]; [assembly:AssemblyDelaySignAttribute(false)]; [assembly:AssemblyKeyFileAttribute("SharedAssembly.snk")]; [assembly:AssemblyKeyNameAttribute("")]; 
end example

You saw most of the important code earlier in this chapter, so I won't go over this in detail. I also think that most of the rest of the code is self-explanatory. Only the AssemblyCultureAttribute attribute needs to be explained, and I do that a little later in this chapter.

Of all the attributes in the preceding source file, only two attributes need to be filled in to enable an assembly to be a shared one. The first attribute is AssemblyVersionAttribute. It already has a default value but I changed it to give it more meaning to me.

The second attribute is AssemblyKeyFileAttribute, in which you place the strong key. Remember, you can either pass a full path to the attribute or use a key in the project source directory. Because I'm using a strong key file in the project source, I have to copy my key file SharedAssembly.snk into the project's source directory.

Before you compile the project, change the project's output directory to be local to the project and not the solution. In other words, change the project's configuration properties' output directory to read only $(ConfigurationName) and not the default $(SolutionDir)$(ConfigurationName). The reason you want to do this is that you don't want a copy of SharedAssembly.dll in the same directory as the application assembly referencing it, because otherwise it will be used instead of the copy in the GAC.

Now, when you compile the project, an assembly called SharedAssembly.dll is generated in the project's Debug or Release directory, depending on which environment you're doing the build in. This file needs to be copied to the GAC either by dragging and dropping it there or via gacutil.exe. Figure 17-6 shows what the entry in the Windows Explorer GAC display looks like.

click to expand
Figure 17-6: SharedAssembly in the GAC

Now you'll create an application assembly to reference the shared assembly (see Listing 17-8). All this application does is write out the version number of the shared assembly.

Listing 17-8: Referencing a Shared Assembly

start example
 using namespace System; using namespace SharedAssembly; Int32 main() {     SharedClass *sa = new SharedClass();     Console::WriteLine(sa->Version);     return 0; } 
end example

The code is not new, but to get this to work you need to reference the assembly SharedAssembly.dll. It is important to understand that the assembly you reference during the compile does not need to be the same as the one that you actually execute at runtime. They just have to have the same name, version, and public key token. Therefore, even though you are going to use the assembly within the GAC, you reference the assembly within the solution to get the definition of the SharedClass class and the Version property.

To reference SharedAssembly.dll, you need to perform the following steps:

  1. Right-click the References folder.

  2. Select the Add Reference menu item. This will bring up the Add Reference dialog box (see Figure 17-7).

    click to expand
    Figure 17-7: The Add Reference dialog box

  3. Select the Projects tab.

  4. Select the shared assembly from the list.

Or, if the shared assembly is in a different solution:

  1. Click Browse, navigate to the location of the assembly, and then select the assembly.

  2. Click OK.

Run ReferenceSharedAssembly.exe. You should get something similar to what is shown in Figure 17-8.

click to expand
Figure 17-8: The result of executing ReferenceSharedAssembly

Now let's see what happens if you change your shared assembly and give it a new version number like this:

 [assembly:AssemblyVersionAttribute("1.1.0.0")]; 

Recompile only the SharedAssembly project and then move the new assembly SharedAssembly.dll to the GAC. First off, notice that now there are two SharedAssembly entries in the GAC that differ by version number.

Run ReferenceSharedAssembly.exe again. (Important: Do not recompile when asked.) Nothing has changed, has it? You still get the same output. This is versioning in action. Why do you get the original version of the shared assembly? Because when you compiled the application program, you tightly bound it to version 1.0.0.0 of the shared assembly. Thus, when it executes, it can only load version 1.0.0.0.

Application Configuration Files

An alarm might be going off in your head right now. Does this mean that whenever you change a shared assembly, you have to keep the same version number or you have to recompile every application that uses shared assembly so that it can be accessed? How do you release a fix to a shared assembly?

The .NET Framework provides a solution to this problem by adding a configuration file to the application that specifies which assembly you want to load instead of the bound version. The application configuration file has the same name as the executable plus a suffix of .config. Therefore, for the preceding example, the application configuration file would be called ReferenceSharedAssembly.exe.config. Yes, the .exe is still in the name.

The application configuration file will look something like Listing 17-9.

Listing 17-9: An Application Configuration File

start example
 <configuration>   <runtime>     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">       <dependentAssembly>         <assemblyIdentity name="SharedAssembly"                             publicKeyToken="332a33ed1547b4e6"                             culture="en-US" />         <bindingRedirect  oldVersion="1.0.0.0"                             newVersion="1.1.0.0" />       </dependentAssembly>     </assemblyBinding>   </runtime> </configuration> 
end example

The only two elements you have to worry about in the file are <assemblyIdentity> and <bindingRedirect>.<assemblyIndentity> contains the identity of the shared assembly that you want to use a different version with. Notice that all the information you need to identify the shared assembly can be found in the Windows Explorer GAC view.

Next is the key to assigning a different version to the <bindingRedirect> element. This element specifies the old version, or the version that the application assembly currently references, and then the new version that you want it to access instead. A cool feature is that the oldVersion tag can take a range:

 <bindingRedirect oldVersion="1.0-1.1" newVersion="1.1.0.0" /> 

Now that you have the file created, place it in the same directory as the executable and run ReferenceSharedAssembly.exe again. (Important: Do not recompile when asked.) This time you will get the output shown in Figure 17-9.

click to expand
Figure 17-9: The result of executing ReferenceSharedAssembly with an application configuration file

As a final note to application configuration files, you can also set the newVersion tag to a prior version of the assembly:

 <bindingRedirect oldVersion="1.1.0.0" newVersion="1.0.0.0" /> 

This comes in handy when the new version is found to not be compatible and you need to fall back to a previous version.




Managed C++ and. NET Development
Managed C++ and .NET Development: Visual Studio .NET 2003 Edition
ISBN: 1590590333
EAN: 2147483647
Year: 2005
Pages: 169

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