程序集重用

程序集重用

除了在源代码层面实现共享(“前.NET Core时代”如何实现跨平台代码重用 ——源文件重用)之外,我们还可以跨平台共享同一个程序集,这种独立于具体平台的“中性”程序集通过创建一种名为“可移植类库(PCL: Portable Class Library)”项目来实现。为了让读者朋友们对PCL的实现机制具有充分的认识,我们先来讨论一个被我称为“程序集动态绑定”的话题。

目录
一、何谓程序集动态绑定?
二、程序集一致性
三、程序集重定向
四、类型的转移
五、可移植类库(PCL)

一、何谓程序集动态绑定?

我们采用C#、VB.NET这样的编程语言编写的源文件经过编译会生成有IL代码和元数据构成的托管模块,一个或者多个托管模块合并生成一个程序集。除了包含必要的托管模块之外,我们还可以将其他文件作为资源内嵌到程序集中,程序集的文件构成一个“清单(Manifest)”文件来描述,这个清单文件包含在某个托管模块中。

元数据使程序集成为一个自描述性(Self-Describling)的部署单元,除了描述定义在本程序集中所有类型之外,这些元数据还包括对引用自外部程序集的所有类新的描述。包含在元数据中针对外部程序集的描述是由编译时引用的程序集决定的[1],引用程序集的名称(包含文件名、版本、语言文化和签名的公钥令牌)会直接体现在当前程序集的元数据中。

在运行时,通过元数据描述的引用程序集信息是CLR定位目标程序集的依据,但是这并不意味着它与实际加载的程序集是完全一致的,后者实际上是根据当前执行环境动态加载的,我们姑且将这个机制成为“程序集动态绑定”。

二、程序集一致性

我们都知道.NET Framework是向后兼容的,也就是说原来针对低版本.NET Framework编译生成的程序集是可以直接在高版本CLR下运行的。我们试想一下这么一个问题:就一个针对.NET Framework 2.0编译生成的程序集自身来说,所有引用的.NET Framework程序集的版本都是2.0,如果这个程序集在4.0环境下执行,CLR在决定加载它所依赖程序集的时候,应该选择2.0还是4.0呢?

我们不妨通过实验来获得这个问题的答案。我们利用Visual Studio创建一个针对.NET Framework 2.0的控制台应用(命名为App),并在作为程序入口的Main方法上编写如下一段代码。如下面代码片断所示,我们在控制台上输出了三个基本类型(Int32、XmlDocument和DataSet)所在程序集的全名。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         Console.WriteLine(typeof(int).Assembly.FullName);
   6:         Console.WriteLine(typeof(XmlDocument).Assembly.FullName);
   7:         Console.WriteLine(typeof(DataSet).Assembly.FullName);
   8:     }
   9: }

直接运行这段程序使之在默认版本的CLR(2.0)下运行会在控制台上输出如下的结果,我们会发现上述三个基本类型所在程序集的版本都是2.0.0.0。在这种情况下,运行时加载的程序集和编译时引用的程序集是一致的。

   1: mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
   2: System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
   3: System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

现在我们直接在目录“\bin\debug”直接找到以Debug模式编译生成的程序集App.exe,并为之创建一个配置文件(命名为App.exe.config)。我们编写了如下一段配置,其目的在于选择4.0版本的CLR运行这个程序。

   1: <configuration>
   2:   <startup>
   3:     <supportedRuntime version="v4.0"/>
   4:   </startup>
   5: </configuration>

或者:

   1: <configuration>
   2:   <startup>
   3:     <requiredRuntime version="v4.0"/>
   4:   </startup>
   5: </configuration>

在无需重新编译(确保运行的依然是针对.NET Framework 2.0编译生成的程序集)直接运行App.exe,我们会在控制台上得到如下所示的输出结果,可以看到三个程序集的版本编程了4.0.0.0。

   1: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
   2: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
   3: System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

这个简单的实例体现了这么一个特征:运行过程中加载的.NET Framework程序集(承载FCL的程序集)是由当前运行时(CLR)决定的,这些程序集的版本总是与CLR的版本相匹配。包含在元数据中的程序集信息提供目标程序集的名称,而版本则由当前运行的CLR来决定,我们将这个重要的机制称为“程序集一致性(Assembly Unification)”,下图很清晰地揭示了这个特性。

三、程序集重定向

在默认情况下,如果某个程序集引用了另一个具有强签名的程序集,CLR在执行的时候总是会根据程序集有效名称(Assembly Qualified Name,由程序集文件名、版本、语言文化和公钥令牌组成)去定位目标程序集,如果无法找到一个与之完全匹配的程序集,一般情况下会抛出一个FileNotFoundException类型的异常。程序集的重定向机制实际上是让CLR在定位目标程序集的时候“放宽”了匹配的条件,即指要求目标程序集的文件名与元数据描述的程序集一致即可。

如下图所示,程序集(Lib.dll)在编译的时候引用了可被重定向的程序集“Retargetable, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a”。在采用运行时Runtime1和Runtime2所在的执行环境下,真正绑定的目标程序集分别为“Retargetable, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”和“Retargetable, Version=3.0.0.0, Culture=neutral, PublicKeyToken
=30ad4fe6b2a6aeed”,除了程序集文件名称,它们的版本和公钥令牌与编译时引用的程序集均不相同。

实际上通过PCL项目编译生成的程序集所引用的都是这种能够被重定向的程序集(以下简称Retargetable程序集)。与普通程序集相比较,这种可被重定向的程序集的唯一不同之处在于它多了一个如下所示的retargetable标记。

   1: 普通程序集
   2: .assembly Lib
   3:  
   4: 可被重定向程序集
   5: .assembly retargetable Lib

这样一个标记可以通过按照如下所示的方式在程序集上应用AssemblyFlagsAttribute特性来添加。不过这样的重定向仅仅是针对.NET Framework自身的程序集有效,虽然我们也可以通过使用AssemblyFlagsAttribute特性为自定义的程序集添加这样一个retargetable标记,但是CLR并不会赋予它重定向的能力。

   1: [assembly:AssemblyFlags(AssemblyNameFlags.Retargetable)]

对于某个程序集来说,针对普通程序集的引用和Retargetable程序集的引用的不同支持会反映在自身的元数据中。下面的代码片断体现了元数据对引用程序集的描述,我们可以看到针对Retargetable程序集的引用同样具有一个retargetable标记。当CLR在定位目标程序集的时候就是根据这个标记决定是否需要重定向到当前运行时环境下与之匹配的程序集,并且这个程序集有可能在版本和公钥令牌均与元数据描述不同。

   1: 针对普通程序集的引用
   2: .assembly extern Lib
   3: {
   4:   .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         
   5:   .ver 1:0:0:0
   6: }
   7:  
   8: 针对Retargetable程序集的引用
   9: .assembly extern retargetable Lib
  10: {
  11:   .publickeytoken = (B7 7A 5C 56 19 34 E0 89)                         
  12:   .ver 1:0:0:0
  13: }

四、类型的转移

所谓类型转移(Type Forwarding)就是将定义在某个程序集中的类型转移到另一个程序集中。我们先通过一个简单的实例让读者朋友们对类型转移有一个感官上的认识。我们利用Visual Studio创建一个针对.NET Framework 3.5的控制台应用,并编写如下一端简单的程序输出两个常用的类型(Function<T>和TimeZoneInfo)所在程序集的名称。现在我们直接运行这个程序,会在控制台上得到如下所示的输出结果,可以看出.NET Framework 3.5(CLR 2.0)环境下的这两个类型定义在程序集System.Core.dll中。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         Console.WriteLine(typeof(Func<>).Assembly.FullName);
   6:         Console.WriteLine(typeof(TimeZoneInfo).Assembly.FullName);
   7:     }
   8: }

输出结果:

   1: System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
   2: System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

现在我们对该程序的配置文件(App.config)作如下的修改,其目的在于采用CLR 4.0来运行该程序。再次运行该程序集之后,我们会在控制台上得到不一样的输出结果。通过如下所示的输出结果我们可以看出当.NET Framework从3.5升级到4.0的时候,将原本定义在程序集System.Core.dll中的部分类型转移到了程序集mscorelib.dll之中。

   1: <configuration>
   2:   <startup>
   3:     <supportedRuntime version="v4.0"/>
   4:   </startup>
   5: </configuration>

输出结果:

   1: mscorelib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 
   2: mscorelib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

跨程序集之间的类型转移帮助框架或者类库的提供者解决这样的难题:某个类型在框架1.0版本的时候定义在程序集A中,当升级到2.0的时候被转移到了程序集B中,使用旧版本的应用可以在不做任何修改的情况下直接对使用的框架进行升级。类型转移需要使用到一个特殊的特性TypeForwardedToAttribute,我们现在通过一个简单的实例来演示如何利用这个特性来解决框架或者类库升级过程在类型跨程序集转移的问题。

这个演示的场景如上图所示:代表应用的App.exe在编译的时候引用了代表框架的程序集Lib.dll,具体使用的是定义其中的类型Foobar,框架进行升级之后新增了一个程序集Lib2.dll,原来定义在Lib.dll中的类型Foobar被转移到了Lib2.dll中。充分利用CLR针对类型转移的支持,我们只需要直接部署新版本的Lib.dll(不包含类型Foobar)和Lib2.dll,现有的程序能够照常运行。

我们利用Visual Studio创建了如上图所示的解决方案。类库项目Lib1代表版本1.0的框架,我们将编译生成的程序集名称设置成Lib,并在其中定义了一个类型Foobar。控制台应用直接应用Lib1,并与其中编写了如下一段简单的程序,其目的在于确认类型Foobar所在的程序集。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         Console.WriteLine(typeof(Foobar).AssemblyQualifiedName);
   6:         Console.Read();
   7:     }
   8: }

类库项目Lib2和Lib3编译生成代表框架升级之后的两个程序集,我们通过修改项目属性将目标程序集名称设置成Lib和Lib2,Lib2具有针对Lib3的项目引用。我们在Lib3中重新定义了代表被转移的类型Foobar,而Lib2实际上是一个空的项目。要体现类型Foobar从Lib.dll转移到Lib2.dll,我们需要在Lib2项目上应用如下所示的一个TypeForwardedToAttribute特性(定义在AssemblyInfo.cs中)。

   1: [assembly:TypeForwardedTo(typeof(Foobar))] 

现在我们对整个解决方案进行编译,然后定位到控制台App项目编译后的输出目录(app\bin\debug),并将项目Lib1编译生成的程序集Lib.dll删除,而将Lib2和Lib3编译生成的程序集Lib.dll和Lib2.dll拷贝到该目录下。现在我们直接运行App.exe,我们会在控制台上得到如下所示的输出结果。

   1: Lib.Foobar, Lib2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null 

如果某个项目应用了TypeForwardedToAttribute特性指向定义在另一个程序集中的被转出类型,类型转移相关的信息会体现在编译生成的元数据中。就我们的实例而言,项目Lib2编译的生成的程序集通过如下的元数据来指向被转移出去的类型所在的目标程序集。

   1: .class extern forwarder Lib.Foobar
   2: {
   3:   .assembly extern Lib2
   4: }

当App.exe被执行的时候,由于元数据体现的依然是针对程序集Lib.dll的引用,所以CLR任然会试图从该程序集中加载类型Foobar。但是通过分析程序集Lib.dll的元数据,CLR知道Foobar已经被转移到程序集Lib2.dll中,所以定义在其中的同名类型Foobar最终会被加载。

五、可移植类库(PCL)

就目前来说,创建PCL项目是实现跨.NET Framework平台程序集共享唯一的方式。当我们采用Class Library(Portal)项目模板创建一个PCL项目的时候,需要在如下图所示的对话框中选择支持的目标平台及其版本。Visual Studio会为新建的项目添加一个名为“.NET”的引用,这个引用指向一个由选定.NET Framework平台决定的程序集列表。由于这些程序集提供的API能够兼容所有选择的平台,我们在此基础编写的程序自然也具有平台兼容性。

如果查看这个特殊的.NET引用所在的地址,我们会发现它指向目录“%ProgramFiles%\Reference Assemblies\Microsoft\Framework\.NETPortable\{version}\Profile\ProfileX”。如果查看 “%ProgramFiles%
\Reference Assemblies\Microsoft\Framework\.NETPortable” 目录,我们会发现它具有如下图所示的结构。

如图上所示,目录“%ProgramFiles%\Reference Assemblies\Microsoft\Framework\.NETPortable”下具有三个代表.NET Framework版本的子目录(v4.0、v4.5和v4.6)。具体到针对某个.NET Framework版本的目录(比如v4.6),其子目录Profile下具有一系列以“Profile”+“数字”(比如Profile31、Profile32和Profile44等)命名的子目录,实际上PCL项目引用的就是存储在这些目录下的程序集。

对于两个不同平台的.NET Framework来说,它们的Core Library在API的定义上存在交集,从理论上来说,建立在这个交集基础上的程序是可以被这两个平台中共享的。如下图所示,如果我们编写的代码需要分别对Windows Desktop/Phone、Windows Phone/Store和Windows Store/Desktop平台提供支持,那么这样的代码依赖的部分仅限于两两的交集A+B、A+C和A+D。如果要求这部分代码能够运行在Windows Desktop/Phone/Store三个平台上,那么它们只能建立在三者之间的交集A上。

针对所有可能的.NET Framework平台(包括版本)的组合,微软会将体现在Core Library上的交集提取出来并定义在相应的程序集中。比如说所有的.NET Framework平台都包含一个核心的程序集mscorelib.dll,虽然定义其中的类型及其成员在各个.NET Framework平台不尽相同,但是它们之间肯定存在交集,微软针对不同的.NET Framework平台组合将这些交集提取出来并定义在一系列同名程序集中,并同样命名为mscorelib.dll。 微软按照这样的方式创建了其他针对不同.NET Framework平台组合的基础程序集,这些针对某个组合的所有程序集构成一系列的Profile,并定义在上面我们提到过的目录下。值得一提的是,所有这些针对某个Profile的程序集均为Retargetable程序集。

当我们创建一个PCL项目的时候,第一个必需的步骤是选择兼容的.NET Framework平台,Visual Studio会根据我们的选择确定一个具体的Profile,并为创建的项目添加针对该Profile的程序集引用。由于所有引用的程序集是根据我们选择的.NET Framework平台“度身定制”的,所以定义在PCL项目的代码才具有可移植的能力。

上面我们仅仅从开发的角度解释了定义在PCL项目的代码本身为什么能够确保是与目标.NET Framework平台兼容的,但是在运行的角度来看这个问题,却存在额外两个问题:

  • 元数据描述的引用程序集与真实加载的程序集不一致,比如我们创建一个兼容.NET Framework 4.5和Silverlight 5.0的PCL项目,被引用的程序集mscorellib.dll的版本为2.0.5.0,但是Silverlight 5.0运行时环境中的程序集mscorellib.dll的版本则为5.0.5.0。
  • 元数据描述的引用程序集的类型定义与运行时加载程序集类型定义不一致,比如引用程序集中的某个类型被转移到了另一个程序集中。

由于PCL项目在编译时引用的均为Retargetable程序集,所以程序集的重定向机制帮助我们解决了第一个问题。因为在CLR在加载某个Retargetable程序集的时候,如果找不到一个与引用程序集在文件名、版本、语言文化和公钥令牌完全匹配的程序集,则会只考虑文件名的一致性。至于第二个问题,自然可以通过上面我们介绍的类型转移机制来解决。



[1] 当我们执行C#编译器(csc.exe)以命令行的形式编译C#源代码时,引用的程序集通过“/reference”开关指定。

时间: 2024-10-13 10:04:31

程序集重用的相关文章

“前.NET Core时代”如何实现跨平台代码重用 ——程序集重用

除了在源代码层面实现共享(“前.NET Core时代”如何实现跨平台代码重用 ——源文件重用)之外,我们还可以跨平台共享同一个程序集,这种独立于具体平台的“中性”程序集通过创建一种名为“可移植类库(PCL: Portable Class Library)”项目来实现.为了让读者朋友们对PCL的实现机制具有充分的认识,我们先来讨论一个被我称为“程序集动态绑定”的话题. 一.何谓程序集动态绑定? 我们采用C#.VB.NET这样的编程语言编写的源文件经过编译会生成有IL代码和元数据构成的托管模块,一个

Win7下配置IIS服务器以及网站发布

本文摘至于:http://heavengate.blog.163.com/blog/static/202381053201391111512986/ 1.vsual Studio 2010下利用本地IIS进行网站发布 1)打开IIS管理工具,新增网站"WSTest",设置物理路径及端口: 2)设置好"网站名称"."物理路径"."端口".默认页面后,解决出现的问题,确保路径: http://localhost:8089能够正常访

程序集=命名空间

命名空间是用来组织和重用代码的编译单元.如同名字一样的意思,NameSpace(名字空间),之所以出来这样一个东西,是因为人类可用的单词数太少,并且不同的人写的程序不可能所有的变量都没有重名现象,对于库来说,这个问题尤其严重,如果两个人写的库文件中出现同名的变量或函数(不可避免),使用起来就有问题了,为了解决这个问题,引入了名字空间这个概念,通过使用 namespace xxx;你所使用的库函数或变量就是在该名字空间中定义的,这样一来就不会引起不必要的冲突了. 所谓namespace,是指标识符

CLR执行模式之托管代码程序集浅析

CLR即公共语言运行时,是一个可由多种编程语言使用的‘运行时’,其核心功能(内存管理,程序集加载,安全性,异常处理和线程同步等)均可由面向CLR的所有语言使用.运行时不必关心开发人员用哪一种语言写源代码,只要编译器是面向CLR的. 通过语言对应的编译器检查语法和分析源码编译生成某些托管模块(PE32或PE32+)通过数据执行保护(DEP)和地址空间布局随机化(ASLR)增强整个系统的安全性.接下来详说其主要组成部分: PE32或PE32+:标准Windows PE文件头(一种格式),使用PE32

DotNet程序集解析

在.NET Framework框架中,程序集是重用.安全性以及版本控制的最小单元.程序集的定义为:程序集是一个或多个类型定义文件及资源文件的集合.程序集主要包含:PE/COFF,CLR头,元数据,清单,CIL代码,元数据. PE/COFF文件是由工具生成的,表示文件的逻辑分组.PE文件包含"清单"数据块,清单是由元数据表构成的另一种集合,这些表描述了构成程序集的文件,由程序集中的文件实现的公开导出的类型,以及与程序集关联在一起的资源或数据文件. 在托管程序集中包含元数据和IL(微软的一

跨平台代码重用

“前.NET Core时代”如何实现跨平台代码重用 ——源文件重用 微软在2002年推出了第一个版本的 .NET Framework,这是一个主要面向Windows 桌面(Windows Forms)和服务器(ASP.NET Web Forms)的基础框架.在此之后,PC的霸主地位不断受到其他设备的挑战甚至取代,为此微软根据设备自身的需求对.NET Framework作了相应的简化和改变,不断推出了针对具体设备类型的.NET Framework,主流的包括Windows Phone.Window

C# CLR及程序集部署浅析

摘 要 .NET Framework 到底是什么?公共语言运行时和 .NET Framework 类库分别指的是什么东西?CLR. CLS. CTS.FCL等这些又是什么?为什么出现程序集的概念?它与动态链接库的区别是什么?什么是强命名程序集?如何签名及部署程序集?这一章将帮助您学习和了解其中的秘密. 第一节 .NET Framework是什么? .NET Framework(.NET框架),是由微软提出并实施的一个集成在Windows中的组件.它基于虚拟机技术实现的平台无关性的软件开发平台,它

C# 动态加载程序集信息

在设计模式的策略模式中,需要动态加载程序集信息,本文通过一个简单的实例,来讲解动态加载Dll需要的知识点. 涉及知识点: AssemblyName类,完整描述程序集的唯一标识, 用来表述一个程序集. Assembly类,在System.Reflection命名空间下,表示一个程序集,它是一个可重用.无版本冲突并且可自我描述的公共语言运行时应用程序构建基块. Module类 表述在模块上执行反射,表述一个程序集的模块信息. Type类,在System命名空间下,表示类型声明:类类型.接口类型.数组

C#编程(八十三)---------- 程序集的含义

程序集的含义 一.程序集是包含一个或多个类型定义文件和资源文件的集合.它允许我们分析可重用类型的逻辑表示和物理表示. 相当于你定义了一个项目XXProject,项目存在很多文件(类,窗体,接口,资源等等),编译生成一个DLL文件,就是程序集. 当你使用这个程序集的时候,引用改程序及就可以得到该程序集里面的所有东西(类,窗体,接口,资源). 程序集的特征如下: 1.程序集定义了可重用的类型 2.程序集标识有一个版本号 3.程序集可以包含与之相关的安全信息 二.使用程序集的原因(也就是使用多文件集的