C#基础知识梳理系列一:CLR及程序集部署
.NET Framework 到底是什么?公共语言运行时和 .NET Framework 类库分别指的是什么东西?CLR、 CLS、 CTS、FCL等这些又是什么?为什么出现程序集的概念?它与动态链接库的区别是什么?什么是强命名程序集?如何签名及部署程序集?这一章将帮助您学习和了解其中的秘密。
.NET Framework(.NET框架),是由微软提出并实施的一个集成在Windows中的组件。它基于虚拟机技术实现的平台无关性的软件开发平台,它以语言运行库(CLR)为平台支持多种语言开发,如C#、VB、托管C++等,以强制的类型安全为基础实施运行在托管环境中来达到代码安全和隔离运行、提供对内存的自动化管理、线程的优化切换、提供一个统一的面向对象编程模型。
.NET Framework环境
版本发布历史:
版本 | 完整版本号 | 发行日期 | Visual Studio |
---|---|---|---|
1.0 | 1.0.3705.0 | 2002-02-13 | Visual Studio .NET 2002 |
1.1 | 1.1.4322.573 | 2003-04-24 | Visual Studio .NET 2003 |
2.0 | 2.0.50727.42 | 2005-11-07 | Visual Studio 2005 |
3.0 | 3.0.4506.30 | 2006-11-06 | |
3.5 | 3.5.21022.8 | 2007-11-19 | Visual Studio 2008 |
4.0 | 4.0.30319.1 | 2010-04-12 | Visual Studio 2010 |
4.5 | 4.5.40805 | 2012-02-20 | Visual Studio 2012 RC |
.NET Framework 包含两大核心组件:公共语言运行时和.NET Framework类库。
公共语言运行时 英文名Common Language Runtime,简称 CLR。它是一个相对开放的运行平台,可供多种语言(C#、VB等)使用的运行时。CLR拥有一组核心功能,包括:内存管理、线程执行、程序集加载、异常处理、强制类型安全等,这些功能对所有面向CLR的语言都支持。在程序运行的时候,CLR对编码语言是未知的,无论任何语言,只要它的编译器是面向CLR的就可以。像微软自己的C#编译器、VB编译器、F#编译器等。当然你也可以实现自己的编译器,但你的编译器必须是面向CLR的。编译器将对源代码进行语法验证和分析,最终生成托管模块(Managed Module),进而一个传说中的程序集诞生了。
托管模块 是一个标准Microsoft Windows可移植执行体,它可能是32位(PE32)文件,也可能是64位(PE32+)文件。托管模块有如下几部分组成:
PE32/PE32+ 头 标准的 Windows PE文件头。如果文件头使用PE32格式,则此文件只能在Windows 的32位或64位版本上运行;如果文件头使用PE32+格式,则此文件只能在Windows 的64位版本上运行。另外,文件头还可能包含与本地CPU代码相关的信息。编译器在编译时,可通过编译平台/platform开关来指定该程序集包含一个PE32头或PE32+头。在Visual Studio中可以对目标平台进行选择,如图:
CLR头 包含了标志此模块为一个托管模块的基本信息,如CLR版本、托管模块入口方法、模块的元数据、资源等。
元数据 元数据是一组数据表,它是一个二进制块。包含三类数据表:一个表描述了与此模块对应的源代码中定义的类型、成员,这是定义表;另一个表描述了此代码所引用的其他类型(成员)列表,这是引用表,还有一类是清单表。常见的元数据定义表有ModuleDef、TypeDef、MethodDef、FieldDef、ParamDef、PropertyDef、EventDef。常见的引用表有AssemblyRef、ModuleRef、TypeRef、MemberRef。
IL代码 也是中间语言。编译器编译源代码时生成的中间代码,在执行环境中,这些IL代码将被CLR的JIT编辑器翻译成CPU能识别的指令,供CPU执行。
在执行时,CLR是通过程序集与托管模块进行沟通的。程序集是一个或多个托管模块和资源文件的逻辑分组,(它就相当于一个省份组织,它划分了一部分人和地域资源)。程序集是由编译器生成,最终生成的可能是EXE或DLL文件。在程序集内有一个清单,其描述了程序集内的文件列表,如托管模块、jpeg文件、XML文件等。CLR能够通过程序集内模块中的自描述信息来确定要执行此程序集中代码时所依赖的其他对象。
动态链接库(Dynamic-Link Libraries,简称DLLs),就是一个可执行模块,其扩展名为.dll,它是基于C++,Delphi等语言生成的。模块中包含了可以被其他应用程序或其他dll使用的例程和资源。dll没有通常的主程序,但它有多个执行入口。DLLs的特点在于它的代码是在运行期动态地链接到调用它的程序中的。
.NET Framework中的 程序集的扩展名dll与动态链接库中的dll无非是同名罢了,是完全不相同的两个概念。
下面以一个控制台程序来模拟CLR加载及程序运行过程。示例代码:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 System.Diagnostics.Debug.WriteLine("hello world"); 6 System.Diagnostics.Debug.WriteLine("hello c#"); 7 } 8 }
(1)在Windows中要运行一个EXE文件,Windows会首先检查这个EXE文件的头,以确定该文件是32位、64位还是WoW64(Windows on Windows64技术,运行32位程序运行在64位版本的Windows环境中),进而创建相应的进程,然后在进程的地址空间中加载MSCorEE.dll的相应x86、x64、IA64版,紧接着进程的主线程调用MSCorEE.dll中的一个CorBindToRuntimeEx函数,这个函数的主要功能就是加载CLR。CorBindToRuntimeEx函数定义:
HRESULT CorBindToRuntimeEx (
[in] LPWSTR pwszVersion,
[in] LPWSTR pwszBuildFlavor,
[in] DWORD startupFlags,
[in] REFCLSID rclsid,
[in] REFIID riid,
[out] LPVOID* ppv
);
pwszVersion 描述要加载的 CLR 的版本。
pwszBuildFlavor 字符串,指定是加载 CLR 的服务器版本还是工作站版本。值:svr 和 wks。
startupFlags。STARTUP_FLAGS 枚举值的组合。这些标志控制并发垃圾回收、非特定于域的代码以及 pwszVersion 参数的行为。如果未设置标志,则默认值为一个域。
rclsid 实现ICorRuntimeHost 接口的 coclass 的 CLSID。支持的值为 CLSID_CorRuntimeHost或 CLSID_CLRRuntimeHost。
Riid 请求自 rclsid 接口的 IID。支持的值为 IID_ICorRuntimeHost 或 IID_ICLRRuntimeHost。
Ppv 返回的指向 riid 的接口指针。
(2)CLR加载完成后,在执行Main方法前CLR会检查Main 方法所引用的所有类型且在内部初始化一个数据结构来记录被引用类型的内的每个方法的入口。由于程序集内包含了元数据和IL,接下来CLR的主要工作就是要先找到WriteLine方法的IL,用JIT编译器对其进行验证(Verification),把IL翻译成本地CPU指令,指令被保存在动态内存中。接着进行压栈,执行第一个WriteLine方法。如果下次要再次执行WriteLine方法,由于第一次已经对该方法进行验证及编译(CPU指令),所以这次直接执行CPU指令即可。所以一个类型或方法,只有在第一次执行时有性能损失,以后对该类型的调用是直接运行CPU指令,性能飞速提高。
Framework 类库 英文名 Framework Class Library 简称 FCL。是一组与CLR紧密集成的可重用的面向对象的类型(程序集)集合。它包含有成千上万个能使我们的托管代码从中导出的类型。它不仅提供了一些常见的编程功能,如字符串处理,数据库连接等,还提供了一些高级开发,如线程同步,反射,垃圾回收等功能;它不仅公开了一些UI库,如Windows from 、Web窗体、Silverlight等,还公开了一些面向服务的编程,如WebService,、Windows 服务、WCF等。
由于类库提供了太多的类型,所以它们以命名空间为组织划分到不同的dll中。例如:
System命名空间 包含了每个应用程序都要用到的所有基本类型。
System.IO命名空间 包含了执行流I/O 以及目录浏览的类型等。
基类库 英文名 Base Class Library 简称BCL。它封装了大量的基础功能,如文件操作、图形操作、网络连接、XML文档解析、安全加密,等等。
由于在执行时CLR对它支持的语言未知,(当然,这些语言必须是面向CLR)即CLR不管要执行的代码是 C# 编写还是VB编写。CLR围绕类型展开,只要其编码语言是面向CLR的,它们就可以相互交流,要想达到相互认识和理解,必须制定类型的定义和行为及状态。这个对类型的定义和行为的描述就是通用类型系统。英文名Common Type System 简称CTS。
CTS规定一个类型可以包含零个或多个成员,这些成员包括:字段,属性,方法,事件等,另外还可以对成员的访问规则进行控制,如public ,private,internal等。
公共语言规范 英文名Common Lahguage Specification 简称 CLS。
CLR集成支持了所有面向CLR的语言,允许在一种语言中使用另一种语言创建的对象,但由于各种语言本身的特性,比如有些语言不支持无符号整数,有些不区分大小写。如果要想做到自己公开的类型能被其他语言所识别,则就要尽可能按照其他语言所支持的特性且自己也支持的特性去构造。CLS详细定义了一个各种面向CLR共有的最小功能集。如图:
通用语言基础架构(Common Language Infrastructure,简称CLI)是一个开放的技术规范。维基百科解释:定义了构成.NET Framework基础结构的可执行码以及代码的运行时环境的规范,它定义了一个语言无关的跨体系结构的运行环境,这使得开发者可以用规范内定义的各种高级语言来开发软件,并且无需修正即可将软件运行在不同的计算机体系结构上。
CLI有时候会和CLR混用。但严格意义上说,这是错误的。因为CLI是一种规范,而CLR则是对这种规范的一个实现。
在经过编译器生成程序集后,如果只是一个应用程序使用该程序集,则直接将该程序集复制到目标目录即可,这就是私有程序集的部署。如果一个程序集有多个程序使用,则需要把这个共用的程序集放到一个公共的目录。像全局应用程序域里的程序集就是公用的。但是,如果有多个部门或公司开发的具有相同名称的程序集放到同一个目录,这就可能不太现实,因为后来部署上去的DLL会覆盖先前一次部署的DLL,很显然以文件名来区分程序集是不可取的。CLR必须提供对程序集进行唯一性验证办法。这就是“强命名程序集”。一个强命名程序集用四个属性共同对一个程序集进行标识,这四个属性是:文件名(不包括扩展名)、版本号、语言文化特性和一个公钥(使用公钥派生出的一个小的Hash 值,公钥标记)。如下一个程序集文件:
“commonTypes,Version=2.0.2.1029,Culture=neutral,PublicKeyToken=c55b39c999a8888d”
强命名程序集可以防被篡改,也可以将强命名程序集部署到全局程序集缓存。
(1)使用SN生成密钥,命令行导航到VS安装目录,使用SN命令即可生成一个密钥,如下:
(2)将已经生成的MyApp.snk复制到项目工程下,如下图,当然也可以放到其他目录:
(3) 设置项目的属性签名
(4) 重新生成项目即可得到具有强命名的程序集。
(5) 如果一个程序集是由多个应用程序使用,必须把它部署到一个让CLR能检测到的已知目录,那这个目录就是全局程序集缓存GAC。.NET4.0的GAC位于:
C:\Windows\Microsoft.NET\Assembly
必须使用GACUtil.exe工具来完成程序集的部署工作。该命令有两个重要的开关:
/i 将某个程序集安装到全局程序集缓存中
/u 将某个程序集从全局程序集缓存中卸载
安装示例:
gacutil /i 强命名程序所在的绝对路径
例如:D:\>gacutil /i C:\MyApp.dll
除了将强命名程序集部署到GAC以外,还可以以私有方式部署程序集,例如将其部署到网络上,在config中配置当程序运行时再加载该程序集。如:
<codeBase version="2.0.0.0" href="http://www.roojune.com/MyApp.dll"/>