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# (第四版)

博客園

相關文章
相關標籤/搜索