clr基本
CLR(Common Language Runtime)是一個可由多種編程語言使用的“運行時”。(例如:c#,c++/cli,vb,f#,ironpython,ironruby,il...)
CLR的核心功能內存管理、程序集加載、安全性、異常處理、線程同步、泛型、尾調用指令和基本的公共語言基礎結構 (CLI) 類型系統等。
托管模塊是一個標准的32位microsoft windows可移值執行體pe32文件(64位系統為pe32+),他們需要clr才能執行。
托管的程序集總是利用windows的數據執行保護和地址空間布局隨機化,這2個功能旨在增強整個系統的安全性。
托管模塊的組成部分:pe32(或pe32+)頭,clr頭,元數據,il中間代碼。
本地代碼編譯器生成的是面向特定cpu架構的代碼。相反每個面向clr的編譯器生成的都是il代碼。
源代碼文件----》編譯器----》托管模塊
加載CLR
.net framework sdk提供了名為clrver.exe的命令行使用程序,它能列出一台機器上安裝的所有clr版本。
c#編譯器可以指定一個平台(基於x86 windows,x64 windows或者ia64 windows).net4.0之前默認anycpu,4.0 exe項目默認x86
如果一個非托管應用程序調用loadlibrary來加載一個托管程序集,windows會自動加載並初始化clr,以便處理程序集中的代碼。
執行clr
托管程序集同時包含元數據和il。il是與cpu無關的機器語言。il比大多數cpu機器語言都要高級。il也能使用匯編語言來寫。
為了執行一個方法,首先必須把它的il轉換為本地cpu指令。這是clr的jit編譯器的職責。
首次執行托管exe---->jitcompiler---->查找方法、從元數據中獲取il、分配內存塊、編譯cpu指令、修改方法對應的記錄項、跳轉內存塊中的本地代碼
第二次執行托管exe跳過jitcompiler直接跳轉內存塊中的本地代碼
對於大多數應用程序,因jit編譯造成的性能損失並不顯著,而且clr的jit編譯器會對本地代碼進行優化,優化后的代碼會獲得更出色的性能。
c#調試產生的pdb文件就是幫助調試器查找局部變量並將il指令映射到源代碼。
release版本就是讓發布程序經過jit優化,所以線上項目最好都以release版本發布
clr提供一個在操作系統進程中執行多個托管應用程序的能力。每個托管的應用程序都在一個appdomain中執行。
IL
IL是基於棧的。
微軟提供ilasm.exe的il匯編器和一個名為ildasm.exe的il反匯編器。
IL指令是無類型的(typeless)
IL最大的優勢並不在於它對底層CPU的抽象,而是應用程序的健壯性和安全性。
FramWork
framework class library是一組dll程序集的名稱,其中包含數千個類型定義,每個類型都公開了一些功能。
通用類型系統(common type system)描述了類型的定義和行為。一個類型可以包含另個或者多個成員。
公共語言運行規范(common language specification)詳細定義了一個最小功能集。任何編譯器生成的類型要向兼容於由其他符合cls面向clr的語言所生成的組件,就必須支持這個最小功能集
響應文件
響應文件是一個文本,其中包含了一組編譯器命令行開關。執行csc.exe時,編譯器會打開響應文件,並使用其中包含的所有開關,感覺就像是這些開關直接在命令行傳遞給csc.exe。
響應文件能帶來很多方便,因為不必每次在編譯項目時,都手動指定需要的命令行參數。
c#編譯器允許同時指定多個響應文件。
程序集
程序集是一個抽象的概念,clr實際上不和模塊一起工作,而是和程序集一起。
程序集是一個或多個模塊/資源文件的邏輯分組。是重用、安全性以及版本控制的最小單元。在clr中相當於一個“組件”
托管模塊(il+元數據)+資源文件 ----》編譯器----》程序集
默認情況,編譯器會把生成的托管模塊轉換成程序集。
對於一個可重用的、可保護的、可版本控制的組件,程序集把它的邏輯表示和物理表示區分開。
在程序集的模塊中還包含與引用的程序集有關的信息(例如版本號)。這些信息使程序集可以自描述。換句話說,clr能判斷出為了執行程序集中的代 碼,程序集中得依賴對象是什么。不需要在注冊表或active directory domain services中保存額外的信息。
程序集的所有文件中,有一個文件容納了清單。清單也是一組元數據表的集合,表中主要包含了作為程序集的組成部分的文件的名稱。
可用單獨的文件對類型進行划分、可在自己的程序集中添加資源或者數據文件、程序集包含的各個類型可以用不同的編程語言來實現。
元數據
元數據是一組數據表。其中的一些數據表描述可模塊中定義的內容,比如類型及成員。還有一些元數據描述了托管模塊引用的內容,比如導入的類型及成員。
元數據是一些老技術的超集,這些老技術包括com的“類型庫”和“接口定義語言”文件。
元數據總是嵌入和代碼相同exe/dll文件中。
元數據的用途:編譯時元數據消除對本地c/c++頭和庫文件的要求,vs使用元數據幫助你智能感知代碼,類型安全驗證,序列化,gc管理。
元數據是一個二進制數據塊,由幾個表構成。這些表分為3個類別:定義表、引用表和清單表。
定義表:moduledef,typedef,methoddef,fielddef,paramdef,propertydef,eventdef
引用表:assemblyref,mouduleref,typeref,memberref
清單表:assemblydef,filedef,manifestresourcedef,exportedtypesdef
程序集還講語言文化作為其身份標識的一部分。沒有指定具體語言文化的程序集稱為語言文化中性。
部署
程序集的打包沒有任何特殊需求。不需要對注冊表進行任何修改,clr就可以在應用程序的目錄中查找引用的程序集。
也可以使用其他機制來打包和安裝程序集文件,比如使用.cab文件。還可以將程序集文件打包成一個msi。
msi文件可實現程序集的“按需安裝”,在clr首次嘗試加載程序集的時候才安裝這個程序集。
部署到和應用程序相同的目錄中得程序集稱為私有部署的程序集,這是因為程序集文件不和其他任何應用程序共享(除非其他應用程序也部署到這個目錄中)。
clr嘗試定位一個程序集文件時,總是先在應用程序基目錄中查找。如果沒有找到,就會掃描幾個子目錄。
對於可執行應用程序(exe),配置文件必須在應用程序的基目錄中,而且必須采用exe文件的全名作為文件名,再加一個.config擴展名
對於microsoft asp.net web窗體應用程序,文件必須在web應用程序的虛擬根目錄中,而且總是命名為web.config
共享程序集與強命名程序集
弱命名程序集與強命名程序集都是c#編譯器或者al.exe產生。他們的區別在於強命名程序集使用發布者的公鑰/私鑰對進行了簽名,它唯一性地標識了程序集的發布者。
一個程序集可以采取兩種方式來部署:私有或全局。弱命名程序集只可以私有部署。
可以使用sn.exe來生成公鑰/私鑰對。
全局程序集緩存
如果一個程序集要由多個應用程序訪問,必須把它放到一個已知的目錄中,而且clr在檢測到對該程序集的一個引用時,必須知道自動檢查該目錄。這個已知的位置成為全局程序集緩存(GAC)。
GAC通常位於c:\windows\assembly目錄下(假定windows安裝到c:\windows目錄)
可以使用GACUtil.exe在GAC中安裝一個強命名程序集
GAC目錄是結構化的:其中包含許多子目錄,並用一個算法來生成這些子目錄的名稱。
CLR解析引用類型
CLR先加載並初始化程序集,然后讀取clr頭,查找標識應用程序的入口方法的methoddeftoken。然后clr會檢索 methoddef元數據表,找到該方法的il代碼在文件中的偏移量,把這些il代碼jit編譯成本地代碼。編譯時會對代碼進行驗證以確保其類型安全性。 最后執行本地代碼。
clr可以在三個地方找到類型:同一個文件(早起綁定)、不同的文件但同一個程序集(當前程序集清單目錄)、不同的文件不同的程序集(其他程序集清單目錄)。
對於clr來說,所有程序集都是根據名稱、版本、語言文化和公鑰來標識的。
GAC根據名稱、版本、語言文化和cpu架構來標識程序集。
類型基礎
所有類型都是從system.object派生
system.object提供4個公共實例方法equals,gethashcode,tostring,gettype。
system.object提供2個受保護方法memberwiseclone,finalize.
clr要求所有對象都用new操作符來創建。
new操作符所作的事情:1.它計算類型及其所有基類型中定義的所有實例字段所需的字節數。堆上的每個對象都需要一些額外的成員--“類型對象 指針”和“同步塊索引”。這些成員由clr用於管理對象。這些額外成員的字節數會計入對象大小。2.它從托管堆中分配指定類型要求的字節數,從而分配對象 的內存,分配的所有字節都設為零。3.它初始化對象的--“類型對象指針”和“同步塊索引”成員。4.調用類型的實例構造器,向其傳入在對new的應用中 指定的任何實參。
clr最重要的特性之一就是類型安全性。在運行時,clr總要知道一個對象是什么類型。調用gettype方法,總是知道一個對象確切的類型是什么。
clr允許將一個對象轉換成為它的實際類型或者它的任何基類型。
使用c#的is和as操作符來轉型。
命名空間用於對相關的類型進行邏輯性分組,開發人員使用命名空間來方便地定位一個類型。
命名空間和程序集不一定是相關的。特別是同一個命名空間中的各個類型可能是在不同的程序集中實現的。
一個線程創建時,會分配一個1mb大小的棧。這個棧的空間用於向方法傳遞實參,並用於方法內部定義的局部變量。棧是從高位內存地址向地位內存地址構建的。
windows進程啟動后,clr加載到其中,托管堆初始化,創建一個線程。當jit將方法的il代碼轉化成本地cpu指令時,會注意到方法內 部引用的所有類型。在這個時候clr要確保定義了這些類型的所有程序集已加載。然后利用程序集的元數據,clr提取與這些類型有關的信息,並創建一些數據 結構來標識類型本身。
堆上的所有對象都包含兩個額外的成員類型對象指針和同步塊索引。定義一個類型時,可以在類型的內部定義靜態數據字段。為這些靜態數據字段提供支 援的字節是在類型對象自身中分配的。在每個類型對象中,最后都包含一個方法表。在方法表中,類型定義的每個方法都有一個對應的記錄項。
當clr確定方法需要的所有類型對象都已創建,而且方法的代碼已經編譯之后,就允許線程開始執行方法的本地代碼。方法的“序幕”代碼執行時必須在線程棧中為局部變量分配內存。
jit編譯器在類型對象的方法表中查找引用了被調用方法的記錄項,對方法進行jit編譯,再調用jit編譯的代碼。
system.object的gettype方法返回的是存儲在制定對象的“類型對象指針”成員中的地址。換言之,gettype方法返回的是指向對象類型的一個指針。這樣一來就可以判斷出系統中任何對象的真實類型。