CLR基础之一---认识CLR [《CLR via C#》读书笔记]

《CLR via C#》读书笔记编程

  1. 什么是CLRapi

    CLR的基本概念数组

    通用语言运行平台(Common Language Runtime,简称CLR)是微软为他们的.Net虚拟机所选用的名称。这是通用语言架构(简称CLI)的微软实现版本,它定义了一个代码运行的环境。CLR运行一种称为“通用中间语言”的字节码,这个是微软的通用中间语言实现版本。
    
    CLR运行在微软的视窗操做系统上。查看通用语言架构能够找到该规格的实现版本列表。其中有一些版本是运行在非Windows的操做系统中。(维基百科CLR)

    以上定义至少包含如下几点信息:安全

    1. CLR是一个相似于JVM的虚拟机,为微软的.Net产品提供运行环境。
    2. CLR上实际运行的并非咱们一般所用的编程语言(例如C#、VB等),而是一种字节码形态的“中间语言”。这意味着只要能将代码编译成这种特定的“中间语言”(MSIL),任何语言的产品都能运行在CLR上。
    3. CLR一般被运行在Windows系统上,可是也有一些非Windows的版本。这意味着.Net也很容易实现“跨平台”。(至于为何你们的印象中.Net的跨平台性不如Java,更多的是微软商业战略致使的)

    CLR和.Net Framework的关系markdown

    .NET框架 (.NET Framework) 是由微软开发,一个致力于敏捷软件开发(Agile software development)、快速应用开发(Rapid application development)、平台无关性和网络透明化的软件开发平台。.NET框架是以一种采用系统虚拟机运行的编程平台,以通用语言运行库(Common Language Runtime)为基础,支持多种语言(C#、VB.NET、C++、Python等)的开发。(维基百科.Net Framework)

    因而可知,.Net Framework是一个支持多种开发语言的开发平台,而这种多语言支持的特性又要以CLR为基础。CLR是一个.Net产品的运行环境。公共语言运行时(Common Language Runtime)和 .Net Framework 类库(FCL)是.Net Framework的两个主要组成部分。网络

    CLR>=CLR+CLI+FCL。

    目前有哪些语言支持CLR架构

    微软已经为多种语言开发了基于CLR的编译器,这些语言包括:C++/CLI、C#、Visual Basic、F#、Iron Python、 Iron Ruby和IL。除此以外,其余的一些公司和大学等机构也位一些语言开发了基于CLR的编译器,例如Ada、APL、Caml、COBOL、Eiffel、Forth、Fortran、Haskell、Lexicon、LISP、LOGO、Lua、Mercury、ML、Mondrian、Oberon、Pascal、Perl、PHP、Prolog、RPG、Scheme、Smaltak、Tcl/Tk。app

    CLR为不一样的编程语言提供了统一的运行平台,在很大程度上对上层开发者屏蔽了语言之间才特性差别。对于CLR来讲,不一样语言的编译器(Compiler)就至关于一个这种语言的代码审查者(Checker),所作的工做就是检查源码语法是否正确,而后将源码编译成CLR所须要的中间语言(IL)。因此编程语言对于CLR是透明的,也就是说CLR只知道IL的存在,而不知道IL是由哪一种语言编译而来。框架

    CLR的这种“公共语言”的特性使得“多语言混合编程”成为可能,让APL开发人员可使用本身熟悉的语言和语法来开发基于.Net的项目。固然,更重要的是,这种特性容许用不一样的语言来开发同一个项目的不一样模块,好比在一个项目中用Visual Basic开发UI、用APL开发财务相关的模块,而与数学计算有关的模块使用F#,充分利用这些语言的特性,将会获得意想不到的效果。编程语言

    什么是CLS

    正如上面所说,CLR集成了这些全部的语言,使得一种编程语言可使用另外一种编程语言建立的对象。语言集成是一个宏伟的目标,然而,一个不得不面对的事实是不一样的编程语言之间存在着极大的差别,好比有些语言不支持操做符重载、有些语言不支持可选参数等等。为了保证用一种面向CLR的编程语言建立的对象可以安全的被其余面向CLR的语言来访问,最好的办法就是在用这种语言编程的时候只是用那些全部面向CLR的语言都支持的特性。为此,微软专门定义了一个“公共语言规范(Common Language Specification)”,即CLS。CLS定义了一个最小的功能集,任何面向CLR的编译器生成的类型想要兼容其余“符合CLS,面向CLR”的组件,都必须支持这个最小功能集。

    CLR/CTS 支持的功能比 CLS 定义的子集多得多。若是不关心语言之间的互操做性,能够开发一套功能非 常丰富的类型,它们仅受你选用的那种语言的功能集的限制。具体地说,在开发类型和方法的时候,若是 但愿它们对外“可见”,可以从符合 CLS 的任何一种编程语言中访问,就必须遵照由 CLS 定义的规则。注意, 假如代码只是从定义(这些代码的)程序集的内部访问,CLS 规则就不适用了。

    到目前为止,除了IL汇编语言支持CLR全部的功能特性以外,其余大多数面向CLR的语言都只提供了CLR部分的功能。也就是说这些语言的功能都是CLR功能特性的一个子集,但为了实现与其余语言的互操做性,他们的功能都是CLS的一个超集(但不必定是同一个超集)。

    想了解更多关于CLS的信息,请点击这里

    若是想要写出遵循CLS的代码,必需要保证一下几个部分的代码遵循CLS。

    • 公共类(public class)的定义
    • 公共类中公共成员的定义,以及派生类(family访问)能够访问的成员的定义。
    • 公共类中公共方法的参数和返回类型,以及派生类能够访问的方法的参数和返回类型。

    小提示:在您的私有类的定义中、在公共类上私有方法的定义中以及在局部变量中使用的功能没必要遵循 CLS 规则。 您还能够在实现您的类的代码中使用任何想要的语言功能并让它还是一个符合 CLS 的组件。

    注意:交错数组(jagged arrays)符合 CLS。 在 .NET Framework 1.0 版中,C# 编译器错误地将其报告为不符合。

    可使用 CLSCompliantAttribute 将成程序集、模块、类型和成员标记为符合CLS或者不符合CLS。 未标记为符合 CLS 的程序集将被认为是不符合 CLS 的。 若是没有 CLS 特性应用于某个类型,则认为该类型与在其中定义该类型的程序集具备相同的 CLS 听从性。 一样,若是没有 CLS 特性应用于某个成员,则认为该成员与定义该成员的类型具备相同的 CLS 听从性。 若是程序元素中包含的元素未标记为符合 CLS,则不能将此程序元素标记为符合 CLS。

    如下代码示例了CLSCompliantAttribute的使用:

    using System;
    // 告诉编译器要检查代码是否符合CLS
    [assembly: CLSCompliant(true)] 
    namespace CLS_CS
    {
        public class CsClass
        {
            // 警告: “CLS_CS.CsClass.Abc()”的返回类型不符合 CLS
            // UInt32不符合CLS。 
            public UInt32 Abc() { return 0; }
            // 警告:仅大小写不一样的标识符“CLS_CS.CsClass.abc()”
            // abc和Abc是两个的方法名称仅仅是大小写不一样。
            public void abc() { }
            //没有警告:私有方法不会促发CLSCompliant规则
            private UInt32 ABC() { return 0; }
        }
    }

    在编译这段代码的时候,因为使用了[assembly: CLSCompliant(true)]特性,编译器就会检查代码是否符合CLS,同时对于不符合CLS的部分给出警告。

  事实上,只要知足一下两个条件,即便程序集、模块或者类型的某些部分不符合CLS,这些程序集、模块或者类型也但是是符合CLS的:

  • 若是元素标记为符合CLS,,则必须使用 CLSCompliantAttribute(同时其参数应设为 false)对不符合 CLS 的部分进行标记。
  • 必须为每一个不符合CLS的成员提供相对应的符合CLS的替换成员。

  对于第一个条件,只要在上面的代码中相应的地方使用[CLSCompliant(false)],就可让编译器再也不产生这些警告,生成的仍是一个符合CLS的程序集。

using System;

// 告诉编译器要检查代码是否符合CLS
[assembly: CLSCompliant(true)] 
namespace CLS_CS
{
    public class CsClass
    {
        // 没有警告
        // UInt32不符合CLS。 
        [ CLSCompliant(false)]
        public UInt32 Abc() { return 0; }
        // 没有警告
        // abc和Abc是两个的方法名称仅仅是大小写不一样。
        [CLSCompliant(false)]
        public void abc() { }
        //没有警告:私有方法不会促发CLSCompliant规则
        private UInt32 ABC() { return 0; }
    }
}   

可是,这样一来,其余语言的模块就不能很方便的访问这些成员了。在必要状况下,须要使用第二个条件,即为不符合CLS的成员提相应的符合CLS的替换成员。

为了方便开发,有些编译器已经为咱们作好了相似的工做,即在编译阶段已把某些不符合CLS的功能编译成了符合CLS的特性。这里以操做符重载为例,前面已经说过,有些面向CLR的语言是不支持操做符重载的,所以操做符重载并非CLS的一部分。那么编译器是如何处理操做符重载的呢。 如下代码简单的示例了操做符“<”和“>”的重载:

using System;
// 告诉编译器要检查代码是否符合CLS
[assembly: CLSCompliant(true)] 
namespace CLS_CS
{
    public class Operatorcs
    {
    // 重载操做符 
        public static Boolean operator >(Operatorcs t1, Operatorcs t2)
        {
            return true;
        }

        // >和 <的重载必需要成对出现 
        public static Boolean operator <(Operatorcs t1, Operatorcs t2)
        {
            return true;
        }
    }
}

将这段代码编译成程序集,而后用IL反编译工具查看生成的IL代码。这里使用的是ILDasm.exe,固然,你也可使用一些其余的工具,例如.Net Reflector、ILSpy等。

能够看到,编译后的IL代码并无任何操做符“>”和"<"的信息,同时,生成了两个名为“opGreaterThan”和“opLessThan”的静态方法,并且这两个方法的返回值和签名都和源码中操做符重载的方法一致,由此,能够推测编译器将操做符重载“翻译”成了对应的静态成员。

而事实正是如此,虽然操做符重载不是CLS的一部分,可是全部面向CLR、符合CLS的语言都会支持类型、成员、属性、字段这些特性。编译器利用这一点用符合CLS的成员方法代替不符合CLS的操做符重载,巧妙的避开了CLS的规则校验,这也是为何这段代码虽然也使用了[assembly: CLSCompliant(true)] 但没有报出“不符合CLS”的警告的缘由。

什么是CTS

须要记住的是CLR全部功能的实现都是基于类型的。一个类型将功能提供给一个应用程序或者另外一个类型来使用。经过类型,用一种编程语言写的代码能与用另外一种语言写的代码沟通。因为类型是CLR的根本,微软专门为如何定义、使用和管理类型定义了一个正式的规范-- 通用类型系统(Common Type System),即CTS。

CTS对类型的定义和行为特性给出了规范,这些特性包括但不只限于如下几点:

  • 类成员(Field、Method、Property、Event)
  • 访问可见性级别(private、family、family and assembly 、assembly、family or assembly 、public)
  • 类型的继承
  • 接口
  • 虚方法
  • 对象的生命周期

同时,全部引用类型都必须继承自System.Object的规则也是在CTS中定义的。

通常来讲,CLS主要提供了一下功能:

    • 创建一个支持跨语言集成、类型安全和高性能代码执行的框架环境。
    • 提供一个支持完整实现多种编程语言的面向对象的模型。
    • 定义各语言必须遵照的规则,有助于确保用不一样语言编写的对象可以交互做用。
    • 提供包含应用程序开发中使用的基元数据类型(如Boolean、Byte、Char、Int32 和 UInt64)的库。
  1. CLR是如何工做的

    借用维基百科上的一副图来描述CLR的运行流程:

    从源代码到应用程序执行CLR主要作了如下工做:

    1. 将源代码编译成托管模块

      托管模块是一个标准的 32 位 Microsoft Windows 可移植执行体(PE32)文件,或者是一个标准的 64 位 Windows 可移植执行体(PE32+)文件,它们都须要 CLR 才能执行。一个托管模块主要包含一下几部分信息:

      • PE32货PE32+头

        这里描述了当前托管模块的基本信息,包括运行的Windows版本、文件类型、生成时间等。对于包含本地 CPU代码的模块,这个头包含了与本地 CPU 代码有关的信息。

      • CLR头

        包含使这个模块成为一个托管模块的信息(可由 CLR 和一些实用程序 进行解释)。头中包含了须要的 CLR 版本,一些 flag,托管模块入口方 法(Main 方法)的 MethodDef 元数据 token,以及模块的元数据、资 源、强名称、一些 flag 以及其余不过重要的数据项的位置/大小

      • 元数据

        每一个托管模块都包含元数据表。主要有两种类型的表:一种类型的表 描述源代码中定义的类型和成员,另外一种类型的表描述源代码引用的 类型和成员

      • IL (中间语言)代码

        编译器编译源代码时生成的代码。在运行时, CLR 将 IL 编译成本地 CPU 指令

    2. 将托管模块合并成程序集

      CLR 实际不和模块一块儿工做,而是和程序集一块儿工做的。能够从如下两点对程序集有一个初步的认识:

      • 首先,程序集是一个或多个模块/资源文件的逻辑性分组。
      • 其次,程序集是重用、安全性以及版本控制的最小单元。
    3. 加载CLR

      在这一步,Windows 检查好 EXE 文件头,决定是建立 32 位、 64 位仍是 WoW64 进程以后,会在进程的地址空间中 加载 MSCorEE.dll 的 x86,x64 或 IA64 版本。若是是 Windows 的 x86 版本,MSCorEE.dll 的 x86 版本在 C:\Windows\System32 目录中。若是是 Windows 的 x64 或 IA64 版本,MSCorEE.dll 的 x86 版本在 C:\Windows\SysWow64 目录中,64 位版本(x64 或者 IA64)则在 C:\Windows\System32 目录中(为了向后 兼容)。而后,进程的主线程调用 MSCorEE.dll 中定义的一个方法。这个方法初始化 CLR,加载 EXE 程序集, 而后调用其入口方法(Main)。随即,托管的应用程序将启动并运行。

    4. 执行程序集中的代码

      到目前为止,源代码已经被编译成二进制的IL而且包含在程序集中,并且被CLR加载。可是,直接执行运算的CPU来讲二进制的IL仍是过高级了,并且不一样的CPU支持的指令集也有所差别。所以,CLR在这里还须要对已经编译好的IL再次编译,针对CPU版本生成能够直接运行的CPU指令,这个过程是由JIT(Just In Time)编译器完成的,能够称做“即时编译”。

      当第一次执行某个函数时,MSCorEE.dll 的JITCompiler函数会从程序集的元数据中获取该方法和方法的IL,而且分配一块内存地址,而后将IL编译成的本地代码放入这块内存,而后执行内存中的本地代码。

      当再次执行这个函数的时候,因为内存中已经存在JIT编译好的本地代码,所以不须要再次进行JIT过程,能够直接执行内存中的本地代码。 能够预知的结果是,这种状况下应用程序启动后第一次调用某个模块所花费的时间要比之后调用这个模块要稍微多一些。

      如今,经过本地代码生成技术,已经能够在编译阶段就根据计算机的的实际环境生成本地代码,这样以来就能够在运行时节省JIT编译的时间,提升程序的启动效率。这看起来是一个不错的功能,可是实际上运用的不是很普遍,主要是有一下限制:

      • 编译时生成的本地代码太过于依赖本地环境,一旦环境有变化 (包括操做系统更新、.Net Framework版本更新、CPU更换等),之前生成的本地代码都再也不适用。
      • 编译时生成的本地代码必需要和程序集保持同步。
      • 编译时生成的本地代码不能像运行时JIT编译那样根据运行时的状况对代码进行优化。
  2. CL有哪些功能

 

  这里借用Jeffrey Richter的一段原话:

At first, I thought that the .NET Framework was an abstraction layer over the Win32 API and COM. As I invested more and more of my time into it, however, I realized that it was much bigger. In a way, it is its own operating system. It has its own memory manager, its own security system, its own file loader, its own error handling mechanism, its own application isolation boundaries (AppDomains), its own threading models, and more.

  虽然原文中Jeffrey Richter说的是.Net Framework,但很显然这些功能都是.Net Framework的核心组件CLR来提供的,正是CLR使.Net Framework并非Win32 API和COM的一个抽象层,而是有了本身的"操做系统"(Jeffrey Richter的意思应该是在必定程度上,虚拟机也能够认为是一个小型的操做系统)。总结起来,CLR主要提供了一下功能:

  1. 基类库支持 (Base Class Library Support)
  2. 内存管理 (Memory Management)
  3. 线程管理 (Thread Management)
  4. 垃圾回收 (Garbage Collection)
  5. 安全性 (Security)
  6. 类型检查 (Type Checker)
  7. 异常处理 (Exception Manager)
  8. 即便编译 (JIT)

本系列主要围绕以上功能对CLR进行探讨。

 


参考资料:

MSDN

CLR Via C# (第四版)

博客园

相关文章
相关标签/搜索