了解了.NEt的結構後,我們該看看.NET利用其結構為我們創造的運行環境--公用語言運行時環境(CLR)。它是C#及其它支持.NET平台的開發工具的運行基礎。具體來說,它為我們的應用提供了以下益處:
●跨語言集成的能力。
●跨語言異常處理。
●內存管理自動化。
●強化的安全措施。
●版本處理技術。
●組件交互的簡化模型。
2.2.1、理解CLR
.NEt提供了一個運行時環境,叫做公用語言運行時,它管理著代碼的執行,並使得開發過程變得更加簡單。這是一種可操控的執行環境,其功能通過編譯器與其它工具共同展現,你的代碼將受益於這一環境。依靠一種以運行時為目標的(指完全支持運行時環境的)編譯器所開發的代碼叫做可操控代碼。它得益於可操控環境的各種特性:跨語言集成、跨語言異常處理、增強的安全性、版本處理與開發支持、簡單的組件交互模型以及調試服務。為了使運行時環境能夠向可操控代碼提供服務,語言編譯器需要產生一種元數據,它將提供在你使用語言中的類型、成員、引用的信息。元數據與代碼一起存儲,每個可加載的CLR映像均包含了元數據。運行時環境使用元數據定位並載入類,在內存中展開對象實例,解決方法調用,產生本地代碼,強制執行安全性,並建立運行時環境的邊界。
運行時環境自動處理對象的展開與引用,當它們不再使用時負責它們的釋放。被運行時環境進行這樣的生命周期管理的對象被稱為可操控代碼。自動內存管理消除了內存溢出,同時也解決了其它一些常見的語法錯誤。如果你的代碼是可操控的,你仍然可以在需要的時候使用非可控代碼,或者在你的.NET應用中同時使用可控與非可控代碼。由於語言編譯器支持他們自己的類型,比如一些原始類型,你可能並不總是知道(也不必知道)你的數據是否是可控的。
CLR使設計跨語言的組件與應用變得更加容易。以不同語言設計的對象能夠彼此間進行通信,並且它們的行為能夠緊密地綜合與協調。舉個例子,你定義了一個類,然後可以在另一種不同的語言中從該類中派生了一個類或者調用它其中的一個方法。你也可以向另一種語言中類的方法傳遞該類的一個實例。這種跨語言的集成之所以可能,因為以運行時間為目標的語言編譯器與工具使用一種運行時間所定義的公用類型系統,他們遵守運行時的規則(公用語言規范)來定義新的類型,生成、使用、保持並綁定類型。
作為元數據的一部分,所有可控組件攜帶了關於它們所依賴的組件與資源的信息。運行時環境使用這些信息來保證你的組件或應用具有需要的所有東西的特定版本,其結果是你的代碼將不會因為版本沖突而崩潰。注冊信息與狀態數據不再保存在難以建立與維護的注冊表中,你所定義的類型及附屬信息作為元數據被保存,這使得復制與移動組件的復雜程度得到降低。
編譯工具用他們自己的方式向開發人員展現CLR的功能。這意味著運行時間的一些特性可能在不同的語言中表現形式將會有所不同。你怎樣體驗運行時的特性將取決於你所使用的語言,比如說,如果你是一位VB開發人員,你可能注意到在運行時環境的幫助下,VB語言比以前具有更多的面向對象的特性。
2.2.2、可操控執行的含義
前面的敘述中,我們多次提到了“可操控”這一概念。這意味著它指向的對象在執行過程中完全被運行時環境所控制。在執行過程中,運行時環境提供以下的服務:自動內存管理、調試支持、增強的安全性及與非可操控代碼的互操作性,例如COM組件。
在可控執行進程中的第一步是選擇源代碼的生成工具。如果你希望你的應用擁有CLR提供的優勢,你必須使用一種(或多種)以運行時為目標的語言編輯器,例如:VB、C#、VC的編譯器,或者一種第三方編譯器如PERL或COBOL編譯器。
由於運行時是一種多語言執行環境,它支持眾多的數據類型和語言特性。你使用的語言編譯器決定你將使用運行時的哪一部分功能子集。在代碼中使用的語法由你的編譯器決定,而不是運行時環境。如果你的組件需要被其他語言的組件完全使用,那麼你必須在你組件的輸出類型中使用CLR所要求的語言特征。
當你完成並編譯你的代碼時,編譯器將它轉換為微軟中間語言(Microsoft Intermediate Language,MSIL),同時產生元數據。當你要執行你的代碼時,這種中間語言被即時(Just In Time,JIT)編譯器編譯成本地代碼。如果安全策略需要的代碼是類型安全的---通常情況下都是如此---JIT編譯器將在編譯進程中對中間語言進行類型檢查。一旦失敗,在代碼執行中將會觸發異常。
2.2.3、CLR的突出特色
跨語言集成的能力
CLR包含了一個豐富的語言特性集,保證了它與各種程序設計語言的兼容性。這一特性集即公用語言規范,稍後將對其進行詳細說明。
內存管理自動化
在執行過程中管理應用程序的資源是一項單調而困難的工作。它會將你的注意力從你本應解決的問題中引開。而垃圾收集機制完全解決了程序員在編程過程中頭痛的問題,跟蹤內存的使用,並知道何時將它們釋放。
在面向對象的環境中,每種類型都標識了對你的應用程序有用的某種資源。為了使用這些資源,你需要為類型分配內存。在應用中,訪問一種資源要通過以下步驟:
(1)為類型分配內存。
(2)初始化內存,設置資源的初始狀態並使其可用。
(3)通過訪問該類型的實例成員來訪問資源。
(4)卸下將被清除的資源狀態。
(5)釋放內存。
這一看似簡單的過程在實際的編程中是產生錯誤的主要來源之一。更可怕的是:內存中的錯誤往往導致不可預見的結果。如果你有過編程的經驗,想想看,有多少次你的程序因為內存訪問錯誤而崩潰?
CLR要求所有的資源從可操控的推(注:在此指一種內存結構)中分配。當一個進程被初始化後,CLR保留了一個未被分配的地址空間。這一區域叫做可操控堆。在堆中保持了指向下一個將被分配給對象的堆地址的指針(NEXT)。初始狀態下,該指針是保留地址空間的基地址。一個應用使用新的操作產生對象。此操作首先檢查新對象需要字節的大小是否會超出保留空間。如果對象大小合適,指向下一個地址的指針將指向堆中的這個對象,該對象的構造器被調用,新的操作返回對象的地址。
當一個應用請求建立一個對象時,地址空間可能不夠大。堆將發現這一點(通過將新對象的大小與NEXT指針相加,並與堆的大小進行比較),這時垃圾收集器就將被調用。在這裡,CLR引入了“代”的概念。代,指堆中對象產生的先後。這樣,垃圾收集器在將發生溢出時回收屬於特定的“代”的對象,而不是回收堆中的所有對象。
(6)、即時編譯
在各種語言的編譯器對源代碼進行編譯之後,在CLR環境中產生的是中間代碼(出於兼容性與跨語言集成的考慮),其內容雖然有效,但在轉化為本地代碼之前它本身是不可執行的。這就是JIT編譯器需要完成的工作。
這裡需要說明一個問題:為什麼要即時編譯,而不是一次性的將中間代碼文件進行編譯?答案很簡單:原因在於效率。在大型的應用中,你很少會用到程序的全部功能,這種邊執行邊編譯的措施比一次性的完全編譯效率更高。
在Windows平台中,CLR帶有三個不同的JIT編譯器:
(7)、缺省的編譯器---主編譯器,由它進行數據流分析並輸出經過優化的本地代碼,所有的中間代碼指令均可被它處理。
(8)、PREJIT,它建立在主JIT編譯器之上。其運行方式更象一個傳統的編譯器:每當一個.NET組件被安裝時它就運行。
(9)、ECONOJIT,在並不充分優化的前提下,它能夠快速完成IL代碼到本地碼的轉換,編譯速度與運行速度都非常快。
為了配合編譯器的工作,在.NET SDK的安裝路徑下的/bin目錄中有一個負責管理JIT的應用程序:jitman.exe。具體的使用參見聯機幫助。
(10)、解決版本與發布問題。
在當前以組件為基礎的系統中,開發人員和用戶對於軟件版本和發布中存在的問題已經十分熟悉了。當我們安裝一個新的應用之後,我們很可能發現原本正常的某個應用程序奇怪的停止了工作。絕大多數開發人員將時間花在了確保所有注冊表入口的一致性,以便激活COM類上。這就是所謂的“DLL地獄”。
.NET平台通過使用集合來解決這一問題。在這裡,“集合”是一個專有名詞,指類型與資源的發布單元,在很大程度上它等同於今天的DLL。正象.NET用元數據描述類型一樣,它也用元數據描述包含類型的集合。通常說來,集合由四個部分組成:集合的元數據(集合的內部清單)、元數據描述的類型、實現類型的中間語言代碼和一組資源。在一個集合中,以上四個部分並不是都必須存在,但是,集合中必須包含類型或資源,這樣集合才有意義。
在.NET中一個基本的設計方針是使用孤立的組件。一個孤立的集合的含義是指一個集合只能被一個應用所訪問。在一台機器上,它不被多個應用共享,也不會受其它應用程序對系統的更改的影響。“孤立”賦予了開發人員在自己的程序中對代碼的完全控制權。任何共享代碼都需要被明確地標識。同時,.NET框架也支持共享集合的概念。一個共享集合指在一台機器上被多個應用共享的集合。共享集合需要嚴格地命名規定。有了.NET,應用程序間的共享代碼是明確定義的。共享集合需要一些額外的規則來避免我們今天遇到的共享沖突問題。共享代碼必須有一個全局唯一的名稱,系統必須提供名稱保護,並在每當引用共享集合時,CLR將對版本信息進行檢查,此外.NET框架允許應用或管理員在明確說明的版本政策下重寫集合的版本信息。