CLR via C#, 4th -- 【CLR基础】 -- 第2章生成、打包、部署和管理应用程序及类型

2.1.NET Framework部署目标

Windows多年来一直因为不稳定和过于复杂而口碑不佳,主要原因是应用程序要用别人实现的代码进行生成和测试。

2.2 将类型生成到模块中

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

下面命令行指示C#编译器生成名为Program.exe的可执行文件(/out:Program.exe),生成的文件是Win32控制台应用程序类型(/t[arget]:exe

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

C#编译器处理源文件时,发现代码引用了System.Console类型的WriteLine方法。此时,编译器要核实该类型确实存在,它确实有WriteLine方法,而且传递的实参与方法形参匹配。由于该类型在C#源代码中没有定义,所以要顺利通过编译,必须向C#编译器提供一组程序集,使它能解析对外部类型的引用。在上述命令行中,我添加了/r[eference]:MSCorLib.dll开关,告诉编译器在MSCorLib.dll程序集中查找外部类型。

MSCorLib.dll是特殊文件,它包含所有核心类型,包括Byte,Char,String,Int32等等。事实上,由于这些类型使用得如此频繁,以至于C#编译器会自动引用MSCorLib.dll程序集。换言之,命令行其实可以简化成下面这样(省略/r开关):

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

此外,由于/out:Program.exe和/texe开关是C#编译器的默认设定,所以能继续简化成以下形式:

csc.exe Program.cs

思考一下C#编译器生成的Program.exe文件。这个文件到底是什么?

它是标准PE(可移植执行体,Portable Executable)文件。这意味着运行32位或64位Windows的计算机能加载它,并能通过它执行某些操作。

响应文件

响应文件是包含一组编译器命令行开关的文本文件。在命令行中,请在@符号之后指定响应文件的名称。

假定MyProject.rsp包含以下文本:

/out:MyProject.exe  
/target:winexe

为了让CSC.exe使用这些设置,可以像下面这样调用它:

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

2.3元数据概述

Program.exe文件中到底有什么?

托管PE文件由4部分构成:PE32(+)头、CLR头、元数据以及IL。

元数据是由几个表构成的二进制数据块。有三种表,分别是定义表(definition table)、引用表(reference table)和清单表(manifest table)。

TABLE 2-1   Common Definition Metadata Tables

Metadata Definition Table Name

Description

ModuleDef

Always contains one entry that identifies the module. The entry includes the module’s file name and extension (without path) and a module ver-sion ID (in the form of a GUID created by the compiler). This allows the file to be renamed while keeping a record of its original name. However,

renaming a file is strongly discouraged and can prevent the CLR from locating an assembly at run time, so don’t do this.

TypeDef

Contains one entry for each type defined in the module. Each entry includes the type’s name, base type, and flags ( public ,  private, etc.)

and contains indexes to the methods it owns in the MethodDef table, the fields it owns in the FieldDef table, the properties it owns in the PropertyDef table, and the events it owns in the EventDef table.

MethodDef

Contains  one entry for each method defined in the module. Each entry

includes the method’s name, flags ( private,  public ,  virtual ,  abstract ,  static, final , etc.), signature, and offset within the module where its IL code can be found. Each entry can also refer to a ParamDef table entry in which more information about the method’s parameters can be found.

FieldDef

Contains  one entry for every field defined in the module. Each entry in -cludes flags ( private,  public , etc.), type, and name.

ParamDef

Contains  one entry for each parameter defined in the module. Each entry includes flags ( in ,  out,  retval , etc.), type, and name.

PropertyDef

Contains one entry for each property defined in the module. Each entry 

includes flags, type, and name.

EventDef

Contains  one entry for each event defined in the module. Each entry includes flags and name.

TABLE 2-2   Common Reference Metadata Tables

Metadata Reference Table Name

Description

AssemblyRef

Contains  one entry for each assembly referenced by the module. Each entry includes the information necessary to bind to the assembly: the assembly’s name (without path and extension), version number, culture, and public key token (normally a small hash value generated from the publisher’s public key, identifying the referenced assembly’s publisher).

Each entry also contains some flags and a hash value. This hash value was intended to be a checksum of the referenced assembly’s bits. The CLR completely ignores this hash value and will probably continue to do so in the future.

ModuleRef

Contains  one entry for each PE module that implements types referenced by this module. Each entry includes the module’s file name and extension (without path). This table is used to bind to types that are implemented in different modules of the calling assembly’s module.

TypeRef

Contains one entry for each type referenced by the module. Each en-try includes the type’s name and a reference to where the type can be found. If the type is implemented within another type, the reference will indicate a TypeRef entry. If the type is implemented in the same module, the reference will indicate a ModuleDef entry. If the type is implemented in another module within the calling assembly, the reference will indicate a ModuleRef entry. If the type is implemented in a different assembly, the reference will indicate an AssemblyRef entry.

MemberRef

Contains one entry for each member (fields and methods, as well as property and event methods) referenced by the module. Each entry in-cludes the member’s name and signature and points to the TypeRef entry for the type that defines the member.

2.4 将模块合并成程序集

程序集(assembly)是一个或多个类型定义文件及资源文件的集合。

在程序集的所有文件中,有一个文件容纳了清单(manifest)。清单也是一个元数据表集合,表中主要包含作为程序集组成部分的那些文件的名称。此外,还描述了程序集的版本、语言文化、发布者、公开导出的类型以及构成程序集的所有文件。

CLR操作的是程序集。换言之,CLR总是首先加载包含“清单”元数据表的文件,再根据“清单”来获取程序集中的其他文件的名称。

下面列出了程序集的重要特点:

  • 程序集定义了可重用的类型。
  • 程序集用一个版本号标记。
  • 程序集可以关联安全信息。

除了包含清单元数据表的文件,程序集其他单独的文件并不具备上述特点。

Microsoft为什么引入“程序集”的概念?

这是因为使用程序集,可重用类型的逻辑表示与物理表示就可以分开。

例如,程序集可能包含多个类型。可以将常用类型放到一个文件中,不常用类型放到另一个文件中。

使用多文件程序集的三点理由:

  • 不同的类型用不同的文件,使文件能以“增量”方式下载,另外,将类型划分到不同的文件中,可以对购买和安装的应用程序进行部分或分批打包/部署。
  • 可在程序集中添加资源或数据文件。
  • 程序集包含的各个类型可以用不同的编程语言来实现。例如,一些类型可以用C#实现,一些用Visual Basic实现,其他则用其他语言实现。编译用C#写的类型时,编译器会生成一个模块。编译用Visual Basic写的类型时,编译器会生成另一个模块。然后可以用工具将所有模块合并成单个程序集。

总之,程序集是进行重用、版本控制和应用安全性设置的基本单元。它允许将类型和资源文件划分到单独的文件中。

TABLE 2-3   Manifest Metadata Tables

Manifest Metadata Table Name

Description

AssemblyDef

Contains a single entry if this module identifies an assembly. The entry includes the assembly’s name (without path and extension), version (major, minor, build, and revision), culture,flags, hash algorithm, and the publisher’s public key (which can be  null).

FileDef

Contains one entry for each PE and resource file that is part of the assembly (except the file containing the manifest because it appears as the single entry in the AssemblyDef table).

The entry includes the file’s name and extension (without path), hash value, and flags. If this assembly consists only of its own file, the FileDef table has no entries.

ManifestResourceDef

Contains one entry for each resource that is part of the assembly. The entry includes the resource’s name, flags ( public  if visible outside the assembly and  private otherwise), and an index into the FileDef table indicating the file that contains the resource file or stream. If the resource isn’t a stand-alone file (such as a .jpg or a .gif), the resource is a stream contained within a PE file. For an embedded resource, the entry also includes an offset indicating the start of the resource stream within the PE file.

ExportedTypesDef

Contains one entry for each public type exported from all of the assembly’s PE modules. The entry includes the type’s name, an index into the FileDef table (indicating which of this assembly’s files implements the type), and an index into the TypeDef table. Note: To save file space, types exported from the file containing the manifest are not repeated in this table because the type information is available using the metadata’s TypeDef table.

指定以下任何命令行开关,C#编译器都会生成程序集:/t[arget]:exe , /t[arget]:winexe, /t[arget]: appcontainerexe , /t[arget]: library(dll), 或者 /t[arget]:winmdobj。所有这些开关都会造成编译器生成含有清单元数据表的PE文件。

除了这些开关,C#编译器还支持/t[arget]:module 开关。这个开关指示编译器生成一个不包含清单元数据表的PE文件。这样生成的肯定是一个DLL PE文件。CLR要想访问其中的任何类型,必须先将该文件添加到一个程序集中。使用/t:module开关时,C编译器默认为输出文件使用.netmodule扩展名。

假定有两个源代码文件:

  • RUT.cs,其中包含不常用类型。
  • FUT.cs,其中包含常用类型。
csc /t:module RUT.cs
csc /out:MultiFileLibrary.dll /t:library /addmodule:RUT.netmodule FUT.cs

一个方法首次调用时,CLR检测作为参数、返回值或者月部变量而被方法引用的类型。然后,CLR尝试加载所引用程序集中含有清单的文件。

注意,CLR并非一上来就加载所有可能用到的程序集。只有在调用的方法确实引用了未加载程序集中的类型时,才会加载程序集。换言之,为了让应用程序运行起来,并不要求被引用程序集的所有文件都存在。

2.4.1 使用Visual Studio IDE将程序集添加到项目中

打开解决方案资源管理器,右击想添加引用的项目,选择“添加引用”来打开“引用管理器

2.4.2 使用程序集链接器

除了使用C#编译器,还可以使用“程序集链接器”实用程序AL.exe来创建程序集。

2.4.3 为程序集添加资源文件

AL.exe或CSC.exe创建程序集时,可用/embed[resource]开关将文件作为资源添加到程序集.

还支持/link[resource]开关,它同样获取包含资源的文件,但只是更新清单的ManifestResourceDer和FileDef表以反映新资源的存在,指出资源包含在程序集的哪个文件中。资源文件不会嵌入程序集PE文件中:相反,它保持独立,而且必须和其他程序集文件一起打包和部署。

2.5 程序集版本资源信息

AL.exe或CSC.exe生成PE文件程序集时,还会在PE文件中嵌入标准的Win32版本资源。可查看文件属性来检查该资源。

版本号

TABLE 2-5   Format of Version Numbers

 

Major Number

Minor Number

Build Number

Revision Number

Example

2

5

719

2

示例版本号:2.5.719.2,前两个编号构成了公众对版本的理解。公众会将这个例子看成是程序集的2.5版本。第三个编号719是程序集的build号。如果公司每天都主成程序集,那么每天都应该递增这个build号。最后一个编号2指出当前build的修订次数。

AssemblyFileVersion \ AssemblyInformationalVersion \ AssemblyVersion

2.6 语言文化

创建一个或多个单独的程序集,只在其中包含语言文化特有的资源-不要包含任何代码。标记了语言文化的程序集称为附属程序集(satellite assembly)

使用/c[ulture]:text开关指定语言文化。其中text是语言文化字符串,例如"en-US"代表美国英语。

2.7 简单应用程序部署(私有部署的程序集)

Windows Store应用对程序集的打包有一套很严格的规则,Visual Studio会将应用程序所有必要的程序集打包成一个.appx文件。

对于非Windows Store的桌面应用,程序集的打包方式没有任何特殊要求。打包一组程序集最简单的方式就是直接复制所有文件。

在应用程序基目录或者子目录部署的程序集称为私有部署的程序集(privately deployec assembly),这是因为程序集文件不和其他任何应用程序共享(除非其他应用程序也部署到该目录)。

之所以能实现这种简单的安装/移动/卸载,是因为每个程序集都用元数据注明了自己引用的程序集,不需要注册表设置。另外,引用(别的程序集的)程序集限定了每个类型的作用域。

2.8简单管理控制(配置)

为了实现对应用程序的管理控制,可在应用程序目录放入一个配置文件(.config)。应用程序的发布者可创建并打包该文件。安装程序会将配置文件安装到应用程序的基目录。

  • 对于可执行应用程序(EXE),配置文件必须在应用程序的基目录,而且必须采用EXE文件全名作为文件名,再附加.config扩展名。
  • 对于Microsot ASP.NET Web窗体应用程序,文件必须在Web应用程序的虚拟根目录中,而且总是命名为Web.config。除此之外,子目录可以包含自己的Web.config,而且配置设置会得到继承。
posted @ 2019-10-20 21:20  FH1004322  阅读(176)  评论(0)    收藏  举报