程序集是任何 .NET Framework 应用程序的基本构造块。例如,在生成简单的 C# 应用程序时,Visual Studio 创建一个单个可移植可执行 (PE) 文件形式的程序集,明确地说就是一个 EXE 或 DLL。
程序集包含描述它们自己的内部版本号和它们包含的所有数据和对象类型的详细信息的元数据。
程序集仅在需要时才加载。如果不使用程序集,则不会加载。这意味着程序集可能是在大型项目中管理资源的有效途径。
程序集可以包含一个或多个模块。例如,计划较大的项目时,可以让几个各个开发人员负责单独的模块,并通过组合所有这些模块来创建单个程序集。
一、程序集概述
程序集具有以下特点:
·程序集作为 .exe 或 .dll 文件实现。
·通过将程序集放在全局程序集缓存中,可在多个应用程序之间共享程序集。
·要将程序集包含在全局程序集缓存中,必须对程序集进行强命名。
·程序集仅在需要时才加载到内存中。
·可以使用反射以编程方式获取关于程序集的信息。
·如果加载程序集的目的只是对其进行检查,应使用诸如 ReflectionOnlyLoadFrom 的方法。
·可以在单个应用程序中使用相同程序集的两个版本。
二、友元程序集
可以从一个程序集访问另一个程序集中的内部类型或内部成员。
备注:
友元程序集功能用于访问内部成员;私有类型和私有成员仍然不可访问。
若要使程序集(程序集 B)能够访问另一个程序集(程序集 A)的内部类型和成员,应使用程序集 A 中的 InternalsVisibleToAttribute 属性。
说明:
在对将要访问另一个程序集(程序集 A)的内部类型或内部成员的程序集(程序集 B)进行编译时,必须用 /out 编译器选项显式指定输出文件的名称(.exe 或 .dll)。这是必需的,因为当编译器将生成的程序集绑定到外部引用时,尚未为该程序集生成名称。
StrongNameIdentityPermission 类还提供共享类型的功能,其与友元程序集的区别如下:
·StrongNameIdentityPermission 应用于单个类型,而友元程序集应用于整个程序集。
·如果希望程序集 B 能够共享程序集 A 中的数百个类型,则必须使用 StrongNameIdentityPermission 修饰所有这些类型;而使用友元程序集时只需声明一次友元关系。
·使用 StrongNameIdentityPermission 时,想要共享的类型必须声明为公共类型。使用友元程序集时,会将共享类型声明为内部类型。
·有关如何生成可访问程序集中的非公共类型的 .netmodule 的信息,请参见 /moduleassemblyname。
示例
在此示例中,程序集使内部类型和内部成员对名为 called cs_friend_assemblies_2 的程序集可见。
// cs_friend_assemblies.cs
// compile with: /target:library
using System.Runtime.CompilerServices;
using System;
[assembly:InternalsVisibleTo("cs_friend_assemblies_2")]
// internal by default
class Class1
{
public void Test()
{
Console.WriteLine("Class1.Test");
}
}
// public type with internal member
public class Class2
{
internal void Test()
{
Console.WriteLine("Class2.Test");
}
}
在此示例中,程序集使用程序集 cs_friend_assemblies.dll 中的内部类型和内部成员。
注意,必须显式指定输出文件的名称 (/out:cs_friend_assemblies_2.exe)。
如果此程序集允许另一个程序集(程序集 C)访问它的内部类型和成员,则程序集 C 不会自动变成程序集 cs_friend_assemblies.dll 的友元。
// cs_friend_assemblies_2.cs
// compile with: /reference:cs_friend_assemblies.dll /out:cs_friend_assemblies_2.exe
public class M
{
static void Main()
{
// access an internal type
Class1 a = new Class1();
a.Test();
Class2 b = new Class2();
// access an internal member of a public type
b.Test();
}
}
Class1.Test
Class2.Test
此示例演示了如何使内部类型和成员对具有强名称的程序集可用。
若要生成 keyfile 并显示公钥,请使用下面的 sn.exe 命令序列:
·sn -k friend_assemblies.snk // 生成强名称密钥
·sn -p friend_assemblies.snk key.publickey // 将公钥从 key.snk 提取到 key.publickey 中
·sn -tp key.publickey // 显示存储在文件 key.publickey 中的公钥
用 /keyfile 将 keyfile 传递到编译器。
// cs_friend_assemblies_3.cs
// compile with: /target:library /keyfile:friend_assemblies.snk
using System.Runtime.CompilerServices;
[assembly:InternalsVisibleTo("cs_friend_assemblies_4, PublicKey=0024000004800000940000000602000000240000525341310004000001000100031d7b6f3abc16c7de526fd67ec2926fe68ed2f9901afbc5f1b6b428bf6cd9086021a0b38b76bc340dc6ab27b65e4a593fa0e60689ac98dd71a12248ca025751d135df7b98c5f9d09172f7b62dabdd302b2a1ae688731ff3fc7a6ab9e8cf39fb73c60667e1b071ef7da5838dc009ae0119a9cbff2c581fc0f2d966b77114b2c4")]
class Class1
{
public void Test()
{
System.Console.WriteLine("Class1.Test");
}
}
此示例演示如何使用对具有强名称的程序集可用的内部类型和成员。
// cs_friend_assemblies_4.cs
// compile with: /keyfile:friend_assemblies.snk /reference:cs_friend_assemblies_3.dll /out:cs_friend_assemblies_4.exe
public class M
{
static void Main()
{
Class1 a = new Class1();
a.Test();
}
}
三、如何:与其他应用程序共享程序集
程序集可以是私有的也可以是共享的:默认情况下,大多数简单的 C# 程序都包含一个私有程序集,原因是不打算将该程序集供其他应用程序使用。
为了与其他应用程序共享程序集,必须将该程序集置于 全局程序集缓存 (GAC) 中。
共享程序集:
·创建程序集。
·为程序集指定一个强名称。
·为程序集指定版本信息。
·将您的程序集添加到全局程序集缓存中。
·从其他应用程序中访问该程序集包含的类型。
四、如何:加载和卸载程序集
程序引用的程序集将在生成时自动加载,不过也可以在运行时将特定的程序集加载到当前应用程序域中。
没有办法卸载单独的程序集而不卸载包含它的所有应用程序域。即使程序集已在范围之外,实际的程序集文件仍然保持被加载,直至包含它的所有应用程序域都被卸载。
如果想要卸载某些程序集而不卸载其他程序集,可考虑创建新的应用程序域,在该域中执行代码,然后卸载该应用程序域。
将程序集加载到应用程序域中:
·使用 AppDomain 和 System.Reflection 类中包含的几个加载方法之一。有关更多信息,请参见将程序集加载到应用程序域中。
卸载应用程序域:
·没有办法卸载单独的程序集而不卸载包含它的所有应用程序域。使用 AppDomain 中的 Unload 方法可卸载应用程序域。
五、如何:确定文件是否为程序集
当且仅当一个文件是托管文件并且在其元数据中包含程序集入口时,该文件才是一个程序集。
如何手动确定一个文件是否为程序集:
·启动 MSIL 反汇编程序 (Ildasm.exe)。
·加载希望测试的文件。
·如果 ILDASM 报告该文件不是可移植的可执行 (PE) 文件,则它不是程序集。有关更多信息,请参见主题 如何:查看程序集内容。
如何以编程方式确定一个文件是否为程序集:
·调用 GetAssemblyName 方法,并向其传递正在测试的文件的完整文件路径和名称。
·如果引发 BadImageFormatException 异常,则该文件不是程序集。
示例:
此示例测试一个 DLL 以确定它是否为程序集。
class TestAssembly
{
static void Main()
{
try
{
System.Reflection.AssemblyName testAssembly =
System.Reflection.AssemblyName.GetAssemblyName(@"C:/Windows/Microsoft.NET/Framework/v3.5/System.Net.dll");
System.Console.WriteLine("Yes, the file is an Assembly.");
}
catch (System.IO.FileNotFoundException)
{
System.Console.WriteLine("The file cannot be found.");
}
catch (System.BadImageFormatException)
{
System.Console.WriteLine("The file is not an Assembly.");
}
catch (System.IO.FileLoadException)
{
System.Console.WriteLine("The Assembly has already been loaded.");
}
}
}
/* Output (with .NET Framework 3.5 installed):
Yes, the file is an Assembly.
*/
GetAssemblyName 方法加载测试文件,然后在读取信息之后释放它。
六、extern
extern 修饰符用于声明在外部实现的方法。extern 修饰符的常见用法是在使用 Interop 服务调入非托管代码时与 DllImport 属性一起使用。在这种情况下,还必须将方法声明为 static,如下示例所示:
[DllImport("avifil32.dll")]
private static extern void AVIFileInit();
extern 关键字还可以定义外部程序集别名,使得可以从单个程序集中引用同一组件的不同版本。
将 abstract 和 extern 修饰符一起使用来修改同一成员是错误的。使用 extern 修饰符意味着方法在 C# 代码的外部实现,而使用 abstract 修饰符意味着在类中未提供方法实现。
extern 关键字在使用上比在 C++ 中有更多限制。
示例
在该示例中,程序接收来自用户的字符串并将该字符串显示在消息框中。程序使用从 User32.dll 库导入的 MessageBox 方法。
//using System.Runtime.InteropServices;
class ExternTest
{
[DllImport("User32.dll", CharSet=CharSet.Unicode)]
public static extern int MessageBox(int h, string m, string c, int type);
static int Main()
{
string myString;
Console.Write("Enter your message: ");
myString = Console.ReadLine();
return MessageBox(0, myString, "My Message Box", 0);
}
}
此示例使用 C 程序创建一个 DLL,在下一示例中将从 C# 程序调用该 DLL。
// cmdll.c
// Compile with: /LD
int __declspec(dllexport) SampleMethod(int i)
{
return i*10;
}
该示例使用两个文件 CM.cs 和 Cmdll.c 来说明 extern。C 文件是示例 2 中创建的外部 DLL,它从 C# 程序内调用。
// cm.cs
using System;
using System.Runtime.InteropServices;
public class MainClass
{
[DllImport("Cmdll.dll")]
public static extern int SampleMethod(int x);
static void Main()
{
Console.WriteLine("SampleMethod() returns {0}.", SampleMethod(5));
}
}
SampleMethod() returns 50.
七、公共语言运行库中的程序集
1、程序集概述
程序集是 .NET Framework 编程的基本组成部分。程序集执行以下功能:
·包含公共语言运行库执行的代码。如果可移植可执行 (PE) 文件没有相关联的程序集清单,则将不执行该文件中的 Microsoft 中间语言 (MSIL) 代码。请注意,每个程序集只能有一个入口点(即 DllMain、WinMain 或 Main)。
·程序集形成安全边界。程序集就是在其中请求和授予权限的单元。
·程序集形成类型边界。每一类型的标识均包括该类型所驻留的程序集的名称。在一个程序集范围内加载的 MyType 类型不同于在其他程序集范围内加载的 MyType 类型。
·程序集形成引用范围边界。程序集的清单包含用于解析类型和满足资源请求的程序集元数据。它指定在该程序集之外公开的类型和资源。该清单还枚举它所依赖的其他程序集。
·程序集形成版本边界。程序集是公共语言运行库中最小的可版本化单元,同一程序集中的所有类型和资源均会被版本化为一个单元。程序集的清单描述您为任何依赖项程序集所指定的版本依赖性。
·程序集形成部署单元。当一个应用程序启动时,只有该应用程序最初调用的程序集必须存在。其他程序集(例如本地化资源和包含实用工具类的程序集)可以按需检索。这就使应用程序在第一次下载时保持精简。
·程序集是支持并行执行的单元。
程序集可以是静态的或动态的。静态程序集可以包括 .NET Framework 类型(接口和类),以及该程序集的资源(位图、JPEG 文件、资源文件等)。静态程序集存储在磁盘上的可移植可执行 (PE) 文件中。您还可以使用 .NET Framework 来创建动态程序集,动态程序集直接从内存运行并且在执行前不存储到磁盘上。您可以在执行动态程序集后将它们保存在磁盘上。
有几种创建程序集的方法。您可以使用过去用来创建 .dll 或 .exe 文件的开发工具,例如 Visual Studio 2005。您可以使用 Windows 软件开发工具包 (SDK) 中提供的工具来创建带有在其他开发环境中创建的模块的程序集。您还可以使用公共语言运行库 API(例如 Reflection.Emit)来创建动态程序集。
2、程序集优点
程序集旨在简化应用程序部署并解决在基于组件的应用程序中可能出现的版本控制问题。
最终用户和开发人员比较熟悉当今基于组件的系统所产生的版本控制和部署问题。一些最终用户曾经历过在计算机上安装新应用程序失败的事情,发现已有应用程序突然停止工作。许多开发人员花费了大量的时间来使所有必需的注册表项保持一致,以便激活 COM 类。
通过在 .NET Framework 中使用程序集,许多开发问题得以解决。因为程序集是不依赖于注册表项的自述组件,所以程序集使无相互影响的应用程序安装成为可能。程序集还使应用程序的卸载和复制得以简化。
版本控制问题:
·目前,Win32 应用程序存在两类版本控制问题:
·版本控制规则不能在应用程序的各段之间表达,并且不能由操作系统强制实施。目前的办法依赖于向后兼容,而这通常很难保证。接口定义一经发布就必须是静态的,并且单段代码必须保持与以前版本向后兼容。此外,通常要对代码进行设计,以便在任意给定时间在计算机上只能出现和执行代码的一个版本。
·没有办法在创建到一起的多套组件集与运行时提供的那套组件之间保持一致。
这两类版本控制问题结合在一起产生了 DLL 冲突,在这些冲突中,安装一个应用程序可能会无意间破坏现有的应用程序,因为所安装的某个软件组件或 DLL 与以前的版本不完全向后兼容。出现此情况后,系统不支持诊断和解决此问题。
最终解决 DLL 冲突:
Microsoft® Windows® 2000 开始致力于解决这些问题。它所提供的两个功能可以部分地解决 DLL 冲突:
·Windows 2000 使您能够创建这样的客户端应用程序,其中的 .dll 依赖文件与该应用程序的 .exe 文件位于相同的目录中。Windows 2000 经过配置,能够在检查完全限定的路径或搜索常规路径前,检查 .exe 文件所在目录中的组件。这使组件可以独立于其他应用程序所安装和使用的组件。
·Windows 2000 锁定 System32 目录中随操作系统提供的文件,使这些文件不会在安装应用程序时被无意替换。
公共语言运行库使用程序集来继续致力于 DLL 冲突的彻底解决。
程序集解决方案:
为了解决版本控制问题以及导致 DLL 冲突的其余问题,运行库使用程序集来执行以下功能:
·使开发人员能够指定不同软件组件之间的版本规则。
·提供强制实施版本控制规则的结构。
·提供允许同时运行多个版本的软件组件(称作并行执行)的基本结构。
3、程序集内容
通常,静态程序集可能由以下四个元素组成:
- 程序集清单,包含程序集元数据。
- 类型元数据。
- 实现这些类型的 Microsoft 中间语言 (MSIL) 代码。
- 资源集。
只有程序集清单是必需的,但也需要类型或资源来向程序集提供任何有意义的功能。
程序集中的这些元素有分组几种方法。您可以将所有元素分组到单个物理文件中,如下图所示。
单文件程序集
或者,可以将一个程序集的元素包含在几个文件中。这些文件可能是编译代码的模块 (.netmodule)、资源(例如 .bmp 或 .jpg 文件)或应用程序所需的其他文件。在您希望组合以不同语言编写的模块并优化应用程序的下载过程时,可创建一个多文件程序集,优化下载过程的方法是将很少使用的类型放入只在需要时才下载的模块中。
在下图中,一个假想应用程序的开发人员已选择将一些实用工具代码单独放入另一个模块中,同时在其原文件中保留一个较大的资源文件(在此例中为一个 .bmp 图像)。.NET Framework 只在文件被引用时下载该文件;通过将很少引用的代码保留在独立于应用程序的文件中来优化代码下载。
多文件程序集
说明: |
构成多文件程序集的那些文件实际上并非由文件系统来链接。它们而是通过程序集清单进行链接,公共语言运行库将这些文件作为一个单元来管理。 |
在此插图中,所有三个文件均属于一个程序集,如 MyAssembly.dll 所包含的程序集清单文件中所述。对于该文件系统,这三个文件是三个独立的文件。请注意,文件 Util.netmodule 被编译为一个模块,因为它不包含任何程序集信息。在创建了程序集后,该程序集清单就被添加到 MyAssembly.dll,指示程序集与 Util.net 模块和 Graphic.bmp 的关系。
现在设计源代码时,您会作出有关如何将应用程序的功能划分到一个或多个文件的明确的决定。在设计 .NET Framework 代码时,您也将作出类似的决定,即如何将应用程序的功能划分到一个或多个程序集中。
4、程序集清单
每一程序集,无论是静态的还是动态的,均包含描述该程序集中各元素彼此如何关联的数据集合。程序集清单就包含这些程序集元数据。程序集清单包含指定该程序集的版本要求和安全标识所需的所有元数据,以及定义该程序集的范围和解析对资源和类的引用所需的全部元数据。程序集清单可以存储在具有 Microsoft 中间语言 (MSIL) 代码的 PE 文件(.exe 或 .dll)中,也可存储在只包含程序集清单信息的独立 PE 文件中。
以下插图显示了清单的不同存储方法。
程序集的类型
对于有一个关联文件的程序集,该清单将被合并到 PE 文件中以构成单文件程序集。您可以创建有独立的清单文件,或清单被合并到同一多文件程序集中某一 PE 文件的多文件程序集。
每一程序集的清单均执行以下功能:
·枚举构成该程序集的文件。
·控制对该程序集的类型和资源的引用如何映射到包含其声明和实现的文件。
·枚举该程序集所依赖的其他程序集。
·在程序集的使用者和程序集的实现详细信息的使用者之间提供一定程度的间接性。
·呈现程序集自述。
程序集清单内容:
下表显示了在程序集清单中包含的信息。前四项(程序集名称、版本号、区域性和强名称信息)构成了程序集的标识。
信息 | 说明 |
程序集名称 | 指定程序集名称的文本字符串。 |
版本号 | 主版本号和次版本号,以及修订号和内部版本号。公共语言运行库使用这些编号来强制实施版本策略。 |
区域性 | 有关该程序集支持的区域性或语言的信息。此信息只应用于将一个程序集指定为包含特定区域性或特定语言信息的附属程序集。(具有区域性信息的程序集被自动假定为附属程序集。) |
强名称信息 | 如果已经为程序集提供了一个强名称,则为来自发行者的公钥。 |
程序集中所有文件的列表 | 在程序集中包含的每一文件的散列及文件名。请注意,构成程序集的所有文件所在的目录必须是包含该程序集清单的文件所在的目录。 |
类型引用信息 | 运行库用来将类型引用映射到包含其声明和实现的文件的信息。该信息用于从程序集导出的类型。 |
有关被引用程序集的信息 | 该程序集静态引用的其他程序集的列表。如果依赖的程序集具有强名称,则每一引用均包括该依赖程序集的名称、程序集元数据(版本、区域性、操作系统等)和公钥。 |
通过在代码中使用程序集属性,您可以添加或更改程序集清单中的一些信息。您可以更改版本信息和信息性属性,包括商标、版权、产品、公司和信息性版本。
5、全局程序集缓存
安装有公共语言运行库的每台计算机都具有称为全局程序集缓存的计算机范围内的代码缓存。全局程序集缓存中存储了专门指定给由计算机中若干应用程序共享的程序集。
应当仅在需要时才将程序集安装到全局程序集缓存中以进行共享。一般原则是:程序集依赖项保持专用,并在应用程序目录中定位程序集,除非明确要求共享程序集。另外,不必为了使 COM 互操作或非托管代码可以访问程序集而将程序集安装到全局程序集缓存。
说明: |
在有些情况下,您显然不希望将程序集安装到全局程序集缓存中。如果您将组成应用程序的某个程序集置于全局程序集缓存中,则将不再能够通过使用 xcopy 命令复制应用程序目录来复制或安装该应用程序。您还必须在全局程序集缓存中移动该程序集。 |
有若干方法可以将程序集部署到全局程序集缓存中:
- 使用专用于全局程序集缓存的安装程序。该方法是将程序集安装到全局程序集缓存的首选方法。
- 使用 Windows 软件开发工具包 (SDK) 提供的名为全局程序集缓存工具 (Gacutil.exe) 的开发工具。
- 使用 Windows 资源管理器将程序集拖到缓存中。
说明: |
在部署方案中,应该使用 Windows Installer 2.0 将程序集安装到全局程序集缓存中。我们一般只在开发方案中使用 Windows 资源管理器或全局程序集缓存工具,这是因为它们不提供使用 Windows Installer 时可以提供的程序集引用计数功能和其他功能。 |
管理员通常使用访问控制列表 (ACL) 来保护 systemroot 目录,以控制写入和执行访问。因为全局程序集缓存安装在 systemroot 目录的子目录中,它继承了该目录的 ACL。建议只允许具有“管理员”权限的用户从全局程序集缓存中删除文件。
在全局程序集缓存中部署的程序集必须具有强名称。将一个程序集添加到全局程序集缓存时,必须对构成该程序集的所有文件执行完整性检查。缓存执行这些完整性检查以确保程序集未被篡改(例如,当文件已更改但清单未反映此更改时)。
6、具有强名称的程序集
强名称是由程序集的标识加上公钥和数字签名组成的。其中,程序集的标识包括简单文本名称、版本号和区域性信息(如果提供的话)。强名称是使用相应的私钥,通过程序集文件(包含程序集清单的文件,并因而也包含构成该程序集的所有文件的名称和散列)生成的。Microsoft® Visual Studio® .NET 和 Windows 软件开发工具包 (SDK) 中提供的其他开发工具能够向一个程序集分配多个强名称。强名称相同的程序集应该是相同的。
通过签发具有强名称的程序集,您可以确保名称的全局唯一性。强名称还特别满足以下要求:
·强名称依赖于唯一的密钥对来确保名称的唯一性。任何人都不会生成与您生成的相同的程序集名称,因为用一个私钥生成的程序集的名称与用其他私钥生成的程序集的名称不相同。
·强名称保护程序集的版本沿袭。强名称可以确保没有人能够生成您的程序集的后续版本。用户可以确信,他们所加载的程序集的版本出自创建该版本(应用程序是用该版本生成的)的同一个发行者。
·强名称提供可靠的完整性检查。通过 .NET Framework 安全检查后,即可确信程序集的内容在生成后未被更改过。但请注意,强名称中或强名称本身并不暗含信任级别,例如由数字签名和支持证书提供的信任。
在引用具有强名称的程序集时,您应该能够从中受益,例如版本控制和命名保护。如果此具有强名称的程序集以后引用了具有简单名称的程序集(后者没有这些好处),则您将失去使用具有强名称的程序集所带来的好处,并依旧会产生 DLL 冲突。因此,具有强名称的程序集只能引用其他具有强名称的程序集。
7、程序集安全注意事项
在您生成程序集时,您可以指定该程序集运行所需的一组权限。是否将特定的权限授予程序集是基于证据的。
使用证据有两种截然不同的方式:
·将输入证据与加载程序所收集的证据合并,以创建用于策略决策的最终证据集。使用这种语义的方法包括 Assembly.Load、Assembly.LoadFrom 和 Activator.CreateInstance。
·原封不动地使用输入证据作为用于策略决策的最终证据集。使用这种语义的方法包括 Assembly.Load(byte[]) 和 AppDomain.DefineDynamicAssembly()。
通过在将运行程序集的计算机上设置安全策略,您可以授予一些可选的权限。如果您希望代码可以处理所有潜在的安全异常,可以执行以下操作之一:
·为您的代码必须具有的所有权限插入权限请求,并预先处理在未授予权限时发生的加载时错误。
·不要使用权限请求来获取您的代码可能需要的权限,但一定要准备处理在未授予权限时发生的安全异常。
说明:
安全性是一个较为复杂的领域,您将要作出很多选择。
在加载时,程序集的证据用作安全策略的输入。安全策略是由企业和计算机的管理员以及用户策略设置建立的,它在执行时确定向所有托管代码授予的权限组。可以为该程序集(如果该程序集具有签名工具生成的签名)的发行者建立安全策略,或者为该程序集的下载网站和区域(就 Internet Explorer 而言)建立安全策略,也可以为该程序集的强名称建立该策略。例如,一台计算机的管理员可以建立这样一种安全策略:它允许从某一网站下载由指定软件公司签发用以访问计算机上的数据库的所有代码,但不授予对该计算机磁盘的写访问权。
强名称程序集和签名工具
可以用两种不同但相互补充的方式对程序集进行签名:使用强名称或使用 .NET Framework 1.0 和 1.1 版中的 文件签名工具 (Signcode.exe) 或 .NET Framework 更高版本中的 签名工具 (SignTool.exe)。使用强名称对程序集进行签名将向包含程序集清单的文件添加公钥加密。强名称签名帮助验证名称的唯一性,避免名称欺骗,并在解析引用时向调用方提供某标识。
但是,任何信任级别都不会与一个强名称关联,这样 文件签名工具 (Signcode.exe) 和 签名工具 (SignTool.exe) 就变得十分重要。这两个签名工具要求发行者向第三方证书颁发机构证实其标识并获取证书。然后此证书将嵌入到您的文件中,并且管理员能够使用该证书来决定是否相信这些代码的真实性。
您可以将强名称和使用 文件签名工具 (Signcode.exe) 或 签名工具 (SignTool.exe) 创建的数字签名一起提供给程序集,或者您可以单独使用其中之一。这两个签名工具一次只能对一个文件进行签名,对于多文件程序集,您可以对包含程序集清单的文件进行签名。强名称存储在包含程序集清单的文件中,但使用 文件签名工具 (Signcode.exe) 或 签名工具 (SignTool.exe) 创建的签名存储在该程序集清单所在的可移植可执行 (PE) 文件中保留的槽中。当您已经具有依赖于 文件签名工具 (Signcode.exe) 或 签名工具 (SignTool.exe) 生成的签名的信任层次结构或者当您的策略只使用密钥部分并且不检查信任链时,就可以使用通过 文件签名工具 (Signcode.exe) 或 签名工具 (SignTool.exe) 对程序集进行的签名(带或不带强名称)。
说明:
在一个程序集上同时使用强名称和签名工具签名时,必须首先分配强名称。
公共语言运行库还将执行哈希验证;程序集清单包含构成该程序集的所有文件的列表,包括当生成清单时存在的每一文件的散列。在加载每一文件时,其内容被散列化并与清单中存储的哈希值进行比较。 如果两个哈希值不匹配,则无法加载该程序集。
因为强名称和使用 文件签名工具 (Signcode.exe) 或 签名工具 (SignTool.exe) 进行签名确保了完整性,因此您可以将代码访问安全策略建立在这两种形式的程序集证据的基础上。强名称和使用 文件签名工具 (Signcode.exe) 或 签名工具 (SignTool.exe) 进行签名通过数字签名和证书来确保完整性。上面提到的所有技术(哈希验证、强名称和使用 文件签名工具 (Signcode.exe) 或 签名工具 (SignTool.exe) 进行签名)共同作用,可以确保程序集没有做过任何方式的改动。
8、程序集版本控制
使用公共语言运行库的程序集的所有版本控制都在程序集级别上进行。一个程序集的特定版本和依赖程序集的版本在该程序集的清单中记录下来。除非被配置文件(应用程序配置文件、发行者策略文件和计算机的管理员配置文件)中的显式版本策略重写,否则运行库的默认版本策略是,应用程序只与它们生成和测试时所用的程序集版本一起运行。
说明:
仅对具有强名称的程序集进行版本控制。
运行库执行以下几步来解析程序集绑定请求:
1. 检查原程序集引用,以确定该程序集的版本是否被绑定。
2. 检查所有适用的配置文件以应用版本策略。
3. 通过原程序集引用和配置文件中指定的任何重定向来确定正确的程序集,并且确定应绑定到调用程序集的版本。
4. 检查全局程序集缓存和在配置文件中指定的基本代码,然后使用在运行库如何定位程序集中解释的探测规则检查该应用程序的目录和子目录。
下图说明了这些步骤。
解析程序集绑定请求
版本信息
- 程序集的版本号,该版本号与程序集名称及区域性信息都是程序集标识的组成部分。该号码将由运行库用来强制实施版本策略,它在运行时的类型解析进程中起着重要的作用。
- 信息性版本,这是一个字符串,表示仅为提醒的目的而包括的附加版本信息。
程序集版本号
每一程序集都有一个版本号作为其标识的一部分。因此,如果两个程序集具有不同的版本号,运行库就会将它们视作完全不同的程序集。此版本号实际表示为具有以下格式的四部分号码:
<major version>.<minor version>.<build number>.<revision>
例如,版本 1.5.1254.0 中的 1 表示主版本,5 表示次版本,1254 表示内部版本号,而 0 则表示修订号。
版本号与其他标识信息(包括程序集名称和公钥,以及与该应用程序所连接的其他程序集的关系和标识有关的信息)一起存储在程序集清单中。
在生成程序集时,开发工具将把每一个被引用程序集的依赖项信息记录在程序集清单中。运行库将这些版本号与管理员、应用程序或发行者设置的配置信息结合使用,以加载被引用程序集的正确版本。
为进行版本控制,运行库会区分常规程序集和具有强名称的程序集。只对具有强名称的程序集执行版本检查。
程序集信息性版本
信息性版本是一个字符串,它仅出于提醒的目的将附加的版本信息附加到一个程序集;此信息不在运行时使用。基于文本的信息性版本相当于产品的营销广告、包装或产品名称,不被运行库使用。例如,信息性版本可以是“公共语言运行库版本 1.0”或“NET Control SP 2”。在 Microsoft Windows 中的文件属性对话框的“版本”选项卡上,此信息显示在“产品版本”项中。
说明:
虽然可以指定任意文本,但是如果字符串的格式不是程序集版本号使用的格式,或者虽然是这种格式但包含通配符,则在编译时会显示一条警告消息。此警告无碍
信息性版本用自定义属性 System.Reflection..::.AssemblyInformationalVersionAttribute 表示。有关信息性版本属性的更多信息,请参见设置程序集属性。
9、程序集位置
对于大多数 .NET Framework 应用程序而言,您可以在以下位置找到构成该应用程序的程序集,这些位置包括:该应用程序的目录中,该应用程序目录的子目录中,或全局程序集缓存中(如果该程序集是共享的话)。可以通过在配置文件中使用 <codeBase> 元素 重写公共语言运行库查找某一程序集的位置。如果该程序集没有强名称,则使用 <codeBase> 元素 指定的位置将被限制在应用程序目录或子目录中。如果程序集具有强名称,则 <codeBase> 元素 能够指定计算机或网络上的任意位置。
当在使用非托管代码或 COM 互操作 应用程序的过程中查找程序集的位置时,类似的规则同样适用:如果该程序集将由多个应用程序共享,则此程序集应被安装到全局程序集缓存中。和非托管代码一起使用的程序集必须作为类型库导出并注册。由 COM 互操作 使用的程序集必须在目录中进行注册,尽管有些情况下会自动进行此注册。
10、程序集和并行执行
并行执行是在同一台计算机上存储和执行应用程序或组件的多个版本的能力。这意味着在同一台计算机上可以同时有运行库的多个版本,并且可以有使用其中某个运行库版本的应用程序和组件的多个版本。并行执行使您能够更多地控制应用程序绑定到的组件版本和应用程序使用的运行库版本。
支持并行存储和执行同一程序集的不同版本是强命名中不可缺少的部分,这种支持内置于运行库基础结构中。因为强名称程序集的版本号是其标识的一部分,所以运行库能够在全局程序集缓存中存储同一程序集的多个版本,并且在运行时加载这些程序集。
尽管运行库使您能够创建并行应用程序,但并行执行并不是自动进行的。