直播中
圖1
在深入.NET體系之前,我們需要了解一些有關.NET的邊緣知識和術語。首先,IL(中間語言)并不是什么新概念,VB、C++編譯器生成和利用IL已經有數(shù)年的歷史,只不過很少有人公開談論它或為它編寫文檔。與過去編譯應用的方法相比,.NET最大的改變之一就在于編譯器所生成的代碼不同。除了名字之外,新的MSIL與VB6編譯器的IL很少有類同之處。因此,如果你以前曾經接觸過IL,現(xiàn)在還得從頭開始學習。請參見圖2,它是GoodByeVB6應用的MSIL代碼片段:
圖2
這個代碼片段設置了一個8字節(jié)的棧,然后把this指針壓棧,調用get_Label1方法。接下來,代碼把要設置的標簽文本壓入堆棧,然后調用setText方法。
傳統(tǒng)的CPU利用寄存器和棧完成所有工作。CLR所提供的執(zhí)行引擎只有一個棧,它的操作過程非常類似于一個逆波蘭表示法計算器。如果某個過程調用具有多個參數(shù),執(zhí)行引擎將在發(fā)出調用之前把參數(shù)壓棧。函數(shù)調用的返回值也通過棧傳遞。
MSIL中的局部變量很容易識別,它們用.locals關鍵詞聲明。如果符號存在的話,你將看到變量名字;否則,你看到的將是V_1、V_2之類的變量:
.locals init ([0] int32 x,
[1] int32 y,
[2] float64 z,
[3] class System.String Vb_t_string_0)
ldarg指令把參數(shù)裝入棧,ldc指令把數(shù)字常量裝入棧,stloc指令把值保存到合適的局部變量:
//000064: Dim x As Integer = 100
IL_0001: ldc.i4.s 100
IL_0003: stloc.0
在這個例子中,常量100被作為4字節(jié)整數(shù)壓入棧,隨后這個值被保存到第1個局部變量。關于MSIL指令的完整說明,請參見IL程序員參考的ILinstrset.doc文件。
本文的所有MSIL輸出都以GoodByeVB6應用的調試版本為基礎。非調試版本雖然不帶代碼行和變量名字,但仍能夠提供大量有用的信息。在查看MSIL代碼的時候,調試符號雖然重要,但不是必不可少的。
當我們運行編譯器時,它生成的不是我們今天熟悉的執(zhí)行文件,而是一個程序集(Assembly)。程序集是一個文件的集合,程序集中的文件可以作為單一整體進行部署。在當前的Windows體系中,我們可以把單個執(zhí)行文件看成一個程序集。但從更嚴格的意義上來說,程序集聚合了執(zhí)行文件和它的所有支持文件,包括DLL、圖形、資源以及幫助文件。
一般地,一個程序集至少由兩個文件構成:執(zhí)行部分,manifest(英文單詞原意:載貨清單,乘客名單)。manifest是程序集內所有文件的清單。程序集內的可執(zhí)行部分又分開稱為模塊(Module)。從概念上說,模塊對應著DLL或者EXE文件;除了父程序集所包含的元數(shù)據(jù)(Metadata)之外,每一個模塊都包含元數(shù)據(jù)。程序集是當前可移植執(zhí)行文件格式(Portable Executable,PE)的一個增強版本。
如圖3所示,文件的開頭是標準的PE頭。文件內部包含了CLR頭,CLR頭的后面是把代碼裝入進程空間所必需的描述數(shù)據(jù)——即元數(shù)據(jù)。元數(shù)據(jù)為執(zhí)行引擎提供了大量信息,其中包括:如何裝載模塊,需要哪些支持文件,如何裝載支持文件,如何與COM以及.NET運行時環(huán)境交互。另外,元數(shù)據(jù)還描述了模塊或者程序集所包含的方法、接口以及類。元數(shù)據(jù)所提供的信息使得JIT編譯器能夠編譯并運行模塊。同時,元數(shù)據(jù)暴露了有關應用的大量內部信息,使得從反匯編IL獲取有價值的代碼更加方便。
圖3
使用.NET代碼的核心問題在于受管理代碼。受管理代碼是專門為在CLR控制之下運行而編寫的代碼,它可以用VB.NET、C#以及C++等語言創(chuàng)建,但C++是唯一能夠創(chuàng)建.NET平臺非受管理代碼的語言。我們無法用VB6為.NET平臺創(chuàng)建非受管理代碼,這是因為在VB6中我們把代碼編譯成i386指令而不是IL代碼。正如使用VB.NET,如果你要使用受管理代碼,你只能把代碼編譯成IL。
現(xiàn)在我們來看看使用這種新的MSIL代碼有哪些優(yōu)點。如果代碼編譯成了MSIL,我們可以在任何支持CLR的平臺上安裝和運行這些代碼。就目前來說,這一點可能不是很吸引人,因為當前支持.NET的平臺還很少:只有32位的Windows。但不久之后,64位平臺和.NET for Windows CE都將提供這方面的支持。把代碼編譯成MSIL格式使得我們能夠無縫地把應用移植到所有這些平臺和未來的新平臺。
MSIL的另外一個優(yōu)點是:JIT編譯器在安裝應用的目標機器上把MSIL代碼編譯成機器指令,它能夠利用目標機器的硬件特點,根據(jù)平臺的具體情況對代碼進行優(yōu)化。這一點很有用,例如,它能夠為目標機器的特殊寄存器優(yōu)化代碼,或為目標機器上帶有特殊處理器的硬件設備優(yōu)化操作代碼。請點擊VB6工程屬性窗口Compile選項卡中Advanced Optimizations按鈕了解更多信息。由于有了程序集中的元數(shù)據(jù),JIT編譯器知道代碼做些什么以及它支持哪些平臺,從而能夠迅速地作出優(yōu)化決定、提高代碼的性能表現(xiàn)。
還有一個優(yōu)點涉及到.NET的兩個V:Validation(檢驗),Verification(核查)。檢驗是對模塊進行的一系列檢查,確保元數(shù)據(jù)、MSIL代碼以及文件格式的一致性。不能通過這些檢查的代碼可能導致執(zhí)行引擎或者JIT編譯器崩潰。一旦模塊通過了檢驗,則代碼是正確的且可以開始運行。
JIT編譯器把MSIL代碼轉換成機器代碼時對代碼進行核查,它是對元數(shù)據(jù)進行復查,保證程序不會訪問它不具有相應許可的內存或其他資源。經過核查的代碼是類型安全的(Type-Safe)代碼。這種核查即使是在程序被直接編譯成機器代碼的時候也要進行,但除非由JIT編譯器進行核查,否則這種核查不是100%精確無誤,因為核查結果依賴于來自其他程序集的元數(shù)據(jù)。如果把源程序直接編譯成機器代碼,我們面臨著這樣一種危險:在目標機器上的其他程序集發(fā)生了變化,從而導致程序不再類型安全。
使用JIT編譯器保證了檢驗和核查是對所有相關程序集的當前版本進行。這些操作確保執(zhí)行程序總是類型安全,程序總是以合適的安全許可運行。你可以用.NET SDK的PEVerify工具自己對代碼進行檢驗和核查。