代码改变世界

CLR via C# 边读边想 01 - .NET 程序是如何运行起来的?

2012-06-04 22:31  richardzhaoxb  阅读(356)  评论(0编辑  收藏  举报

微软的.Net在2001年作为新事物出现,给我们带来了很多新的概念、技术和术语,在日常的开发中很少关注,要深入了解.NET就必须先了解这些术语的含义:

CLR,common language runtime。是.Net支持的开发语言所共用的运行时。这个运行时提供了一些核心的功能:内存管理、程序集加载、安全性、错误处理、线程同步等。

其实CLR并不知道拿进来运行的代码是用什么语言写的,因为它只接受编译好的Managed Module。

Compiling Source Code into Managed Modules

Managed Module是一个标准的Windows可移植可执行(portable executable,简称PE)文件。32位的是 PE32, 64位的是 PE32+。 PE32可以在32位和64位上运行,而PE32+只可以在64位上运行。

 

下图就是源代码编译的过程:

从图中可以看出,你可以使用如何支持CLR的语言开发程序,只要用相应的编译器就可以编译成Managed Module。

下面列出了一个Managed Module所包含的内容: 

内容 描述
 PE Header

 这个文件的类型:GUI、CUI还是DLL

一个时间戳:标记这个文件被编译生成的时间。

 CLR Header 

这个文件依赖的CLR的版本,

这个文件的启动方法的 MethodDef 元数据信息,

文件元数据,资源文件,强命名等的地址和大小。 

 元数据(Matedata)

每一个Managed module 都包含两个Matedata table:

一个描述了这个文件中定义了哪些类和类的成员,

另外一个描述了这个文件引用了哪些类和类的成员。 

 IL Code
编译器生产的 IL 中间语言代码,在运行时,由CLR把 IL 翻译为机器码。 

 

 

 

 

 

 

 

 

 

 

 

 

 

 IL 代码也叫托管代码。 值得注意的是,Matedata 和 IL 是编译过程的共同产物,一定是编译在一个文件中,不会各自独立存在。

 

Combining Managed Modules into Assemblies

虽然我们的代码时被编译为Managed Module,但是我们实际接触的更多的是程序集(Assembly),是的,实际上CLR是加载Assembly,而不是加载Managed Module。

Assembly是一个逻辑的的分组,包含一个或多个的Manged Module或资源文件。而且Assembly是可以被复用的,安全性(Security)部署的,可被版本标示的最小单位。

从图上看出,Assembly中有一个Manifest,这其实是另外一组Matedata,描述了这个文件由哪些Managed Module组成、这个Assembly实现的对外公开的类型、以及嵌入在内的资源文件和数据文件。

在VS.NET中,编译器默认帮我们把一个Project中的所有类型都编译为一个Assembly,你也可以按照自己的想法组织Assembly的构成,比如把不常用的类编译为一个独立的Assembly,要做到这一点,你就要自己操作关于Assembly的命令行工具,比如AL.exe(Assembly Linker)。

Assembly中还包含了引用到哪些其他程序集的信息。这一点让Assembly成为了自解析的,使得.Net程序的部署比之前的 C/C++,COM+的unmanaged components程序方便的多,不需要注册了。

 

Loading the Common Language Runtime

要运行.Net 的程序,目标机器必须安装 .Net Framework,但是如何判断一台机器是否安装了.Net Framework 呢?

答:检查%SystemRoot%\System32 目录下是否有 MSCorEE.dll 文件。

如何判断一个机器上有安装.Net Framework 的具体哪个版本?

答:检查注册表 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP 中的子键。或者在.Net Framework SDK 中有一个命令行工具:CLRVer.exe也可以列出本机安装的所有CLR的版本。

 

Executing Your Assembly’s Code

通常情况下,我们用高级语言开发程序,然后用相应的编译器生成 IL ,IL 比机器语言的级别高一些。但是 IL 也可以用汇编来写,微软也提供了相关的工具:IL Assembler,ILAsm.exe;IL Disassembler,ILDasm.exe。

要真正运行一个method(), IL 必须被转化为 机器指令,这个工作是由 CRL's JIT (just-in-time) 编译器完成的。

 

如何保护你的 IL 代码,

第一个层次是使用Web Service这样的服务,客户永远不能得到你的dll

第二个层次是使用一些第三方的工具,把你的 程序集中的 IL 中的变量混乱化。

第三个层次是使用CLR’s interoperability,可以控制一些托管代码和非托管代码的交互。

 

The Native Code Generator Tool: NGen.exe

安装.Net Framework 时,NGen.exe 也会安装到你的机器上,这个工具主要用来把 IL 代码编译成本地机器码,由于使用这个工具编译后的代码不再需要CLR's JIT 在运行时编译,所以可以提升程序的性能,但同时这样做也带来一些不好的因素,比如说用NGen.exe编译的机器代码不能根据具体的CPU规格来优化程序,但是JIT编译器却可以。

所以对于NGen.exe的使用要特别的小心,其实对于服务器端程序来说,比如ASP.NET只是客户端的第一次访问比较慢一些,后续的访问的运行速度都是很快的。对于客户端的程序,用NGen.exe编译的代码可以提高启动速度,减少进程的working set。

 

The Framework Class Library (FCL)

FCL就是由一大堆 DLL 组成,这些DLL中定义了成千上万的类以及这些类提供的功能。微软也不断的提供额外的类供开发人员使用,比如说 DirectX SDK。

使用FCL中的 Assembly,我们可以开发例如以下的应用类型:

  • Web Services:使用 ASP.NET XML Web Service 或 WCF 可以很容易从Internet上访问的调用方法。
  • Web Forms Apps:ASP.NET Web Forms Application 或者 Web Site。
  • Rich Windows GUI Apps:Windows Form,WPF。
  • Rich Internet Applications(RIAs):基于Internet的 Silverlight 技术。
  • Windows Console Apps:控制台程序。
  • Windows Services:服务。
  • Database Store Procedure:SQL Server,Oracle,DB2 都允许开发人员用.Net 开发存储过程。
  • Component Library:对立的组件,被上面的Apps来引用。

 

The Common Type System(CTS)

通过上面的了解,你会发现CLR对于我们来说最重要的就是一大堆的类,这些类为我们的应用程序开发提供了便利。由于类型是CLR的根本,所以微软定义了一个规范Common Type System 来描述如何定义一个类以及如何定义它的行为。

CTS规定一个类可以包含0个或多个成员,类的成员可以有这几种:Field、Method、Property、Event。

CTS还规定了类成员可视性的规则和可视性修饰符:

  • Private:只能被同一个类的其他成员访问。
  • Family:在C#/C++中的关键字是 protected,只能被类的子类访问,不管这个子类和父类是否在一个Assembly中。
  • Family and assembly:只能被在同一个Assembly的子类访问,在C# 和 VB.NET 中都没有这个关键字。
  • Assembly:只能被在同一个Assembly的其他类型访问,在C#中使用关键字 internal。
  • Family or assembly:子类或同一个Assembly的都可访问,在C#中使用关键字 protected internal
  • Pubic:可被任何类访问。

CTS还规定了类的继承规则,虚方法规则,对象生命周期的规则,等等。虽然你不需要学习CTS的这些规则,但是他们已经体现在你使用的高级语言中了。

CTS还有一个最重要的规则,所有的类最终必须继承自System.Object。

 

The Common Language Specification(CLS)

由于每种语言都是实现了CLR中的一个集合,每种语言所包含的集合不尽相同,那要保证不用语言编译出来的程序的互通,就有了CLS的存在,它定义了一个最小的集合是每种语言都必须实现并遵守的。

如果你想保证你的程序是遵守CLS的,可以给程序集加上一个Attribute: [assembly: CLSCompliant(true)]

 1 using System;
 2 
 3 // Tell compiler to check for CLS compliance
 4 [assembly: CLSCompliant(true)]
 5 
 6 namespace SomeLibrary 
 7 {
 8     // Warnings appear because the class is public
 9     public sealed class SomeLibraryType 
10     {
11         // Warning: Return type of 'SomeLibrary.SomeLibraryType.Abc()'
12         // is not CLS-compliant
13         public UInt32 Abc() { return 0; }
14         
15         // Warning: Identifier 'SomeLibrary.SomeLibraryType.abc()'
16         // differing only in case is not CLS-compliant
17         public void abc() { }
18         
19         // No warning: this method is private
20         private UInt32 ABC() { return 0; }
21     }
22 }

编译程序会得到两个警告,一个是因为不是所有的语言都支持 Unsigned Int 类型,另外一个是因为两个方法名 Abc() 和 abc() 只有大小写的区别,在VB.Net中是无法区分的。

但是如果把 SomeLibraryType 前面的 public 关键字去掉,这个两个警告就会消失,因为这个类会成为 internal 的,其他Assembly不能访问他的内部,而和它同一个Assembly的类肯定和它是用同一中语言编写的。

 

Interoperability with Unmanaged Code

托管代码可以通过 P/Invoke(Platform Invoke) 的方式来调用非托管DLL中的功能。

在和非托管代码的互通性方面,接触的比较少,以后有需要时再来学习。