|
通過MSIL了解CLR的運(yùn)行原理 作者: Wednesday, April 17 2002 2:12 PM
作為.NET最低層次的公共基礎(chǔ),微軟中介語(yǔ)言(MSIL或IL)對(duì)一般開發(fā)者具有非常重要的意義。除了好奇心以外,仔細(xì)研究應(yīng)用程序的IL能讓你更為清楚地了解到公共語(yǔ)言運(yùn)行時(shí)(CLR)執(zhí)行高級(jí)C#或VB.NET代碼的基本原理,從而有助于你發(fā)現(xiàn)和解決一些比較細(xì)微的問題。
在這篇文章里,我將引領(lǐng)讀者了解IL,學(xué)習(xí)有關(guān)的一些關(guān)鍵指令,同時(shí)對(duì)CLR的操作機(jī)理做一點(diǎn)基礎(chǔ)性解釋。我不打算教你用IL編程,而是分析一些IL語(yǔ)法和語(yǔ)句使你對(duì)IL有更多了解。
ILDASM簡(jiǎn)介 微軟的IL拆卸實(shí)用程序Ildasm.exe(通常位于\Program Files\Microsoft.Net\FrameworkSDK\Bin目錄下)可以析構(gòu).NET assembly(裝配)、根據(jù)你的要求從程序中抽取IL代碼。對(duì)某一assembly調(diào)用該使用程序后,ILDASM會(huì)給出該assembly中所有類和名稱空間的一個(gè)視圖,如圖A所示:
/article/UploadPic/200671911658800.gif 圖A
ILDASM瀏覽assembly
當(dāng)你進(jìn)到某個(gè)類的成員或其方法,ILDASM就會(huì)為你顯示該成員的IL代碼。如果之前你曾經(jīng)看到過匯編器或J++字節(jié)碼,那么IL可能在你看來會(huì)覺得有點(diǎn)眼熟。在另一方面,如果你僅對(duì)抽象的高級(jí)程序語(yǔ)言有所了解,那么IL看起來更像是胡言亂語(yǔ)。
好,現(xiàn)在你知道如何窺視assembly的IL代碼了,但這些代碼都意味著什么呢?在回答這個(gè)問題之前,首先讓我們先來了解下CLR的有關(guān)知識(shí)。
虛擬CPU 對(duì).NET程序來說,.NET CLR在功能上就如同一塊虛擬的CPU,它執(zhí)行IL代碼、操作數(shù)據(jù)。CLR和真實(shí)的CPU類似之處在于它們都不直接操作內(nèi)存中的變量而是使用程序變量的臨時(shí)拷貝,CLR把這些程序變量存放在堆棧上。從內(nèi)存拷貝某個(gè)變量到堆棧的行為稱做裝載(loading),而從堆棧拷回某個(gè)變量到內(nèi)存的行為則被稱做存儲(chǔ)(storing)。
所以把兩個(gè)數(shù)字相加的過程應(yīng)該是這樣的:
1.裝載第1個(gè)數(shù)字并把它推入堆棧。
2.裝載第2個(gè)數(shù)字并把它推入堆棧。
3.從堆棧中取出這兩個(gè)數(shù)字并把它們相加。
4.把結(jié)果存儲(chǔ)到內(nèi)存。
什么是堆棧? 理解IL的關(guān)鍵是知道堆棧的工作原理。堆棧是一種抽象數(shù)據(jù)結(jié)構(gòu),其操作機(jī)理是后進(jìn)先出。當(dāng)你把新條目推進(jìn)堆棧時(shí),已經(jīng)在堆棧內(nèi)的任何條目都會(huì)壓到堆棧的深處。同樣的,把一個(gè)條目從堆棧移出則會(huì)讓堆棧內(nèi)的其他條目都向堆棧的頂部移動(dòng)。只有堆棧最頂端的條目能從堆棧中取出,條目離開堆棧的順序和它們被推進(jìn)堆棧的順序一樣。你不妨回想下自動(dòng)售貨機(jī)的裝貨和取貨過程就明白了。
重要的IL語(yǔ)句 既然你已經(jīng)明白了CLR操作的基礎(chǔ)知識(shí),下面我們就接著討論你面前的那些代碼。怎么?沒有看到什么代碼?那么請(qǐng)你看看這里列出的IL代碼。
Sidebar A: Sample IL listing .method private instance voidListen() cil managed { // Code size 169 (0xa9) .maxstack4 .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:ldfldclass [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:ldfldclass 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:ldvirtftninstance 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.st IL_0078:ldarg.0 IL_0079:ldfldclass [System]System.Collections.Specialized.ListDictionary Chat.ChatServer::ChatThreads IL_007e:ldloc.3 IL_007f:ldloc.st IL_0081:callvirt instance void [System]System.Collections.Specialized.ListDictionary::Add(object, object) IL_0086:nop IL_0087:ldloc.st 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.sIL_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
你首先看見的是對(duì)當(dāng)前方法的IL聲明,其中包括方法的名字,返回類型、參數(shù)列表以及附著于該方法的其他修飾關(guān)鍵詞(static/shared、public、virtual等等)。對(duì)象構(gòu)造器則被賦給一個(gè)特殊的名字:.ctor。
在IL中,方法參數(shù)按照它們?cè)趨?shù)列表中的位置依次被引用。如果方法是靜態(tài)或共享方法,那么參數(shù)0則是參數(shù)列表中的第1個(gè)參數(shù)。而對(duì)實(shí)例方法來說,參數(shù)0則是指向該方法所在類的實(shí)例的指針(Me或者this)。方法中的所有局部變量都在.locals標(biāo)記的段落中以同樣的方式聲明。
在聲明所有的局部變量以后,程序的實(shí)際正文才開始。每條IL指令,或opcode都可以根據(jù)你的喜好以一個(gè)IL_ 標(biāo)記作為代碼行開頭。我們接下來再了解些更重要的IL指令。
變量用法 以LD開頭的指令把變量從內(nèi)存裝載到堆棧供其操作。裝載指令有若干條,每一條裝載指令都操作特定類型的變量。以下就是其中的一些裝載指令:
LDC把一個(gè)數(shù)字常數(shù)裝入堆棧。這條指令有兩個(gè)修飾詞。第一個(gè)是類型標(biāo)識(shí)符,第二個(gè)是實(shí)際的數(shù)值。 LDLOC把一個(gè)局部變量裝入堆棧。另外還有一條LDLOCA指令把一個(gè)局部變量的地址(而非變量的內(nèi)容)裝入堆棧。變量由它們?cè)?locals節(jié)的位置標(biāo)識(shí)。這些指令裝載位置4及以后位置使用不同的語(yǔ)法,但是索引號(hào)會(huì)出現(xiàn)在指令中。 LDARG裝載成員的一個(gè)參數(shù),而LDARGA指令則裝載參數(shù)的地址。變量由它們?cè)?locals節(jié)中的位置標(biāo)識(shí)。這些指令裝載位置4及以后位置使用不同的語(yǔ)法,但是索引號(hào)仍然出現(xiàn)在指令中。 LDELEM把數(shù)組元素裝入堆棧而且通常先于表示這個(gè)索引的其他裝載語(yǔ)句之前使用。 LDLEN把一個(gè)數(shù)組的長(zhǎng)度裝入堆棧。 LDFLD和LDSFLD把類域(成員變量)和靜態(tài)類域裝入堆棧。域由一個(gè)全名識(shí)別。 每一條裝載指令都有對(duì)應(yīng)的一條存儲(chǔ)指令,后者以ST開頭,負(fù)責(zé)把一個(gè)條目存入內(nèi)存。例如,STLOC就負(fù)責(zé)把堆棧最頂端的條目存入一個(gè)局部變量。存儲(chǔ)指令指定變量的句法規(guī)則通常和它們對(duì)應(yīng)的裝載指令類似。
比較操作 如果你不能比較兩個(gè)值而且根據(jù)其比較結(jié)果做出決定,那么許多問題都無法用任何程序語(yǔ)言來解決。IL有一套比較操作符,它們都以C字母開頭,比較堆棧中的值。通常,如果比較結(jié)果為真則會(huì)把1推入堆棧否則就推入0。
大多數(shù)這類指令都很容易由它們的名字區(qū)分出來。例如,CEQ比較兩個(gè)值是否相等,而CGT則確定堆棧最頂端的值是否比第二個(gè)最頂端值更大。 CLT類同于CGT,不過執(zhí)行的是小于比較操作。
Goto 通常,在對(duì)兩個(gè)值進(jìn)行比較之后會(huì)根據(jù)比較的結(jié)果結(jié)果實(shí)施一些操作。IL分支指令(以BR開頭)根據(jù)堆棧最頂端的條目中的內(nèi)容跳到其他指令。BRTRUE 和BRFALSE彈出堆棧最頂端的條目,然后根據(jù)該項(xiàng)為真(1)還是為假(0)而分別跳到指定的代碼行。如果沒有執(zhí)行指令跳躍則繼續(xù)執(zhí)行下一條指令。另外還有一個(gè)無條件分支操作符BR,它總是跳到指定的代碼行。
你會(huì)發(fā)現(xiàn)分支操作就好像源代碼中的if語(yǔ)句以及顯式執(zhí)行的Goto操作。IL中的分支命令同樣具有高級(jí)流程控制結(jié)構(gòu)的對(duì)等體,比如:if, case, while, for等等。
創(chuàng)造新對(duì)象和調(diào)用其他代碼 CALL和CALLVIRT指令調(diào)用其他方法和函數(shù)。CALL通常表示被調(diào)用的方法是靜態(tài)的或共享的,而CALLVIRT則用于實(shí)例方法。就兩種指令來說,方法的名字都會(huì)在指令中包括。被送到方法的任何參數(shù)都會(huì)被彈出堆棧而且要在方法被調(diào)用之前裝載。
因?yàn)閯?chuàng)建一個(gè)新對(duì)象需要調(diào)用構(gòu)造器,所以IL的對(duì)象創(chuàng)建也類似于其他的方法調(diào)用。參數(shù)首先被裝載到堆棧,然后執(zhí)行NEWOBJ指令,它調(diào)用對(duì)象的構(gòu)造器同時(shí)把對(duì)象的索引放回堆棧。指令中得有對(duì)象的名字。
以上就是大致的IL語(yǔ)法操作。除了滿足你內(nèi)心的求知欲望以外,我希望你能從我的闡述中得到足夠的信息來理解IL代碼的真實(shí)含義。
|
溫馨提示:喜歡本站的話,請(qǐng)收藏一下本站!