Generating Assemblies

This is the point where this chapter becomes a lot more applied in nature. We've so far picked up quite a reasonable understanding of the internal workings of assemblies, both at the level of what information they contain and at the level of how that information is arranged within and between files. It's now time to start working towards applying that understanding to actually generate a couple of assemblies. In this section we'll review some of the tools that are available to generate and modify assemblies - looking first at the big picture and then focusing on two areas that can cause particular confusion: signing assemblies to create a strong name, and localizing assemblies. Finally, in the next section we'll present two actual examples in which we work through the generation of assemblies - in one case working at the command line, in the other case working with VS.NET.

Assembly Utilities

There are quite a few permutations of assemblies, satellite assemblies, and related files, and therefore quite a few commands that can be used at the command line to generate the various files. The following diagram shows the general picture of all the ways that you can get from source files and resource files to actual assemblies:

click to expand

You should treat the diagram as an overall roadmap, with the names alongside the arrows indicating which command-line utility can perform the conversion. The way to use the diagram is to first work out what source files you are expecting to start off with, then look at what type of assembly you want to create (for example, private or public, with or without a strong name and/or resources), then you can use the diagram to identify how to get that type of assembly generated.

Detailed usage instructions for, and lists of, the various parameters you can supply to the command-line utilities are of course available in the documentation. However, the following list summarizes the broad purposes of the command-line tools you'll most likely need to use, and we'll see examples of the use of all these utilities in the samples at the end of the chapter.

  • al.exe is the assembly linker. This tool is responsible for creating an assembly by linking together various files that contain compiled code or resources intended to go in the assembly. It takes as input one or more assemblies, modules or resource files, and related files such as key files, and outputs an assembly with the desired identity.

  • gacutil.exe is the utility that is responsible for the assembly cache. Its main functions are to install assemblies to the cache and to remove them from the cache.

  • resgen.exe is responsible for compiling resources from text-based files into the binary format used in assemblies. It is often used in conjunction with resxgen.exe, which can convert image files into XML-based text files that can be processed by resgen.

  • sn.exe generates the public/private key combinations that al.exe uses to sign assemblies, as well as performing other tasks such as resigning an assembly.

  • Compiler tools, such as csc.exe (C#), cl.exe (C++), and vbc.exe (VB) are the programs that convert source files into assemblies or modules.

If you are using VS.NET, you will find that VS.NET uses many of these tools in its build process, saving you from having to worry about the detailed command-line syntax in many cases. You might, however, still want to practice working at the command line in order to get a clearer picture of what is actually happening - something that VS.NET hides. Also, you will find the support for many options in VS.NET is limited, so that even with a VS.NET project, you may find you need to use the command line for certain tasks (for example, VS.NET cannot at present generate multi-file assemblies).

Compiling Resource Files

We'll now have a closer look at the process of compiling resources, From now on, our discussion of resources will focus solely on embedded resources, as these require compilation into a certain format. As previously mentioned, for linked resources, the file type is literally whatever type of file you want linked to the assembly - no modification of this file is performed.

Types of Resource Files

You'll have noticed from the previous diagram that there are three types of files that are relevant to embedded resources.

.txt Files

.txt files are designed for string-only resources. They have a simple format, allowing you to easily type in a resource by hand. For example, a typical .txt file that is destined to become an embedded resource might look like this:

 FileMenu = File FileMenuSave = Save FileMenuOpen = Open 

In other words, it contains a series of name-value pairs, allowing your code to load each string by name.

.resx Files

.resx files are actually XML files, which conform to a fairly strict schema. The advantage of a .resx file over a text file is that it can represent resources of any type. Whereas a text file simply contains a table of strings, a .resx file contains a table where each item might be a different type; the type of each item being indicated by XML tags. .resx files can be quite large, so we don't have space to present a full example, but to give you an idea, a few lines from within a .resx file might look like this:

 <data name="greeting.Text">   <value>Hello, World!</value> </data> <data name="btnShowFlag.Location" type="System.Drawing.Point, System.Drawing, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">   <value>16, 72</value> </data> 

This snippet is taken from the .resx file that VS.NET will generate in an example we present later in the chapter. It defines the text for a System.Windows.Forms.Panel control called greeting, and the location of a Button control called btnShowFlag. Notice the way that the .resx file defines, in plain text format, not only the name but also the type of the data (which appears to default to string).

For types that require binary data, such as bitmaps, the XML file will contain base-64 encoding of the binary data. Base-64 encoding is a way of representing binary data in purely text format. As you might guess, this is quite a verbose format (added to the XML file format which is itself verbose), but you don't need to worry about that. Data is only stored in .resx files temporarily, prior to compilation, and resources are not shipped in that format.

.resources Files

.resources files are the actual files that are incorporated into the assembly. Whereas .txt files and .resx files are formatted as text files in order to make it easy for the developer to modify them by hand, the corresponding .resources file contains the data in the actual binary format that will be embedded in the assembly. The process of generating a .resources file from a .txt or .resx file is known as compilation by analogy with process of compiling source code. Thus the .txt and .resx files play the role of the "source files" which the developer manipulates.

resgen.exe and resxgen.exe

The workhorse utility for compiling resources is resgen.exe. This utility is quite capable of compiling either a .txt file or a .resx file into a .resources file (and can equally well decompile a .resources file if passed the appropriate parameters, though we won't do that in this chapter). For example, to compile a .txt file, MyResource.txt, into the file MyResource.resources you would write:

 resgen MyResource.txt 

While to compile a .resx file you would write:

 resgen MyOtherResource.resx 

If you wish you can even use resgen to convert a .txt file into an XML .resx file:

 resgen MyResource.txt MyResource.resx 

Using resgen you can probably see quite easily how to generate a string resource. You type in the .txt file, use resgen to compile it, then use al to embed the resource in an assembly. However, other resources are less obvious since you need to start off with a .resx file, which is harder to write. If you want a resource that contains a mixture of types such as strings and primitive types, your best bet is simply to find some compiled . resources file on your system (such as a VS.NET-generated one) use resgen to decompile it into a .resx file, and then rename and edit this file. However, that's not possible if you want to embed some binary data such as a bitmap into a resource. For that you are going to need to programmatically generate the .resx file containing the base-64 encoded data. Although we won't go into details here, the relevant classes to do this are in the System.Resources namespace.

Fortunately, for the case of bitmaps, the most common scenario, Microsoft has made available a command-line utility which can generate a .resx file from a bitmap. It's resxgen.exe. Unusually, however, resxgen is not a straight utility, but is an example that you'll need to compile from C# source files. It's in the Framework SDK samples, and as of version 1 of the .NET Framework you can find it at <Framework SDK Folder>\Samples\Tutorials\resourcesandlocalization\resxgen. Here, <Framework SDK Folder> indicates the folder at which you have installed the .NET Framework SDK. You'll find two C# source files there, resxgen.cs and argparser.cs, along with a batch file to build the project, build.bat. You can either use this batch file or invoke the C# compiler directly:

 csc /out:resxgen.exe resxgen.cs argparser.cs 

Once resxgen has been built, using it is quite simple:

 resxgen /i:MyBitmap.bmp /o:MyBitmap.resx 

Once you have your .resources file (or files), you can either use the assembly linker tool, al.exe, to convert them into a resource-only assembly, or you can pass the names of these files as parameters to your high-level language compiler to get them embedded with the emitted code. We'll see how to do both of these in the GreetMe example that we present soon.

Localization and Satellite Assemblies

We'll now examine the model used in .NET for localizing resources. The system is based on what are known as satellite assemblies, and the following table of a typical file structure illustrates the principle:

FOLDERS

FILES IN THIS FOLDER

 

Name

Name

Culture

MainFolder

MyLibrary.dll

Invariant culture

MainFolder/en

MyLibrary.resources.dll

en (=English)

MainFolder/de

MyLibrary.resources.dll

de (=German)

MainFolder/en-CA

MyLibrary.resources.dll

en-CA (=English, Canada)

This diagram shows an assembly called MyLibrary.dll, which has to be localized into specific versions for English and German. (Although we've shown the situation for a DLL, the same principles hold if MyLibrary were replaced by an EXE - the only thing that would change would be MyLibrary's file extension). In addition, there are slight differences in the user interface for English-speaking Canadians, so a version for English in Canada is supplied. This is obviously only a small fraction of the number of localized versions that would be supplied in real life (for example, we've provided an en-CA version but not supplied anything for French speakers in Canada - quite a significant oversight) but it'll do to illustrate the principles.

The point to note is that for each localized version there is a subfolder beneath the folder containing the main assembly, with the same name as the corresponding culture string. Within each folder is another assembly - these are known as satellite assemblies - and each satellite assembly has the same base name as the main assembly, but with the string .resource appended before to the .dll extension. You'll need to keep strictly to these names as the ResourceManager class relies on these naming conventions being followed.

The main assembly should contain all the code, just as for non-localized applications. It should also contain a version of the resources that can be used in any country for which a specific version is not supplied (which in practice usually means having strings in English, on the basis that English is the language most likely to be understood by the biggest number of computer users). As far as identity is concerned, the culture of the main assembly should be the invariant culture. The satellite assemblies should contain only resources - no code. If you do for some reason want to place code in a satellite assembly, for example, if you have code that is only executed for certain cultures, then you'll have to explicitly load that assembly to access and execute the code: the resources infrastructure won't help you.

Each satellite assembly should have its culture set to the appropriate culture, and contain a version of the resources in that language and appropriate to that country. Your application is now localized, and the ResourceManager.GetString() and ResourceManager.GetObject() methods will always retrieve a correctly localized resource. The implementation of these methods works like this. The ResourceManager first checks the UI culture of the thread it is running on. It then looks for a satellite resource with this culture, and obtains the relevant string or other object from this resource. If it can't find it then it'll just grab the corresponding object from the (culture-invariant) main assembly. In fact it's more intelligent even than this. Suppose the application is running with the UICulture set to en-US and ResourceManager is asked to load a string from the resources. The resource manager will look for an en-US satellite assembly. If it doesn't find this assembly, or if the assembly exists but doesn't contain the required string, it will look to see if there's a culture-neutral en satellite assembly that contains generic English-language resources, and which contains the required string. If that fails, it'll look in the main assembly for the language-invariant resources, and finally if that doesn't yield the string, it'll throw an exception. A consequence of this architecture is that satellite assemblies don't need to contain all resource objects - each one only needs to contain those resources that are different from the corresponding object in the parent culture.

Note I've referred specifically to the CurrentUICulture. The Thread class has several culture-related properties, and it's beyond the scope of this chapter to go into them in detail. ResourceManager uses Thread.CurrentUICulture to determine which localized resources should be loaded. Several other .NET classes use a different property, Thread.CurrentCulture, to determine aspects of string formatting. But in most cases you'll allow both of these properties to have the same value anyway. If you don't set cultures explicitly, the values will be inherited from the operating system.

That's the theory; the only question is: how do you create a resource-only satellite assembly? The answer is that you use the assembly linker al.exe, just as you would to create an ordinary assembly that contains code. You just don't specify any code files as input. For example, if you have a file called MyResources.resources, which contains the German (de) version of resources, and which need to be converted into a satellite assembly called MyLibrary.resources.dll. You'd do this:

 al /res:MyReaources.resources /c:de /out:MyLibrary.resources.dll 

This command emits the assembly. The /c flag indicates the culture of the emitted assembly and must be set for the ResourceManager to be able to locate the assembly.

It's important to understand that the satellite assembly is still a portable executable file, containing all the relevant PE headers, as well as the assembly manifest. It just happens not to contain any IL code.

Signing Assemblies

The signing of assemblies ready to place them into the Global Assembly Cache is relatively straightforward in principle.

Generation of the key will be done using the utility sn.exe:

 sn  -k OurKey.snk 

The above command generates a file called OurKey.snk, which contains the public and private key pair. There are now two ways that you can get the assembly signed by this key. The first way is to indicate this key using the /keyfile parameter to the assembly linker utility. Thus, taking the previous example, if we want the MyResources satellite assembly to be signed as well, we would type in:

 al /res:MyResources.resources /c:de /keyfile:OurKey.snk /out:MyLibrary.resources.dll 

For compiled source code, the usual practice is to indicate the key file in the AssemblyKeyFile attribute in the source code. For example, in C#:

 [assembly:AssemblyKeyFile("OurKey.snk")]; 

Although the above procedure will get the assembly signed, it's probably not how you'll do it in practice in a real organization. Typically, companies won't want their developers to have access to the company's private key. In Chapter 13, we'll examine an alternative procedure for delay-signing assemblies, which doesn't require access to the private key until the final build of the product ready for shipping is done.

Once an assembly is signed, it can be placed in the global assembly cache:

 gacutil /i MyLibrary.dll 

One point to understand is that you can sign code without placing it in the assembly cache. Even for private assemblies, it's good practice to sign them with your organization's private key as an extra security precaution. (Provided of course that the private key really is kept confidential.)



Advanced  .NET Programming
Advanced .NET Programming
ISBN: 1861006292
EAN: 2147483647
Year: 2002
Pages: 124

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