.NET編譯技術內幕(1)

來源:互聯網
上載者:User
編譯 通過MSIL瞭解CLR的運行原理
作者: Wednesday, April 17 2002 2:12 PM



作為.NET最低層次的公用基礎,微軟中介語言(MSIL或IL)對一般開發人員具有非常重要的意義。除了好奇心以外,仔細研究應用程式的IL能讓你更為清楚地瞭解到通用語言執行平台(CLR)執行進階C#或VB.NET代碼的基本原理,從而有助於你發現和解決一些比較細微的問題。

在這篇文章裡,我將引領讀者瞭解IL,學習有關的一些關鍵指令,同時對CLR的操作機理做一點基礎性解釋。我不打算教你用IL編程,而是分析一些IL文法和語句使你對IL有更多瞭解。

ILDASM簡介
微軟的IL拆卸公用程式Ildasm.exe(通常位於\Program Files\Microsoft.Net\FrameworkSDK\Bin目錄下)可以析構.NET assembly(裝配)、根據你的要求從程式中抽取IL代碼。對某一assembly調用該使用程式後,ILDASM會給出該assembly中所有類和名稱空間的一個視圖,如圖A所示:

" target=_blank>image001.gif

圖A


ILDASM瀏覽assembly

當你進到某個類的成員或其方法,ILDASM就會為你顯示該成員的IL代碼。如果之前你曾經看到過彙編器或J++位元組碼,那麼IL可能在你看來會覺得有點眼熟。在另一方面,如果你僅對抽象的進階程式語言有所瞭解,那麼IL看起來更像是胡言亂語。

好,現在你知道如何窺視assembly的IL代碼了,但這些代碼都意味著什麼呢?在回答這個問題之前,首先讓我們先來瞭解下CLR的有關知識。

虛擬CPU
對.NET程式來說,.NET CLR在功能上就如同一塊虛擬CPU,它執行IL代碼、操作資料。CLR和真實的CPU類似之處在於它們都不直接操作記憶體中的變數而是使用程式變數的臨時拷貝,CLR把這些程式變數存放在堆棧上。從記憶體拷貝某個變數到堆棧的行為稱做裝載(loading),而從堆棧拷回某個變數到記憶體的行為則被稱做儲存(storing)。

所以把兩個數字相加的過程應該是這樣的:

1.裝載第1個數字並把它推入堆棧。

2.裝載第2個數字並把它推入堆棧。

3.從堆棧中取出這兩個數字並把它們相加。

4.把結果儲存到記憶體。

什麼是堆棧?
理解IL的關鍵是知道堆棧的工作原理。堆棧是一種抽象資料結構,其操作機理是後進先出。當你把新條目推進堆棧時,已經在堆棧內的任何條目都會壓到堆棧的深處。同樣的,把一個條目從堆棧移出則會讓堆棧內的其他條目都向堆棧的頂部移動。只有堆棧最頂端的條目能從堆棧中取出,條目離開堆棧的順序和它們被推進堆棧的順序一樣。你不妨回想下自動售貨機的裝貨和取貨過程就明白了。

重要的IL語句
既然你已經明白了CLR操作的基礎知識,下面我們就接著討論你面前的那些代碼。怎嗎?沒有看到什麼代碼?那麼請你看看這裡列出的IL代碼。

Sidebar A: Sample IL listing


.method private instance void  Listen() cil managed
{
  // Code size       169 (0xa9)
  .maxstack  4
  .locals init ([0] unsigned int8[] buff,
           [1] class [System]System.Net.Sockets.NetworkStream ChatStream,
           [2] class [System]System.Net.Sockets.TcpClient RemoteClient,
           [3] string strHandle,
           [4] class Chat.ChatServer/TextRelayer 'handler',
           [5] class [mscorlib]System.Threading.Thread t)
  IL_0000:  nop
  IL_0001:  nop
  .try
  {
    IL_0002:  br         IL_0091
    IL_0007:  ldarg.0
    IL_0008:  ldfld      class [System]System.Net.Sockets.TcpListener Chat.ChatServer::Listener
    IL_000d:  callvirt   instance class [System]System.Net.Sockets.TcpClient [System]System.Net.Sockets.TcpListener::AcceptTcpClient()
    IL_0012:  stloc.2
    IL_0013:  ldloc.2
    IL_0014:  callvirt   instance class [System]System.Net.Sockets.NetworkStream [System]System.Net.Sockets.TcpClient::GetStream()
    IL_0019:  stloc.1
    IL_001a:  ldc.i4.s   26
    IL_001c:  newarr     [mscorlib]System.Byte
    IL_0021:  stloc.0
    IL_0022:  ldloc.1
    IL_0023:  ldloc.0
   IL_0024:  ldc.i4.0
    IL_0025:  ldc.i4.s   24
    IL_0027:  callvirt   instance int32 [System]System.Net.Sockets.NetworkStream::Read(unsigned int8[],
                                                                                       int32,
                                                                                      int32)
    IL_002c:  pop
    IL_002d:  call       class [mscorlib]System.Text.Encoding [mscorlib]System.Text.Encoding::get_ASCII()
    IL_0032:  ldloc.0
    IL_0033:  callvirt   instance string [mscorlib]System.Text.Encoding::GetString(unsigned int8[])
    IL_0038:  stloc.3
    IL_0039:  ldarg.0
    IL_003a:  ldfld      class Chat.ChatServer/IncomingChatRequest Chat.ChatServer::RequestConnect
    IL_003f:  ldloca.s   strHandle
    IL_0041:  callvirt   instance bool Chat.ChatServer/IncomingChatRequest::Invoke(string&)
    IL_0046:  brtrue.s   IL_0051
    IL_0048:  ldloc.2
    IL_0049:  callvirt   instance void [System]System.Net.Sockets.TcpClient::Close()
    IL_004e:  nop
    IL_004f:  br.s       IL_008f
    IL_0051:  nop
    IL_0052:  ldloca.s   strHandle
    IL_0054:  ldarg.0
    IL_0055:  ldflda     class Chat.ChatServer/IncomingText Chat.ChatServer::RelayText
    IL_005a:  ldloca.s   ChatStream
    IL_005c:  newobj     instance void Chat.ChatServer/TextRelayer::.ctor(string&,
                                                                          class Chat.ChatServer/IncomingText&,
                                                                          class [System]System.Net.Sockets.NetworkStream&)
    IL_0061:  stloc.s    'handler'
    IL_0063:  ldloc.s    'handler'
    IL_0065:  dup
    IL_0066:  ldvirtftn  instance void Chat.ChatServer/TextRelayer::HandleChat()
    IL_006c:  newobj     instance void [mscorlib]System.Threading.ThreadStart::.ctor(object,
                                                                                     native int)
    IL_0071:  newobj     instance void [mscorlib]System.Threading.Thread::.ctor(class [mscorlib]System.Threading.ThreadStart)
    IL_0076:  stloc.s    t
    IL_0078:  ldarg.0
    IL_0079:  ldfld      class [System]System.Collections.Specialized.ListDictionary Chat.ChatServer::ChatThreads
    IL_007e:  ldloc.3
    IL_007f:  ldloc.s    t
    IL_0081:  callvirt   instance void [System]System.Collections.Specialized.ListDictionary::Add(object,
                                                                                                  object)
    IL_0086:  nop
    IL_0087:  ldloc.s    t
    IL_0089:  callvirt   instance void [mscorlib]System.Threading.Thread::Start()
    IL_008e:  nop
    IL_008f:  nop
    IL_0090:  nop
    IL_0091:  ldc.i4.1
    IL_0092:  brtrue     IL_0007
    IL_0097:  leave.s    IL_00a6
  }  // end .try
  finally
  {
    IL_0099:  nop
    IL_009a:  ldstr      "Listener Thread Aborted."
    IL_009f:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_00a4:  nop
    IL_00a5:  endfinally
  }  // end handler
  IL_00a6:  nop
  IL_00a7:  nop
  IL_00a8:  ret
} // end of method ChatServer::Listen





你首先看見的是對當前方法的IL聲明,其中包括方法的名字,傳回型別、參數列表以及附著於該方法的其他修飾關鍵詞(static/shared、public、virtual等等)。物件建構器則被賦給一個特殊的名字:.ctor。

在IL中,方法參數按照它們在參數列表中的位置依次被引用。如果方法是靜態或共用方法,那麼參數0則是參數列表中的第1個參數。而對執行個體方法來說,參數0則是指向該方法所在類的執行個體的指標(Me或者this)。方法中的所有局部變數都在.locals標記的段落中以同樣的方式聲明。

在聲明所有的局部變數以後,程式的實際本文才開始。每條IL指令,或opcode都可以根據你的喜好以一個IL_ 標記作為程式碼開頭。我們接下來再瞭解些更重要的IL指令。

變數用法
以LD開頭的指令把變數從記憶體裝載到堆棧供其操作。裝載指令有若干條,每一條裝載指令都操作特定類型的變數。以下就是其中的一些裝載指令:

LDC把一個數字常數裝入堆棧。這條指令有兩個修飾詞。第一個是類型標識符,第二個是實際的數值。
LDLOC把一個局部變數裝入堆棧。另外還有一條LDLOCA指令把一個局部變數的地址(而非變數的內容)裝入堆棧。變數由它們在.locals節的位置標識。這些指令裝載位置4及以後位置使用不同的文法,但是索引號會出現在指令中。
LDARG裝載成員的一個參數,而LDARGA指令則裝載參數的地址。變數由它們在.locals節中的位置標識。這些指令裝載位置4及以後位置使用不同的文法,但是索引號仍然出現在指令中。
LDELEM把數組元素裝入堆棧而且通常先於表示這個索引的其他裝載語句之前使用。
LDLEN把一個數組的長度裝入堆棧。
LDFLD和LDSFLD把類域(成員變數)和靜態類域裝入堆棧。域由一個全名識別。
每一條裝載指令都有對應的一條儲存指令,後者以ST開頭,負責把一個條目存入記憶體。例如,STLOC就負責把堆棧最頂端的條目存入一個局部變數。儲存指令指定變數的句法規則通常和它們對應的裝載指令類似。

比較操作
如果你不能比較兩個值而且根據其比較結果做出決定,那麼許多問題都無法用任何程式語言來解決。IL有一套比較操作符,它們都以C字母開頭,比較堆棧中的值。通常,如果比較結果為真則會把1推入堆棧否則就推入0。

大多數這類指令都很容易由它們的名字區分出來。例如,CEQ比較兩個值是否相等,而CGT則確定堆棧最頂端的值是否比第二個最頂端值更大。 CLT類同於CGT,不過執行的是小於比較操作。

Goto
通常,在對兩個值進行比較之後會根據比較的結果結果實施一些操作。IL分支指令(以BR開頭)根據堆棧最頂端的條目中的內容跳到其他指令。BRTRUE 和BRFALSE彈出堆棧最頂端的條目,然後根據該項為真(1)還是為假(0)而分別跳到指定的程式碼。如果沒有執行指令跳躍則繼續執行下一條指令。另外還有一個無條件分支操作符BR,它總是跳到指定的程式碼。

你會發現分支操作就好像原始碼中的if語句以及顯式執行的Goto操作。IL中的分支命令同樣具有進階流程式控制制結構的對等體,比如:if, case, while, for等等。

創造新對象和調用其他代碼
CALL和CALLVIRT指令調用其他方法和函數。CALL通常表示被調用的方法是靜態或共用的,而CALLVIRT則用於執行個體方法。就兩種指令來說,方法的名字都會在指令中包括。被送到方法的任何參數都會被彈出堆棧而且要在方法被調用之前裝載。

因為建立一個新對象需要調用構造器,所以IL的對象建立也類似於其他的方法調用。參數首先被裝載到堆棧,然後執行NEWOBJ指令,它調用對象的構造器同時把對象的索引放回堆棧。指令中得有對象的名字。

以上就是大致的IL文法操作。除了滿足你內心的求知慾望以外,我希望你能從我的闡述中得到足夠的資訊來理解IL代碼的真實含義。





相關文章

E-Commerce Solutions

Leverage the same tools powering the Alibaba Ecosystem

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。