《.net編程先鋒C#》第二章 理論基礎(chǔ)-公用語言 運行環(huán)境
2024-07-10 13:00:25
供稿:網(wǎng)友
第二章 理論基礎(chǔ)-公用語言 運行環(huán)境
既然你已經(jīng)具有了c#全面的印象,我也想讓你了解ngws runtime的全貌。c#依靠由ngws提供的運行時;因此,有必要知道運行時如何工作,以及它背后所蘊含的概念。
所以,這一章分為兩部分——它們是所有的概念和使用的基礎(chǔ)。兩部分的內(nèi)容雖然有些重疊,但它有助于加深理解正在學(xué)習(xí)的概念。
2.1 ngws runtime
ngws和ngws runtime為你提供了一種運行時環(huán)境。該運行時管理執(zhí)行代碼,并提供了使編程更容易的服務(wù)。只要你的編譯器支持這種運行時,你就會從這種受管理的執(zhí)行環(huán)境中得益。
你猜測c#編譯器支持ngws runtime很正確,但是不僅它支持ngws runtime,vb和c++也支持。這些為支持運行時所創(chuàng)建的代碼稱作"受管代碼"(managed code)。以下是你的應(yīng)用程序從ngws runtime那里所得到的利益:
交叉語言集成(通過通用語言規(guī)范)
自動內(nèi)存管理(垃圾收集)
交叉語言異常處理(統(tǒng)一展開)
增強(qiáng)安全(包括類型安全)
版本支持("dll地獄"終結(jié)者)
組件交互簡化模式
因ngws runtime 要提供了所有的這些好處,編譯器必須把元文件和受管代碼一起發(fā)出。元文件描述代碼中的類型,它和你的代碼存在一起(與pe類似---pe為可變位執(zhí)行文件)
正如你從很多種交叉語言功能所看到的,ngws runtime主要是關(guān)于高度集成交叉多異編程語言(tight integration across multiple different programming languages)。這種支持可達(dá)到允許你從一個vb對象派生出一個c#類的程度(我后面會給出要討論的文章)。
c#程序員將會喜歡的一個功能是,他們不必?fù)?dān)心內(nèi)存管理—也就是說不必?fù)?dān)心臭名昭著的內(nèi)存泄漏。ngws runtime提供了內(nèi)存管理,當(dāng)對象和變量的生命期結(jié)束(不再被引用)時,垃圾收集器釋放它們。我真的喜歡這個功能,因為在com中的內(nèi)存管理一直是我的一塊心病。
應(yīng)該鼓勵配置一個管理應(yīng)用程序或者組件。因為管理應(yīng)用程序含有元數(shù)據(jù)文件,ngws runtime可以利用這些信息,以確保你的應(yīng)用程序具有它所需的各種規(guī)定版本。所產(chǎn)生的明顯效果為,由于你的代碼沒有相互之間的依賴,很少可能出現(xiàn)中斷。
這章余下來的將分為兩部分,每一部分討論ngws runtime的各個方面,直到你的c#應(yīng)用程序能執(zhí)行為止。
1、中間語言(intermediate language,縮寫il)和元數(shù)據(jù)
2、即時編譯器(just-in-time compliers,簡稱jiters)
2.1.1 中間語言和元數(shù)據(jù)
由c#編譯器生成的受管代碼并不是原始代碼,但它是中間語言(il)代碼。這種il代碼自身變成了ngws runtime的受管執(zhí)行進(jìn)程的入口。il代碼明顯的優(yōu)勢在于它是cpu無關(guān)的,這也意味著,你要用目標(biāo)機(jī)器上的一個編譯器才能把il代碼轉(zhuǎn)換成原始代碼。
盡管il代碼由編譯器產(chǎn)生,但它并不是編譯器提供給運行時僅有的東西。編譯器同樣產(chǎn)生有關(guān)你代碼的元數(shù)據(jù),它告訴運行時有關(guān)你代碼的更多的東西,例如各種類型的定義、各種類型成員的簽名以及其它數(shù)據(jù)。基本上,元數(shù)據(jù)是類型庫、注冊表內(nèi)容和其它用于com的信息。盡管如此,元數(shù)據(jù)還是直接和執(zhí)行代碼合并在一起,并不處在隔離的位置。
il和元數(shù)據(jù)存放于擴(kuò)展了pe格式的文件中(pe格式用于.exe和.dll文件)。當(dāng)這樣的一個pe文件被裝載時,運行時從文件中定位和分離出元數(shù)據(jù)和il。
在進(jìn)一步說明之前,我想給你已有的il指令的簡短目錄。盡管它不是一個完整的清單,也不需要你熟記和理解,但是它列出了你所必需的、c#程序所基于的知識基礎(chǔ)。
算術(shù)和邏輯操作符
控制流
直接內(nèi)存訪問
堆棧操作
參數(shù)和局部變量
堆棧分配
對象模式
實例類型值
臨界區(qū)
數(shù)組
分型位置
即時編譯器(jitters)
2.1.2 即時編譯器(jitters)
由c#或其它能產(chǎn)生受管代碼的編譯器所生成的受管代碼就是il碼。雖然il代碼被包裝在一個有效的pe文件中,但是你還是不能執(zhí)行它,除非它被轉(zhuǎn)換成為受管原始代碼。這就是ngws runtime 即時編譯器(也稱作jitters)大顯身手的時候。
為什么你會對即時編譯代碼感到厭繁, 為什么不把整個il pe文件編譯成原始代碼? 答案是時間——需要把il代碼編譯成cpu規(guī)格的代碼的時間。這種編譯將更加有效率,因為一些程序段從來就沒有被執(zhí)行過。例如,在我的字處理器中,郵件合并功能從來就沒有被編譯。
從技術(shù)上說,全部的處理過程如下:當(dāng)一個類型被裝載時,裝載器創(chuàng)建一個存根(stub),并使它連接每一個類型的方法。當(dāng)一個方法第一次被調(diào)用時,存根把控制交給jit。jit把il編譯為原始代碼,且把存根指針指向緩沖了的原始代碼。接著的調(diào)用將執(zhí)行原始碼。在某些位置上(at some point),所有的il都被轉(zhuǎn)換成為原始代碼,而jitter處于空閑狀態(tài)。
正如我在前面提到的,jit編譯器有很多,不止一個。在windows平臺上,ngws runtime裝有3個不同的jit編譯器。
jit——這是ngws runtime默認(rèn)使用的jit編譯器。它是一個后臺(back end)優(yōu)化的編譯器 ,在前臺(up front)實行數(shù)據(jù)流分析,并創(chuàng)建了高度優(yōu)化的受管原始代碼做為輸出結(jié)果。jit可以使用不嚴(yán)格的il指令集編碼,但是所需資源將十分可觀。主要的限制在于內(nèi)存足跡(footprint)、結(jié)果工作集,以及實行優(yōu)化所消耗的時間。
econojit—— 和主jit相比,econjit的目標(biāo)是把il高速地轉(zhuǎn)換成受管原始代碼。它允許緩沖所產(chǎn)生的原始代碼,但是輸出碼并不象主jit生成的代碼那樣優(yōu)化(代碼小)。當(dāng)內(nèi)存緊張時,快速代碼生成方案的優(yōu)勢將蕩然無存。通過永久地拋棄無用的已jit過的代碼,你可以把更大的il程序裝入代碼緩沖區(qū)。因為jit編譯快,執(zhí)行速度也仍然很快。
prejit—盡管它是基于主jit的,但操作起來更象是一個傳 統(tǒng)的編譯器。你安裝了ngws組件,它才能運行,才可以把il代碼編譯成受管原始代碼。當(dāng)然最終的結(jié)果為,更快的裝載時間和更快的應(yīng)用程序啟動時間(不需要更多的jit編譯)。
在所列出的jitters中,有兩個是運行時的jitters。可是你怎么決定要使用哪一個jit,它如何使用內(nèi)存? 有一個稱做"jit編譯管理器"的小應(yīng)用程序(jitman.exe),它存放于ngws sdk安裝目錄下的bin目錄中。當(dāng)執(zhí)行該程序時,它把一個圖標(biāo)加到系統(tǒng)任務(wù)條上,雙擊該圖標(biāo)打開程序?qū)υ捒?見圖2.1)。
圖2.1 jit編譯管理器允許你設(shè)置各種相關(guān)性能的選項
盡管它是一個小小的對話框,可是你所選擇的選項功能是相當(dāng)強(qiáng)大的。每一個選項將在以下描述。
use econojit only 選項——當(dāng)該復(fù)選框沒有選上時,ngws runtime使用默認(rèn)的正常的jit編譯器。前面就曾經(jīng)解釋過兩種jitter的區(qū)別。
max code pitch overhead(%)選項——該設(shè)置僅保留給econojit。它控制了jit編譯時間和執(zhí)行代碼時間的百分比。如果超過了設(shè)定的域值,代碼緩沖區(qū)得到擴(kuò)充,以縮短jit編譯所消耗的時間。
limit size of code cache選項——該項默認(rèn)為非選。沒有選擇該項意味著緩沖區(qū)將使用它所能得到的內(nèi)存。如果你想限制緩沖區(qū)大小,復(fù)選該選項,這將允許你使用max size of cache(bytes)選項。
max size of cache(bytes)選項—控制容納jit代碼的緩沖區(qū)的最大值。雖然你可以非常嚴(yán)格地限制這個值,但你還是應(yīng)該小心,不能超過這個緩沖區(qū)所適合的最大值。否則該方法的jit編譯將會失敗。
optimize for size選項——告訴jit 編譯器,優(yōu)化的目的是為了使代碼更小而不是能執(zhí)行得更快。這個設(shè)置默認(rèn)是關(guān)掉的。
enable concurrent gc[garbage collection]選 項——垃圾收集(gc)默認(rèn)地運行在用戶代碼的線程中。意味gc發(fā)生時,可能會注意到回應(yīng)有輕微的延遲。為防止出現(xiàn)該現(xiàn)象,打開當(dāng)前gc。注意,當(dāng)前gc比標(biāo)準(zhǔn)gc更慢,它僅在windows 2000上寫時(the time of writing)有效。
當(dāng)用c#創(chuàng)建項目時,你可能使用不同的設(shè)置試驗過。當(dāng)創(chuàng)建 ui-intensive應(yīng)用程序時,你將會看到允許當(dāng)前gc的最大差別。
2.2 虛擬對象系統(tǒng)(vos)
到目前為止,你僅看到了ngws runtime如何工作,但是并不了解它工作的技術(shù)背景以及為什么它要這樣工作。這節(jié)都是關(guān)于 ngws 虛擬對象系統(tǒng)的(vos)。
以下為在vos中形成聲明、使用和管理類型模型時,ngws runtime的規(guī)則。在vos背后的思想是建立一個框架,在執(zhí)行代碼時不能犧牲性能,允許交叉語言集成和類型安全。
我提到的框架是運行時架構(gòu)的基礎(chǔ)。為了幫助你更好地了解它,我將它勾出四個區(qū)域。當(dāng)開發(fā)c#應(yīng)用程序和組件時,理解它們很重要。
vos類型系統(tǒng)——提供豐富的類型系統(tǒng),它打算支持全面編程語言的完全實施。
元數(shù)據(jù)——描述和引用vos類型系統(tǒng)所定義的類型。元數(shù)據(jù)的永久格式與編程語言無關(guān),但是,元數(shù)據(jù)拿自己當(dāng)作一種互換機(jī)制(nterchange mechanism)來使用,這種互換是在在工具和ngws的虛擬執(zhí)行系統(tǒng)之間。
通用語言規(guī)范(cls)——cls定義了vos中類型的子集,也定義了常規(guī)的用法。如果一個類庫遵守cls的規(guī)則,它確保類庫可以在其它所有能實現(xiàn)cls的編程語言上使用。
虛擬執(zhí)行系統(tǒng)(ves)——這是vos實時的實現(xiàn)。ves負(fù)責(zé)裝入和執(zhí)行為ngws運得時編寫的程序。
這四個部分一起組成了ngws runtime架構(gòu)。每一部分在下面小節(jié)中描述。
2.2.1 vos類型系統(tǒng)
vos類型系統(tǒng)提供豐富的類型系統(tǒng),它打算支持多種編程語言的完全實施。所以,vos必須都支持面向?qū)ο蟮恼Z言和過程編程語言。
現(xiàn)在,存在著很多種近似但有點不兼容的類型。就拿整型當(dāng)例子,在vb中,它是16位長,而在c++中,它是32位。還有更多的例子,特別是用在日期和時間以及數(shù)據(jù)庫方面的數(shù)據(jù)類型。這種不兼容使應(yīng)用程序的創(chuàng)建和維護(hù)不必要地復(fù)雜化,尤其當(dāng)程序使用了多種編程語言時。
另一個問題是,因為編程語言之間存在著一些差別,你不能在一種語言中重用另一種語言創(chuàng)建的類型。(com用二進(jìn)制標(biāo)準(zhǔn)接口部分地解決了這個問題)。 當(dāng)今代碼重用肯定是有限的。
發(fā)布應(yīng)用程序的最大障礙是各種編程語言的對象模型不統(tǒng)一。幾乎每一方面都存在著差異:事件、屬性、永久保存(persistence)等等。
vos這里將改變 這種現(xiàn)象 。vos定義了描述值的類型,并規(guī)定了類型的所有值所必須支持的一條合約。由于前面提到的支持面向?qū)ο蠛瓦^程編程語言,就存在著兩種值和對象。
對于值,類型存儲于表述(representation)中,同樣操作也在其中實行。對象更強(qiáng)大因為它顯式地存于表述中。每一個對象都有一個區(qū)別于其它對象的識別號。支持不同的vos類型在第四章 "c#類型"中提出。
2.2.2元數(shù)據(jù)
盡管元數(shù)據(jù)用于描述和引用由vos類型系統(tǒng)定義的類型,但它還不能鎖定到這個單個目標(biāo)。當(dāng)你寫一個程序時,通過利用類型聲明,你所聲明的類型(假定它們是數(shù)值類型或引用類型)被介紹給ngws runtime類型系統(tǒng)。類型聲明在存于pe可執(zhí)行文件內(nèi)部的元數(shù)據(jù)中得到描述。
基本上,元數(shù)據(jù)用于各項任務(wù):用于表示ngws runtime用途的信息,如定位和裝載類、 內(nèi)存中這些類的事例、解決調(diào)用 、翻譯il為原始碼、加強(qiáng)安全并設(shè)置運行時上下文邊界。
你不必關(guān)心元數(shù)據(jù)的生成。元數(shù)據(jù)是由c#的"代碼轉(zhuǎn)il編譯器"(code-to-il compiler,不是jit編譯器)生成的。代碼轉(zhuǎn)il編譯器發(fā)送二進(jìn)制元數(shù)據(jù)信息給pe文件,是以標(biāo)準(zhǔn)的方式發(fā)送的,不象c++編譯器那樣,為出口函數(shù)創(chuàng)建它們自己的修飾名字。
你從元數(shù)據(jù)和可執(zhí)行代碼并存所獲得的主要優(yōu)勢為,有關(guān)類型的信息同類型自身固定在一起,不會遍布很多地方。同樣有助于解決存在于com中的版本問題。進(jìn)一步地,你可以在相同的上下文中使用不同的版本庫,因為庫不僅被注冊表引用,也被包含在可執(zhí)行代碼中的元數(shù)據(jù)引用。
2.2.3通用語言規(guī)范
通用語言規(guī)范(cls)并不是虛擬對象系統(tǒng)(vos)真正的一部分,它是特殊的。cls定義了vos中的一個類型子集,也定義了必須符合cls的常規(guī)用法。
那么,對此有什么迷惑呢?如果一個類庫遵守cls規(guī)則,其它編程語言同樣也遵守cls規(guī)則,那么其它編程語言的客戶也可以使用類庫。cls是關(guān)于語言的交互可操作性(interoperability)。因此,常規(guī)用法必須僅遵循外部可訪問項目 (externally visible items)如方法、屬性和事件等等。
我所描述的優(yōu)點是你可以做以下工作。用c#寫一個組件,在vb中派生它,因加在vb中的功能是如此之強(qiáng)大,在c#中再次從vb類派生它。只要所有的外部可訪問項遵守cls規(guī)則,這樣是可行的。
我在這本書中出示的代碼不關(guān)心cls協(xié)定。但在構(gòu)建你的類庫時要注意到cls協(xié)定。我提供了表2.1,用以給類型和外部可訪問項定義協(xié)定規(guī)則。
這個清單不完整。它僅包含一些很重要的項目。我不指出出現(xiàn)在本書中每一種類型的cls協(xié)定,所以有個好主意:當(dāng)你尋找cls協(xié)定時,至少應(yīng)該用瀏覽該表,以了解哪種功能有效。不要擔(dān)心你不熟悉這章表中的每一個含義,在這本書中你會學(xué)到它們。
表2.1 通能語言規(guī)范中的類型和功能
bool
char
byte
short
int
long
float
double
string
object(所有對象之母)
arrays(數(shù)組)
數(shù)組的維數(shù)必須是已知的(>=1),而且最小下標(biāo)數(shù)必須為0。
要素類型必須是一個cls類型。
類型(types)
可以被抽象或隱藏。
零或更多的接口可以被實現(xiàn)。不同的接口允許擁有具有相同名字和簽名的方法。
一個類型可以準(zhǔn)確地從一個類型派生。允許成員被覆蓋和被隱藏。
可以有零或更多的成員,它們是字段(fields)、方法、事件或者類型。
類型可以擁有零或更多個構(gòu)造函數(shù)。
一種類型的可訪問性可以是公共的或者對ngws組件來說是局部的;但是,僅公共成員可以認(rèn)為是類型接口的一部分。
所有的值型必須從系統(tǒng)值型繼承。異常是一個枚舉——它必須從系統(tǒng)枚舉(system enum)繼承。
類型成員
類型成員允許隱藏或者覆蓋另一種類型中的其它成員。
參數(shù)和返回值的類型都必須是 cls 協(xié)定 類型。
構(gòu)造函數(shù)、方法和屬性可以被重載。
一個類型可以有抽象成員,但僅當(dāng)類型不被封裝時。
方法
一種方法可以是靜態(tài)、虛擬或者實例。
虛擬和實例方法可以是抽象的,或者是一個實現(xiàn)。靜態(tài)方法必須總擁有一個實現(xiàn)。
虛擬方法可能是最后的(或者不是)。
字段(fields)
可以是靜態(tài)或者是非靜態(tài)。
靜態(tài)字段可以被描述或只初始化。
屬性
當(dāng)獲取和設(shè)置方法而不是使用屬性語法時,屬性可以公開。
獲取的返回類型和設(shè)置方法的第一個參數(shù)必須是相同的cls類型——屬性的類型。
屬性名字必須不同,不同的屬性類型用于區(qū)分是不充分的。
由于使用方法實現(xiàn)屬性訪問,如果 propertyname 是同一個類中定義的一個屬性,你不能實現(xiàn)命名為 get_propertyname 和 set_propertyname 的方法。
屬性可以被索引。
屬性訪問必須遵循這種命名格式:get_proname,set_propname。
枚舉(enumerations)
強(qiáng)調(diào)類型必須是byte、short、int 或long。
每一個成員是一個枚舉類型的靜態(tài)描述字段。
一個枚舉不能實現(xiàn)任何接口。
你允許給多字段設(shè)定相同的值。
一個枚舉必須繼承系統(tǒng)枚舉(隱含在c#中)
異常
可以被引發(fā)和被捕獲。
自定義異常必須繼承系統(tǒng)異常。
接口
可需要實現(xiàn)其它接口。
一個接口可以定義屬性、事件和虛擬方法。實現(xiàn)取決于派生類。
事件
增加和取消方法必須是都提供或者都沒有 ,每一種方法采用一個參數(shù),它是一個從系統(tǒng)代表元(system delegate)派生下來的類。
自定義屬性
可以僅使用下更類型:type(類型),char, char, bool, byte, short, int, long, float, double, enum (一種cls 類型), and object.
代表元(delegates)
可以被創(chuàng)建和被激活
標(biāo)識符(identifiers)
一個標(biāo)識符的第一個字母必須來自一限制集。
通過大小寫在單一范圍內(nèi),不可能唯一地區(qū)別兩個或更多個標(biāo)識符(大小寫不敏感)。
2.2.4虛擬執(zhí)行系統(tǒng)(ves)
虛擬執(zhí)行系統(tǒng)實現(xiàn)了虛擬對象系統(tǒng)。通過實現(xiàn)一個負(fù)責(zé)ngws runtime的執(zhí)行引擎(execution engine,縮寫ee)創(chuàng)建ves。這個執(zhí)行引擎執(zhí)行你用c#編寫和編譯的應(yīng)用程序。
下列組件為ves的一部分。
1、中間語言(il)——被設(shè)計為很容易受各種各樣的編譯器所兼容 。在該框架之外,c++、vb和c#編譯器都能夠生成il。
2、裝入受管代碼——這包括解決內(nèi)存中的名字、 表層類(laying out classes ),并且創(chuàng)建jit編譯所必需的存根。通過執(zhí)行經(jīng)常性校驗,包括加強(qiáng)一些訪問規(guī)則,類裝載器同樣也增強(qiáng)了安全性。
3、用jit轉(zhuǎn)換il成原始代碼——il代碼并不是設(shè)計成為一種傳統(tǒng)的解釋字節(jié)代碼或樹型代碼,il轉(zhuǎn)換是真正的編譯。
4、裝入元數(shù)據(jù)、校驗類型安全和方法的完整性
5、垃圾收集(gc)和異常處理——兩者都是基于堆棧格式的服務(wù)。受管代碼允許你動態(tài)地跟蹤堆棧。要動態(tài)地識別各個堆棧框架,jitter或其它編譯器必須提供一個代碼管理器。
6、描繪和查錯服務(wù)——兩者都取決于由源語言編譯器所生成的信息。必須發(fā)出兩個映射:一個映射從源語言結(jié)構(gòu)發(fā)到指令流中的地址,一個映射從地址發(fā)到堆棧框架中的位置。當(dāng)執(zhí)行從il到原始代碼的轉(zhuǎn)換時,這些映射被重新計算。
7、管理線程和上下文,還有遠(yuǎn)程管理——ves為受管代碼提供這些服務(wù)。
雖然這個清單并不完整,但它足以讓你理解運行時基于的由ves提供的低層架構(gòu)。肯定將會有專門討論運行時的書,而這本書將稍為深入地挖掘各種話題。
2.3 小結(jié)
這一章,我?guī)愎淞艘换剡\行時的世界。我描述了當(dāng)創(chuàng)建、編譯和配置c#程序時它是如何工作的。你學(xué)會了中間語言(il),還有元數(shù)據(jù)是如何用于描述被編譯為il的類型。元數(shù)據(jù)和il都用于jitter檢測和執(zhí)行你的代碼。你甚至可以選擇用哪一種jitter來執(zhí)行應(yīng)用程序。
在這一章的第二部分,涉及到了運行時為何按這種方式工作的理論。你學(xué)了虛擬對象系統(tǒng)(vos)和組成它的那部分。對于類庫設(shè)計者最為感興趣的就是通用語言規(guī)范(cls),它為基于vos的語言交互操作設(shè)定規(guī)則。最后,你看到了虛擬執(zhí)行系統(tǒng)(ves)如何通過ngws runtime實現(xiàn)vos。