# Chapter 2 Building, Packaging, Deploying, and Administering Applications and Types

Today, applications consist of several types, which are typically created by you and Microsoft. In addition, there are many component vendors creating and selling types that other companies can use to reduce a software project’s development time. If these types are developed using any language that targets the common language runtime (CLR), they can all work together seamlessly; a type written in one language can use another type as its base class without concern for the language the base type was developed in.

# .NET Framework Deployment Goals

Over the years, Windows has gotten a reputation for being unstable and complicated. This reputation, whether deserved or not, is the result of many different factors. First, all applications use dynamic-link libraries (DLLs) from Microsoft or other vendors.

I’m sure that everyone reading this book has experienced some variation of this problem: when installing a new application, you discover that it has somehow corrupted an already-installed application. This predicament is known as “DLL hell.” This type of instability puts fear into the hearts and minds of the typical computer user. The end result is that users have to carefully consider whether to install new software on their machines. Personally, I’ve decided not to try out certain applications out of fear that it might adversely affect some application I really rely on.

The second reason that contributed to the aforementioned reputation of Windows is installation complexities. Today, when most applications are installed, they affect all parts of the system. For example, installing an application causes files to be copied to various directories, updates registry settings, and installs shortcuts on your desktop and Start menu/screen. The problem with this is that the application isn’t isolated as a single entity. You can’t easily back up the application because you must copy the application’s files and also the relevant parts of the registry. In addition, you can’t easily move the application from one machine to another; you must run the installation program again so that all files and registry settings are set properly. Finally, you can’t easily uninstall or remove the application without having this nasty feeling that some part of the application is still lurking on your machine.

The third reason has to do with security. When applications are installed, they come with all kinds of files, many of them written by different companies. In addition, web applications frequently have code (like ActiveX controls) that is downloaded in such a way that users don’t even realize that code is being installed on their machine. Today, this code can perform any operation, including deleting files or sending email. Users are right to be terrified of installing new applications because of the potential damage they can cause. To make users comfortable, security must be built into the system so that the users can explicitly allow or disallow code developed by various companies to access their system’s resources.

The .NET Framework addresses the DLL hell issue in a big way, as you’ll see while reading this chapter and Chapter 3. It also goes a long way toward fixing the problem of having an application’s state scattered all over a user’s hard disk. For example, unlike COM, types no longer require settings in the registry. Unfortunately, applications still require shortcut links. As for security, the .NET Framework CHAPTER 2 Building, Packaging, Deploying, and Administering Applications and Types 35 includes a security model called code access security. Whereas Windows security is based on a user’s identity, code access security allows hosts to set permissions, thereby controlling what the loaded components can do. A host application like Microsoft SQL Server can grant just a few permissions to code, whereas a locally installed (self-hosting) application could run with full trust (all permissions). As you’ll see, the .NET Framework enables users to control what gets installed and what runs, and in general, to control their machines, more than Windows ever did.

💡 小结:在 Windows 下,应用程序都使用来自 Microsoft 或其他厂商的动态链接库(Dynamic-Link Library, DLL)。对于用户来说,当它们更新安装的软件时,并不能保证新安装的文件一定向后兼容以前的文件。很多人都可能遇到这样的问题:安装新应用程序时,它可能莫名其妙破坏了另一个已经安装好的应用程序,这就是所谓的 “DLL hell”。造成 Windows 口碑不佳的第二个原因是安装的复杂性。大多数应用程序在安装时都会影响到系统的全部组件。应用程序不是一个孤立的实体,应用程序备份不易,因为必须复制应用程序的全部文件以及注册表中的相关部分。除此之外,也不能轻松地将应用程序从一台机器移动到另一台机器,只有再次运行安装程序才能确保所有文件和注册表设置的正确性。最后,即使卸载或移除了应用程序,也免不了担心它的一部分内容仍潜伏在我们的机器中。第三个原因涉及安全性。应用程序安装时会带来各种文件,其中许多是不同的公司开发的。此外,Web 应用程序经常会悄悄下载一些代码,而这些代码能够执行任何操作,包括删除文件或者发送电子邮件。而.NET Framework 正在尝试彻底解决 DLL hell 问题,另外,.NET Framework 还在很大程度上解决了应用程序状态在用户硬盘中四处分散的问题。安全性方面,.NET Framework 包含称为 “代码访问安全性”(Code Access Security)的安全模型。Windows 安全性基于用户身份,而代码访问安全性允许宿主设置权限,控制加载的组件能做的事情。.NET Framework 允许用户灵活地控制哪些东西能够安装,哪些东西能够运行。

# Building Types into a Module

In this section, I’ll show you how to turn your source file, containing various types, into a file that can be deployed. Let’s start by examining the following simple application.

public sealed class Program {
 public static void Main() {
 System.Console.WriteLine("Hi");
 }
}

To build this sample application, put the preceding code into a source code file, say, Program.cs, and then execute the following command line.

csc.exe /out:Program.exe /t:exe /r:MSCorLib.dll Program.cs

This command line tells the C# compiler to emit an executable file called Program.exe ( /out:Program.exe ). The type of file produced is a Win32 console application ( /t[arget]:exe ).

When the C# compiler processes the source file, it sees that the code references the System.Console type’s WriteLine method. At this point, the compiler wants to ensure that this type exists somewhere, that it has a WriteLine method, and that the argument being passed to this method matches the parameter the method expects. Because this type is not defined in the C# source code, to make the C# compiler happy, you must give it a set of assemblies that it can use to resolve references to external types. In the preceding command line, I’ve included the /r[eference]:MSCorLib.dll switch, which tells the compiler to look for external types in the assembly identified by the MSCorLib.dll file.

MSCorLib.dll is a special file in that it contains all the core types: Byte, Char, String, Int32, and many more. In fact, these types are so frequently used that the C# compiler automatically references the MSCorLib.dll assembly. In other words, the following command line (with the /r switch omitted) gives the same results as the line shown earlier.

csc.exe /out:Program.exe /t:exe Program.cs

If, for some reason, you really don’t want the C# compiler to reference the MSCorLib.dll assembly, you can use the /nostdlib switch. Microsoft uses this switch when building the MSCorLib.dll assembly itself. For example, the following command line will generate an error when CSC.exe attempts to compile the Program.cs file because the System.Console type is defined in MSCorLib.dll.

csc.exe /out:Program.exe /t:exe /nostdlib Program.cs

Response Files

Before leaving the discussion about compiler switches, I’d like to spend a moment talking about response files. A response file is a text file that contains a set of compiler command-line switches. When you execute CSC.exe, the compiler opens response files and uses any switches that are specified in them as though the switches were passed to CSC.exe on the command line. You instruct the compiler to use a response file by specifying its name on the command line prepended by an @ sign. For example, you could have a response file called MyProject.rsp that contains the following text.

/out:MyProject.exe
/target:winexe

To cause CSC.exe to use these settings, you’d invoke it as follows.

csc.exe @MyProject.rsp CodeFile1.cs CodeFile2.cs

The C# compiler supports multiple response files. In addition to the files you explicitly specify on the command line, the compiler automatically looks for files called CSC.rsp. When you run CSC.exe, it looks in the directory containing the CSC.exe file for a global CSC.rsp file. Settings that you want applied to all of your projects should go in this file. The compiler aggregates and uses the settings in all of these response files. If you have conflicting settings in the local and global response files, the settings in the local file override the settings in the global file. Likewise, any settings explicitly passed on the command line override the settings taken from a local response file.

Untitled

Referencing all of these assemblies could slow the compiler down a bit. But if your source code doesn’t refer to a type or member defined by any of these assemblies, there is no impact to the resulting assembly file, nor to run-time execution performance.

💡 小结:将类型生成到模块中可以使用 csc.exe 命令,/out: 文件名 指定生成的目标文件文件名,/t: 类型 指定应用程序的类型。Windwos 支持三种应用程序,控制台用户界面(Console User Interface,CUI)应用程序使用 /t:exe 开关;图形用户界面(Graphical User Interface,GUI)应用程序使用 /t:winexe 开关;Windows Store 应用使用 /t:appcontainerexe 开关。/r: 引用程序集 告诉编译器在指定程序集中查找外部类型。C# 编译器会自动引用 MSCorLib.dll 程序集,若不想自动引用 MSCorLib.dll 程序集,可以使用 /nostdlib 开关。响应文件时包含一组编译器命令行开关的文本文件。执行 CSC.exe 时,编译器打开响应文件,并使用其中包含的所有开关,感觉就像是这些开关直接在命令行上传递给 CSC.exe。C# 编译器支持多个响应文件,除了命令行上显式指定的文件,编译器还会自动查找名为 CSC.rsp 文件。CSC.exe 运行时,会在 CSC.exe 所在目录查找全局 CSC.rsp 文件。本地和全局响应文件中的某个设置发生冲突,将以本地设置为准。类似地,命令上显式指定的设置将覆盖本地响应文件中的设置。引用所有这些程序集对编译器的速度有一点影响。但是,如果源代码没有引用上述任何程序集定义的类型或成员,就不会影响最终的程序集文件,也不会影响程序的执行性能。另外,指定 /noconfig 命令行开关,编译器将忽略本地和全局 CSC.rsp 文件。

# A Brief Look at Metadata

Now we know what kind of PE file we’ve created. But what exactly is in the Program.exe file? A managed PE file has four main parts: the PE32(+) header, the CLR header, the metadata, and the IL. The PE32(+) header is the standard information that Windows expects. The CLR header is a small block of information that is specific to modules that require the CLR (managed modules). The header includes the major and minor version number of the CLR that the module was built for: some flags, a MethodDef token (described later) indicating the module’s entry point method if this module is a CUI, GUI, or Windows Store executable, and an optional strong-name digital signature (discussed in Chapter 3). Finally, the header contains the size and offsets of certain metadata tables contained within the module. You can see the exact format of the CLR header by examining the IMAGE_COR20_HEADER defined in the CorHdr.h header file.

typedef struct IMAGE_COR20_HEADER
{
    // Header versioning
    DWORD                   cb;              
    WORD                    MajorRuntimeVersion;
    WORD                    MinorRuntimeVersion;
    
    // Symbol table and startup information
    IMAGE_DATA_DIRECTORY    MetaData;        
    DWORD                   Flags;           
  
	// The main program if it is an EXE (not used if a DLL?)
    // If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is not set, EntryPointToken represents a managed entrypoint.
	// If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is set, EntryPointRVA represents an RVA to a native entrypoint
	// (depricated for DLLs, use modules constructors intead). 
    union {
        DWORD               EntryPointToken;
        DWORD               EntryPointRVA;
    };
    
    // This is the blob of managed resources. Fetched using code:AssemblyNative.GetResource and
    // code:PEFile.GetResource and accessible from managed code from
	// System.Assembly.GetManifestResourceStream.  The meta data has a table that maps names to offsets into
	// this blob, so logically the blob is a set of resources. 
    IMAGE_DATA_DIRECTORY    Resources;
	// IL assemblies can be signed with a public-private key to validate who created it.  The signature goes
	// here if this feature is used. 
    IMAGE_DATA_DIRECTORY    StrongNameSignature;
    IMAGE_DATA_DIRECTORY    CodeManagerTable;			// Depricated, not used 
	// Used for manged codee that has unmaanaged code inside it (or exports methods as unmanaged entry points)
    IMAGE_DATA_DIRECTORY    VTableFixups;
    IMAGE_DATA_DIRECTORY    ExportAddressTableJumps;
	// null for ordinary IL images.  NGEN images it points at a code:CORCOMPILE_HEADER structure
    IMAGE_DATA_DIRECTORY    ManagedNativeHeader;
    
} IMAGE_COR20_HEADER, *PIMAGE_COR20_HEADER;

Untitled

The metadata is a block of binary data that consists of several tables. There are three categories of tables: definition tables, reference tables, and manifest tables.

As the compiler compiles your source code, everything your code defines causes an entry to be created in one of the tables described in Table 2-1. Metadata table entries are also created as the compiler detects the types, fields, methods, properties, and events that the source code references. The metadata created includes a set of reference tables that keep a record of the referenced items.

Untitled

Untitled

💡 小结:托管 PE 文件由 4 部分构成:PE32 (+) 头、CLR 头、元数据以及 IL。PE32 (+) 头是 Windows 要求的标准信息。CLR 头是一个小的信息块,是需要 CLR 的模块(托管模块)特有的。这个头包含模块生成时所面向的 CLR 的 major (主) 和 minor (次) 版本号;一些标志(flag);一个 MethodDef token,该 token 指定了模块的入口方法(前提是该模块是 CUI、GUI 或 Windows Store 执行体);一个可选的强名称数字签名。最后,CLR 头还包含模块内部的一些元数据表的大小和偏移量。可以查看 CorHdr.h 头文件定义的 IMAGE_COR20_HEADER 来了解 CLR 头的具体格式。元数据是由几个表构成的二进制数据块。有 3 种表,分别是定义表(definition table)、引用表(reference table)和清单表(manifest table)。编译器编译源代码时,diamagnetic 定义的任何都系都会在元数据定义表中创建一个记录项。此外,编译器还会检测源代码引用的类型、字段、方法、属性和事件,并创建相应的元数据记录项。在创建的元数据中包含一组引用表,它们记录了所引用的内容。

# Combining Modules to Form an Assembly

The Program.exe file discussed in the previous section is more than just a PE file with metadata; it is also an assembly. An assembly is a collection of one or more files containing type definitions and resource files. One of the assembly’s files is chosen to hold a manifest. The manifest is another set of metadata tables that basically contain the names of the files that are part of the assembly. They also describe the assembly’s version, culture, publisher, publicly exported types, and all of the files that comprise the assembly.

The CLR operates on assemblies; that is, the CLR always loads the file that contains the manifest metadata tables first and then uses the manifest to get the names of the other files that are in the assembly. Here are some characteristics of assemblies that you should remember:

■ An assembly defines the reusable types.

■ An assembly is marked with a version number.

■ An assembly can have security information associated with it.

An assembly’s individual files don’t have these attributes—except for the file that contains the manifest metadata tables.

To package, version, secure, and use types, you must place them in modules that are part of an assembly. In most cases, an assembly consists of a single file, as the preceding Program.exe example does. However, an assembly can also consist of multiple files: some PE files with metadata and some resource files such as .gif or .jpg files. It might help you to think of an assembly as a logical EXE or a DLL.

An assembly allows you to decouple the logical and physical notions of reusable types.

I’ve identified three reasons to use multifile assemblies:

■ You can partition your types among separate files, allowing for files to be incrementally downloaded as described in the Internet download scenario.

■ You can add resource or data files to your assembly.

■ You can create assemblies consisting of types implemented in different programming languages.

💡 Note:总之,程序集是进行重用、版本控制和应用安全性设置的基本单元。如果多个类型能共享相同的版本号和安全性设置,建议将所有这些类型放在同一个文件中,而不是分散到多个文件中,更不要分散到多个程序集中,因为加载较少的程序集有助于减少工作集(working set),并缓解进程地址空间的碎片化。

Untitled

The existence of a manifest provides a level of indirection between consumers of the assembly and the partitioning details of the assembly and makes assemblies self-describing. Also, note that the file containing the manifest has metadata information that indicates which files are part of the assembly, but the individual files themselves do not have metadata information that specifies that they are part of the assembly.

💡 Note:包含清单的程序集文件还有一个 AssemblyRef 表。程序集全部文件引用的每个程序集在这个表中都有一个记录项。这样一来,工具只需打开程序集的清单,就可知道它引用的全部程序集,而不必打开程序集的其他文件。此外,清单元数据表其实并不包含从清单所在的 PE 文件导出的类型。这是一项优化措施,旨在减少 PE 文件中清单信息量。

There are many ways to add a module to an assembly. If you’re using the C# compiler to build a PE file with a manifest, you can use the /addmodule switch. To understand how to build a multifile assembly, let’s assume that we have two source code files:

■ RUT.cs, which contains rarely used types

■ FUT.cs, which contains frequently used types

csc /t:module RUT.cs

csc /out:MultiFileLibrary.dll /t:library /addmodule:RUT.netmodule FUT.cs

Untitled

As the client code executes, it calls methods. When a method is called for the first time, the CLR detects the types that the method references as a parameter, a return type, or as a local variable. The CLR then attempts to load the referenced assembly’s file that contains the manifest. If the type being accessed is in this file, the CLR performs its internal bookkeeping, allowing the type to be used. If the manifest indicates that the referenced type is in a different file, the CLR attempts to load the necessary file, performs its internal bookkeeping, and allows the type to be accessed. The CLR loads assembly files only when a method referencing a type in an unloaded assembly is called. This means that to run an application, all of the files from a referenced assembly do not need to be present.

💡 Note:元数据 token 是一个 4 字节的值。其中,高位字节指明 token 的类型 (0x01=TypeRef,0x02=TypeDef,0x23=AssemblyRef, 0x26=File (文件定义),0x27=ExportedType)。要获取完整列表,请参见 .NET Framework SDK 包含的 CorHdr.h
文件中的 CorTokenType
枚举类型。token 的三个低位字节指明对应的元数据表中的行。例如,0x26000001 这个实现 token 引用的是 File 表的第一行。大多数表的行从 1 而不是 0 开始编号。 TypeDef 表的行号实际从 2 开始。

//
// Token tags.
//
typedef enum CorTokenType
{
    mdtModule               = 0x00000000,       //
    mdtTypeRef              = 0x01000000,       //
    mdtTypeDef              = 0x02000000,       //
    mdtFieldDef             = 0x04000000,       //
    mdtMethodDef            = 0x06000000,       //
    mdtParamDef             = 0x08000000,       //
    mdtInterfaceImpl        = 0x09000000,       //
    mdtMemberRef            = 0x0a000000,       //
    mdtCustomAttribute      = 0x0c000000,       //
    mdtPermission           = 0x0e000000,       //
    mdtSignature            = 0x11000000,       //
    mdtEvent                = 0x14000000,       //
    mdtProperty             = 0x17000000,       //
    mdtMethodImpl           = 0x19000000,       //
    mdtModuleRef            = 0x1a000000,       //
    mdtTypeSpec             = 0x1b000000,       //
    mdtAssembly             = 0x20000000,       //
    mdtAssemblyRef          = 0x23000000,       //
    mdtFile                 = 0x26000000,       //
    mdtExportedType         = 0x27000000,       //
    mdtManifestResource     = 0x28000000,       //
    mdtGenericParam         = 0x2a000000,       //
    mdtMethodSpec           = 0x2b000000,       //
    mdtGenericParamConstraint = 0x2c000000,
    mdtString               = 0x70000000,       //
    mdtName                 = 0x71000000,       //
    mdtBaseType             = 0x72000000,       // Leave this on the high end value. This does not correspond to metadata table
} CorTokenType;

# Using the Assembly Linker

Instead of using the C# compiler, you might want to create assemblies by using the Assembly Linker utility, AL.exe. The Assembly Linker is useful if you want to create an assembly consisting of modules built from different compilers (if your compiler doesn’t support the equivalent of C#’s /addmodule switch) or perhaps if you just don’t know your assembly packaging requirements at build time. You can also use AL.exe to build resource-only assemblies, called satellite assemblies, which are typically used for localization purposes. I’ll talk about satellite assemblies later in the chapter.

The AL.exe utility can produce an EXE or a DLL PE file that contains only a manifest describing the types in other modules. To understand how AL.exe works, let’s change the way the MultiFileLibrary.dll assembly is built.

csc /t:module RUT.cs

csc /t:module FUT.cs

al /out: MultiFileLibrary.dll /t:library FUT.netmodule RUT.netmodule

Figure 2-3 shows the files that result from executing these statements.

Untitled

# Adding Resource Files to an Assembly

When using AL.exe to create an assembly, you can add a file as a resource to the assembly by using the /embed[resource] switch. This switch takes a file (any file) and embeds the file’s contents into the resulting PE file. The manifest’s ManifestResourceDef table is updated to reflect the existence of the resources.

AL.exe also supports a /link[resource] switch, which also takes a file containing resources. However, the /link[resource] switch updates the manifest’s ManifestResourceDef and FileDef tables, indicating that the resource exists and identifying which of the assembly’s files contains it. The resource file is not embedded into the assembly PE file; it remains separate and must be packaged and deployed with the other assembly files.

Like AL.exe, CSC.exe also allows you to combine resources into an assembly produced by the C# compiler. The C# compiler’s /resource switch embeds the specified resource file into the resulting assembly PE file, updating the ManifestResourceDef table. The compiler’s /linkresource switch adds an entry to the ManifestResourceDef and the FileDef manifest tables to refer to a stand-alone resource file.

💡 小结:程序集是一个或多个类型定义文件及资源文件的集合。在程序集的所有文件中,有一个文件容纳了清单。清单也是一个元数据表集合,表中主要包含了作为程序集组成部分的那些文件的名称。此外,还描述了程序集的版本、语言文化、发布者、公开导出的类型以及构成程序集的所有文件。类型为了顺利地进行打包、版本控制、安全保护以及使用,必须放在作为程序集一部分的模块中。而任意数量的模块和资源文件的组合则形成了多文件程序集。所谓多文件程序集指的是,在逻辑层面,拥有清单文件的文件本身已经完整的描述了自身,由于有了清单的存在,程序集的用户不用关心程序集的划分细节;但在物理层面,程序集所引用的一些模块可能又存在于别的文件当中。这也是 “使用程序集,可重用类型的逻辑表示与物理表示就可以分开” 这句话的含义。多文件程序集有以下优势:1. 不同的类型用不同的文件,使文件能以 “增量” 的方式下载。2. 可在程序集中添加资源或数据文件(使用 AL.exe 或 CSC.exe)。3. 程序集包含的多个类型可以用不同的编程语言来实现。遗憾的是,不能直接从 Microsoft Visual Studio 集成开发环境中创建多文件程序集。只能用命令行工具创建多文件程序集。

# Assembly Version Resource Information

When AL.exe or CSC.exe produces a PE file assembly, it also embeds into the PE file a standard Win32 version resource. Users can examine this resource by viewing the file’s properties. Application code can also acquire and examine this information at run time by calling System.Diagnostics.FileVersionInfo ’s static GetVersionInfo method with the assembly path as parameter. Figure 2-4 shows the Details tab of the MultiFileLibrary.dll Properties dialog box.

When building an assembly, you should set the version resource fields by using custom attributes that you apply at the assembly level in your source code. Here’s what the code that produced the version information in Figure 2-4 looks like.

using System.Reflection;
// FileDescription 版本信息:
[assembly: AssemblyTitle("MultiFileLibrary.dll")]
// Comments 版本信息:
[assembly: AssemblyDescription("This assembly contains MultiFileLibrary's types")]
// CompanyName 版本信息:
[assembly: AssemblyCompany("Wintellect")]
// ProductName 版本信息:
[assembly: AssemblyProduct("Wintellect (R) MultiFileLibrary's Type Library")]
// LegalCopyright 版本信息:
[assembly: AssemblyCopyright("Copyright (c) Wintellect 2013")]
// LegalTrademarks 版本信息:
[assembly:AssemblyTrademark("MultiFileLibrary is a registered trademark of Wintellect")]
// AssemblyVersion 版本信息:
[assembly: AssemblyVersion("3.0.0.0")]
// FILEVERSION/FileVersion 版本信息:
[assembly: AssemblyFileVersion("1.0.0.0")]
// PRODUCTVERSION/ProductVersion 版本信息:
[assembly: AssemblyInformationalVersion("2.0.0.0")]
// 设置 Language 字段 (参见 2.6 节” 语言文化 “)
[assembly:AssemblyCulture("")]

Untitled

💡 Note:Windows 资源管理器的属性对话框明显遗漏了一些特性值。最遗憾的是没有显示 AssemblyVersion 这个特性的值,因为 CLR 加载程序集时会使用这个值,详情将在第 3 章讨论。

# Version Numbers

the previous section, you saw that several version numbers can be applied to an assembly. All of these version numbers have the same format: each consists of four period-separated parts, as shown in Table 2-5.

Untitled

You’ll notice that an assembly has three version numbers associated with it. This is very unfortunate and leads to a lot of confusion. Let me explain each version number’s purpose and how it is expected to be used:

AssemblyFileVersion

This version number is stored in the Win32 version resource. This number is for information purposes only; the CLR doesn’t examine this version number in any way. Typically, you set the major and minor parts to represent the version you want the public to see. Then you increment the build and revision parts each time a build is performed. Ideally, Microsoft’s tool (such as CSC.exe or AL.exe) would automatically update the build and revision numbers for you (based on the date and time when the build was performed), but unfortunately, they don’t. This version number can be seen when using Windows Explorer and is typically used to identify a specific version of an assembly when troubleshooting a customer’s system.

AssemblyInformationalVersion

This version number is also stored in the Win32 version resource, and again, this number is for information purposes only; the CLR doesn’t examine or care about it in any way. This version number exists to indicate the version of the product that includes this assembly. For example, version 2.0 of a product might contain several assemblies; one of these assemblies is marked as version 1.0 because it’s a new assembly that didn’t ship in version 1.0 of the same product. Typically, you set the major and minor parts of this version number to represent the public version of your product. Then you increment the build and revision parts each time you package a complete product with all its assemblies.

AssemblyVersion

This version number is stored in the AssemblyDef manifest metadata table. The CLR uses this version number when binding to strongly named assemblies (discussed in Chapter 3). This number is extremely important and is used to uniquely identify an assembly. When starting to develop an assembly, you should set the major, minor, build, and revision numbers and shouldn’t change them until you’re ready to begin work on the next deployable version of your assembly. When Assembly-A references a strongly named Assembly-B, Assembly-B’s version is embedded inside Assembly-A’s AssemblyRef table’s entry. This way, when the CLR needs to load Assembly-B, it knows exactly which version Assembly-A was built and tested with. It is possible to have the CLR load a different version by using a binding redirect, which is discussed in Chapter 3.

💡 小结:AL.exe 或 CSC.exe 生成 PE 文件程序集时,还会在 PE 文件中嵌入标准的 Win32 版本资源,可以通过定制特性或者 AL.exe 命令行开关设置这些信息。通过查看文件属性可以检查该资源,但在属性对话框中并不会显示 AssemblyVersion 这个特性的,因为 CLR 加载程序集时会使用这个值,这个版本号唯一地标识了程序集,如果程序集 A 引用了强命名的程序集 B,程序集 B 的版本会嵌入程序集的 AssemblyRef 表。这样一来,当 CLR 需要加载程序集 B 时,就准确地知道当初生成和测试的是程序集的哪个版本。程序集有三个版本号,AssemblyFileVersion 和 AssemblyInformationalVersion 版本号仅供参考,CLR 既不检查,也不关心它。

# Culture

Like version numbers, assemblies also have a culture as part of their identity. For example, I could have an assembly that is strictly for German, another assembly for Swiss German, another assembly for US English, and so on. Cultures are identified via a string that contains a primary and a secondary tag (as described in RFC 1766). Table 2-6 shows some examples.

Untitled

In general, if you create an assembly that contains code, you don’t assign a culture to it. This is because code doesn’t usually have any culture-specific assumptions built into it. An assembly that isn’t assigned a culture is referred to as being culture neutral.

If you’re designing an application that has some culture-specific resources to it, Microsoft highly recommends that you create one assembly that contains your code and your application’s default (or fallback) resources. When building this assembly, don’t specify a culture. This is the assembly that other assemblies will reference when they create and manipulate types it publicly exposes.

Now you can create one or more separate assemblies that contain only culture-specific resources— no code at all. Assemblies that are marked with a culture are called satellite assemblies. For these satellite assemblies, assign a culture that accurately reflects the culture of the resources placed in the assembly. You should create one satellite assembly for each culture you intend to support.

You’ll usually use the AL.exe tool to build a satellite assembly. You won’t use a compiler because the satellite assembly should have no code contained within it. When using AL.exe, you specify the desired culture by using the /c[ulture]:text switch, where text is a string such as “en-US,” representing US English. When you deploy a satellite assembly, you should place it in a subdirectory whose name matches the culture text. For example, if the application’s base directory is C:\MyApp, the US English satellite assembly should be placed in the C:\MyApp\en-US subdirectory. At run time, you access a satellite assembly’s resources by using the System.Resources.ResourceManager class.

Normally, you shouldn’t build an assembly that references a satellite assembly. In other words, an assembly’s AssemblyRef entries should all refer to culture-neutral assemblies. If you want to access types or members contained in a satellite assembly, you should use reflection techniques as discussed in Chapter 23, “Assembly Loading and Reflection.”

💡 小结:除了版本号,程序集还将语言文化(culture)作为其身份标识的一部分。创建含代码的程序集时一半不指定具体的语言文化。这是因为代码只讲 “逻辑”,不涉及具体的语言文化。未指定具体语言文化的程序集称为语言文化中性(culture neutral)。标记了语言文化的程序集称为附属程序集(satellite assembly)。通常使用 AL.exe 生成程序集。不用编译器的是因为附属程序集本身就不该含有代码。部署附属程序集时,应该把它保存到专门的子目录中,子目录名称和语言文化的文本匹配。一般不要生成引用了附属程序集的程序集。换言之,程序集的 AssemblyRef 记录项只应引用语言文化中性的程序集。

# Simple Application Deployment (Privately Deployed Assemblies)

Windows Store apps have very strict rules about packaging assemblies, and Visual Studio will package all of an application’s required assemblies together into a single .appx file, which is either uploaded to the Windows Store or can be side-loaded onto a machine. When a user installs an .appx file, all the assemblies it contains are placed in a directory where the CLR will load them and Windows adds an application tile to the user's Start screen. If other users install the same .appx file, the previously installed assemblies are used and the new user simply gets a tile added to their Start screen. When a user uninstalls a Windows Store app, the system removes the tile from the user's Start screen. If no other users have the app installed, then Windows destroys the directory along with all the assemblies. Note that different users can install different versions of the same Windows Store app. To accommodate this, Windows installs the assemblies into different directories so that multiple versions of a single app can reside on a single machine simultaneously.

For desktop (non-Windows Store) applications, assemblies don’t dictate or require any special means of packaging. The easiest way to package a set of assemblies is simply to copy all of the files directly. For example, you could put all of the assembly files on a CD-ROM and ship it to the user with a batch file setup program that just copies the files from the CD to a directory on the user’s hard drive. Because the assemblies include all of the dependent assembly references and types, the user can just run the application and the runtime will look for referenced assemblies in the application’s directory. No modifications to the registry are necessary for the application to run. To uninstall the application, just delete all the files—that’s it!

Of course, you can package and install the assembly files by using other mechanisms, such as .cab files (typically used for Internet download scenarios to compress files and reduce download times). You can also package the assembly files into an MSI file for use by the Windows Installer service (MSIExec.exe). Using MSI files allows assemblies to be installed on demand the first time the CLR attempts to load the assembly. This feature isn’t new to MSI; it can perform the same demand-load functionality for unmanaged EXE and DLL files as well.

Of course, Visual Studio has a built-in mechanism that you can use to publish an application by displaying a project’s Properties pages and clicking the Publish tab. You can use the options available on the Publish tab to cause Visual Studio to produce an MSI file and copy the resulting MSI file to a website, FTP server, or file path. The MSI file can also install any prerequisite components such as the .NET Framework or Microsoft SQL Server Express Edition. Finally, the application can automatically check for updates and install them on the user’s machine by taking advantage of ClickOnce technology.

Assemblies deployed to the same directory as the application are called privately deployed assemblies because the assembly files aren’t shared with any other application (unless the other application is also deployed to the same directory). Privately deployed assemblies are a big win for developers, end users, and administrators because they can simply be copied to an application’s base directory, and the CLR will load them and execute the code in them. In addition, an application can be uninstalled by simply deleting the assemblies in its directory. This allows simple backup and restore as well.

This simple install/move/uninstall scenario is possible because each assembly has metadata indicating which referenced assembly should be loaded; no registry settings are required. In addition, the referencing assembly scopes every type. This means that an application always binds to the same type it was built and tested with; the CLR can’t load a different assembly that just happens to provide a type with the same name. This is different from COM, in which types are recorded in the registry, making them available to any application running on the machine.

💡 小结:对于 Windows Store 应用程序的打包,Visual Studio 会将应用程序所有必要的程序集打包成一个.appx 文件。用户安装.appx 文件时,其中包含的所有程序集都进入一个目录。对于非 Windows Store 的桌面应用,打包一组程序集最简单的方式就是直接复制所有文件。由于已经包含了所有依赖的程序集和类型,所以用户能直接运行应用程序,“运行时” 会在应用程序目录查找引用的程序集。不需要对注册表进行任何修改就能运行程序。卸载的话直接删除所有文件即可。也可以使用.cab 文件、MSI 文件打包和安装程序集。在应用程序基目录或者子目录部署的程序集称为私有部署的程序集(privately deployed assembly),这是因为程序集文件不和其他任何应用程序共享(除非其他应用程序徐也部署到改目录)。子所以能实现这种简单的安装 / 移动 / 卸载,是因为每个程序集都用元数据标明了自己引用的程序集,不需要注册表设置。一个应用程序总是和它生成和测试时的类型绑定。即便另一个程序集恰好提供了同名类型,CLR 也不可能加载那个程序集。这一点有别于 COM。在 COM 中,类型是在注册表中登记的,造成机器上运行的任何应用程序都能使用那些类型。

# Simple Administrative Control (Configuration)

To allow administrative control over an application, a configuration file can be placed in the application’s directory. An application’s publisher can create and package this file. The setup program would then install this configuration file in the application’s base directory. In addition, the machine’s administrator or an end user could create or modify this file. The CLR interprets the content of this file to alter its policies for locating and loading assembly files.

These configuration files contain Extensible Markup Language (XML) and can be associated with an application or with the machine. Using a separate file (versus registry settings) allows the file to be easily backed up and also allows the administrator to copy the application to another machine—just copy the necessary files and the administrative policy is copied too.

Whenever the CLR attempts to locate an assembly file, it always looks in the application’s directory first, and if it can’t find the file there, it looks in the AuxFiles subdirectory. You can specify multiple semicolon-delimited paths for the probing element’s privatePath attribute. Each path is considered relative to the application’s base directory. You can’t specify an absolute or a relative path identifying a directory that is outside of the application’s base directory. The idea is that an application can control its directory and its subdirectories but has no control over other directories.

The name and location of this XML configuration file is different depending on the application type:

■ For executable applications (EXEs), the configuration file must be in the application’s base directory, and it must be the name of the EXE file with “.config” appended to it.

■ For Microsoft ASP.NET Web Form applications, the file must be in the Web application’s virtual root directory and is always named Web.config. In addition, subdirectories can also contain their own Web.config file, and the configuration settings are inherited. For example, a Web application located at http://Wintellect.com/Training would use the settings in the Web.config files contained in the virtual root directory and in its Training subdirectory.

As mentioned at the beginning of this section, configuration settings apply to a particular application and to the machine. When you install the .NET Framework, it creates a Machine.config file. There is one Machine.config file per version of the CLR you have installed on the machine.

The Machine.config file is located in the following directory:

%SystemRoot%\Microsoft.NET\Framework\version\CONFIG

Of course, %SystemRoot% identifies your Windows directory (usually C:\WINDOWS), and version is a version number identifying a specific version of the .NET Framework (something like v4.0.#####).

Settings in the Machine.config file represent default settings that affect all applications running on the machine. An administrator can create a machine-wide policy by modifying the single Machine.config file. However, administrators and users should avoid modifying this file because it contains many settings related to various things, making it much more difficult to navigate. Plus, you want the application’s settings to be backed up and restored, and keeping an application’s settings in the application-specific configuration file enables this.

💡 小结:为了实现对应用程序的管理控制,可在应用程序目录放入一个配置文件。应用程序的发布者可创建并打包该文件。安装程序会将配置文件安装到应用程序的基目录。另外,计算机管理员或最终用户也能创建或修改该文件。CLR 会解析文件内容来更改程序集文件的定位和加载策略。配置文件包含 XML 代码,它既能和应用程序关联,也能和机器关联。CLR 尝试定位程序集文件时,总是先在应用程序基目录查找。通过 XML 格式的配置文件可以指定包含程序集的子目录。对于可执行应用程序(EXE),配置文件必须在应用程序的基目录,而且必须采用 EXE 文件全名作为文件名,再附加.config 扩展名。对于 Microsoft ASP.NET Web 窗体应用程序,文件必须在 Web 应用程序的虚拟根目录中,而且总是命名为 Web.config。除此之外,子目录可以包含自己的 Web.config,而且配置设置会得到继承。应用于机器的配置设置指的是:.NET Framework 在安装时会创建一个 Machine.config。机器上安装的每个版本的 CLR 都有一个对应的 Machine.config。Machine.config 文件的设置将会影响机器上运行的所有应用程序的默认设置。