前文回顧:
前言:
今天是乙未羊年的第一天,小匹夫先在這里給各位看官拜個年了。不知道各位看官是否和匹夫一樣,摸鍵盤的手都已經有點生疏了呢?所以,為了不忘卻程序猿的使命,不冷落程序猿最好的伙伴--鍵盤。匹夫決定來寫《用CIL寫程序》的最新一篇文章。可是寫什么主題呢?之前匹夫也介紹過CIL其實也是面向對象的,所以尋思着大過年的,不如就寫一個類,一個用來抽象化小匹夫的類吧,既可以介紹下小匹夫,小匹夫也可以借這個類給各位拜年。那么順序由上到下,無外乎如何聲明一個類,類成員如何定義,以至於到后來如何實例化一個類,並且調用實例的各個方法,當然本文的完整CIL代碼各位可以在附錄部分看到。
披着慕容小匹夫的外衣
OK,那么聲明一個類的第一步是什么呢?定義一個類的名字?寫構造函數?NO,NO。
聲明一個類的第一步,自然是要告訴別人你聲明的是一個類!不是方法,不是變量,而是一個類。那么我們仿照前兩篇文章中介紹的聲明方法的方式:.method,來聲明我們的類。那么各位是不是已經想到了呢?對,我們直接使用“.class”指令來聲明一個類。
那么第二步呢?對,既然知道它是一個類了,那么我們顯然還可以指定關鍵字比如abstract、sealed、public、private或者interface等等,而在CIL中很多都是可以省略的,所以這里我們也就省略了。之后我們還需要給它起個名字來標識它,既然題目已經叫做《這個叫慕容小匹夫的類》了,那么我們的類的名字就叫做MurongXiaoPiFu。
之后第三步呢?對啊,貌似應該有個構造函數啊。但有時候我們寫C#代碼的時候,並不需要自己手動寫構造函數呀。可為什么到了CIL里就需要自己手動寫呢?因為C#的編譯器會自動為我們生成構造函數,而CIL的編譯器則不會。所以構造函數這個環節我們一定不要忘記。
所以類的聲明部分我們就寫出來了:
//MurongXiaoPiFu類的聲明 .class MurongXiaoPiFu { .method public void .ctor() { //TODO } }
OK,需要的三個步驟走完了。這里總結一下:
- .class指令標識我們聲明的是一個類
- 這個類的關鍵字和名字緊跟.class之后。這里小匹夫寫的類是MurongXiaoPiFu。
- 必須要寫構造函數”.method public void .ctor()“
好了,要寫構造函數這一點我們已經明確了。我們聲明完.ctor()之后,該如何實現它呢?那么我們首先考慮一下一個沒有參數的構造函數都可能要做一些什么。
匹夫在上一篇文章中詳細的介紹過CIL是如何使用堆棧的,包括值類型是如何使用堆棧以及引用類型是如何使用堆棧。此處的MurongXiaoPiFu類其實就是一個引用類型,所以呢?不錯,它需要把自己的引用壓棧。
那么還有呢?所有的引用類型都派生自System.Object,所以生成一個新的類實例其實是通過System.Object的構造函數來實現的。那么基於以上2點,我們的構造函數的實現也就十分清楚了:
//構造函數.ctor的實現 .method public void .ctor() { .maxstack 1 ldarg.0 //1.將實例的引用壓棧 call instance void [mscorlib]System.Object::.ctor() //2.調用基類的構造函數 ret }
但是小匹夫覺得寫到這里還有點不完整,還缺點啥呢?對啊,面向對象的三大法寶:封裝、繼承、多態嘛。其中繼承和多態都和所謂的繼承有很大的關系。所以說到類,不說繼承似乎有點不專業。為了不給各位留下匹夫不專業的印象,繼承還是不得不說。但是看匹夫你定義的這個類MurongXiaoPiFu貌似沒有用上繼承啊?
此言差矣。殊不知,MurongXiaoPiFu這個類默認是繼承自System.Object這個基類的。只不過當我們省略掉繼承的語句時,CIL的編譯器會自動將我們的類識別為從System.Object派生而來的,換言之,會被當做是引用類型。那么如果我們不省略繼承語句,我們的類該如何顯式地實現繼承呢?沒錯,用extend關鍵字。那么加上繼承自System.Object的語句(雖然這是多余的,但是為了演示繼承的用法這里還是很low的這樣寫了)之后,我們完整的聲明代碼如下:
//MurongXiaoPiFu類的聲明 .class MurongXiaoPiFu extends [mscorlib]System.Object //同樣System.Object來自mscorlib程序集 { //構造函數.ctor的實現 .method public void .ctor() { .maxstack 1 ldarg.0 //1.將實例的引用壓棧 call instance void [mscorlib]System.Object::.ctor() //2.調用基類的構造函數 ret } }
好了,行文至此,各位對CIL中如何聲明一個類應該就有了一個直觀的印象了吧。不過這還不夠啊,沒有類中的各個成員,這個類也就沒有什么實際的用途呀。所以接下來匹夫繼續完善這個叫慕容小匹夫的類,來介紹一下小匹夫自己,同時也為各位拜個年。
我的成員我做主
成員變量
那么具體到我們的類,它的成員變量無非就是介紹一個人所需要的信息。無非就是姓名(name),年齡(age),性別(sex),職業(job)這些咯。匹夫相信在C#中,各位都知道如何聲明一個成員變量。但是在CIL的世界中呢?應該如何聲明一個變量呢?
和聲明方法的.method指令類似,這里匹夫再引入一個指令.field用來聲明變量。同樣.field之后也可以加一些修飾符。
但是寫代碼對這些變量賦值之前,匹夫還是要強調一下CIL的執行都是依托於堆棧的,也就是要把堆棧記心中。這里我們需要用到stfld指令,這個指令是什么意思呢?簡短截說就是把棧中的數據彈出,並賦值給這個類實例中對應的字段。那么之前,顯然我們要將那個值先入棧,這樣才能出棧賦值給實例中的字段嘛。
所以咯,匹夫的基本信息就是這樣的了:
//聲明各個變量
.field public string name
.field public int32 age
.field public string sex
.field public string job
//為各個成員變量賦值 ldstr "陳嘉棟" stfld string MurongXiaoPiFu::name ldc.i4.s 0x19 //25歲 stfld int32 MurongXiaoPiFu::age ldstr "男人" stfld string MurongXiaoPiFu::sex ldstr "程序猿" stfld string MurongXiaoPiFu::job
成員函數
匹夫的基本資料有了,但是匹夫還想向大伙拜年和介紹自己呢啊。所以光有成員變量還是不夠的,我們還需要幾個成員函數。說到一個類的函數,首先要關注點什么呢?對啊,一個類中的函數究竟是靜態函數呢還是實例函數呢?所以接下來我們要去實現的函數既要包括靜態函數,還要有實例函數。當然,如果要全面一些,我們還要考慮進去虛函數。比如這三個函數:
- 問好(SayHi)靜態函數
- 介紹自己(Introduce)實例函數
- 拜年(HappyNewYear)實例函數,虛函數
同時,這兩個實例函數還會用到剛才定義的幾個成員變量,所以這里我們會引入另一個新的指令ldfld。這個指令是啥意思呢?別忘了堆棧額~所有被操作的數據都需要通過堆棧,所以各位明白了吧?這個指令就是將字段中值壓入堆棧中供之后使用的。
首先,我們使用static關鍵字實現靜態函數SayHi:
//問好,靜態函數 .method static void SayHi() {
.maxstack 1 ldstr "你好!" call void [mscorlib]System.Console::WriteLine(string)
ret }
然后,我們實現那兩個實例函數,與靜態函數使用static相反,實例函數使用instance關鍵字。不過在CIL中類的成員函數默認是實例函數,因此我們可以省略instance關鍵字。同時,由於是實例函數,因此在獲取實例的各個變量之前肯定要先知道該實例的引用。所以每一步取值,我們首先要”ldarg.0“將實例的引用壓棧,之后再使用”ldfld“去取值壓棧。
//實例函數介紹自己 .method void Introduce() { .maxstack 7 ldstr "我叫{0},今年{1}歲,是一個{2}" ldarg.0 ldfld string MurongXiaoPiFu::name ldarg.0 ldfld int32 MurongXiaoPiFu::age box int32 //對int型的年齡有一個裝箱 ldarg.0 ldfld string MurongXiaoPiFu::job call void [mscorlib]System.Console::WriteLine(string, object, object, object) ret } //實例函數,虛函數,過年好 .method public virtual void HappyNewYear() { .maxstack 1 ldstr "過年好" call void [mscorlib]System.Console::WriteLine(string) ret }
OK,這樣我們的成員函數就定義完畢了。
實例化之后給各位拜年
好啦,既然我們的慕容小匹夫的類已經定義好了。那么就讓我們來實例化一個實例出來給各位拜個年吧。
首先,我們還是使用第一篇文章中寫的第一個函數Fanyou來作為我們程序的入口,控制整個流程。那么我們需要在Fanyou函數中聲明一個局部變量來存放MurongXiaoPiFu這個類的實例引用。這個變量我們就叫做Murong吧。
.locals init ( class MurongXiaoPiFu Murong)
然后,我們需要實例化一個MurongXiaoPiFu類。所以此處匹夫要引入newobj指令了,newobj指令通過調用類的構造函數來創造一個新的實例。創造好實例之后,這個實例的引用還躺在棧中,所以為了給剛剛才聲明的變量Murong賦值,我們需要將棧中的實例引用彈出賦值給Murong,因此需要用到stloc。
newobj instance void class MurongXiaoPiFu::'.ctor'() stloc Murong
好了,到此我們已經將慕容小匹夫這個類實例化了。那么接下來呢?不錯,該調用相應的方法給各位拜年啦!
調用實例的方法
首先我們要調用我們定義的靜態函數SayHi。和上面定義成員函數不同而且也很有趣的一點,就是我們使用call指令默認調用的是靜態函數。所以我們調用SayHi就變得十分簡單了。
//調用靜態函數 call void MurongXiaoPiFu::SayHi()
而如果要調用實例函數,則要先將實例的引用壓棧,而且在call的后面還需要加上instance。所以我們調用Introduce和HappyNewYear需要分別指定它們的實例,也就是將實例的引用使用ldloc壓棧,之后call的時候還需要指明調用的是實例方法。(當然,如果各位有過使用工具將程序集反編譯成CIL代碼的經歷的話。在調用某個實例的方法的部分,各位十有八九看到的可能是callvirt而非call,這個話題本來小匹夫這篇文章也想討論來着,不過實在有點太晚了。所以留個坑,日后再專門討論)
//調用實例方法 ldloc Murong call instance void class MurongXiaoPiFu::Introduce() ldloc Murong callvirt instance void class MurongXiaoPiFu::HappyNewYear()
那么編譯,再執行的結果如圖。
好,其實寫到此處也算是一個good ending了。年也給大家拜了,CIL也和大家聊了。 那么~~~
如果各位看官覺得文章寫得還好,那么就容小匹夫跪求各位給點個“推薦”,謝啦~
裝模作樣的聲明一下:本博文章若非特殊注明皆為原創,若需轉載請保留原文鏈接(http://www.cnblogs.com/murongxiaopifu/p/4296151.html )及作者信息慕容小匹夫
附錄
.assembly extern mscorlib { .ver 4:0:0:0 .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. } .assembly 'HelloWorld' { } .method static void Fanyou() { .entrypoint .maxstack 4 .locals init ( class MurongXiaoPiFu Murong) newobj instance void class MurongXiaoPiFu::'.ctor'() stloc Murong call void MurongXiaoPiFu::SayHi() ldloc Murong call instance void class MurongXiaoPiFu::Introduce() ldloc Murong callvirt instance void class MurongXiaoPiFu::HappyNewYear() // ldstr "Hello World!" // call void [mscorlib]System.Console::WriteLine(string) ret } .method static void AddLife() { .maxstack 4 //局部變量 .locals init (int32 num1, int32 num2, int32 result) //第一個功能:顯示提示輸入加數,並獲取輸入的值 //在屏幕上顯示“請輸入第一個加數” ldstr "請輸入第一個加數" call void [mscorlib]System.Console::WriteLine(string) //獲取用戶的輸入值 call string [mscorlib]System.Console::ReadLine() //將輸入的字符串轉化成int call int32 [mscorlib]System.Int32::Parse(string) //值出棧,賦給局部變量num1 stloc num1 //num2 ldstr "請輸入第二個加數" call void [mscorlib]System.Console::WriteLine(string) call string [mscorlib]System.Console::ReadLine() call int32 [mscorlib]System.Int32::Parse(string) stloc num2 //第二個功能:相愛相殺,不對,應該是相愛相加... //將值從變量中壓入堆棧 ldloc num1 ldloc num2 //求和 add //將結果賦值給result stloc result //最后一個功能,關鍵的其實是裝箱 //顯示的格式 ldstr "{0} + {1} = {2}" //將num1,num2,result裝箱,供之后的writeLine使用。 ldloc num1 box int32 ldloc num2 box int32 ldloc result box int32 //將算式顯示出來 call void [mscorlib]System.Console::WriteLine(string, object, object, object) ret } //MurongXiaoPiFu類的聲明 .class MurongXiaoPiFu extends [mscorlib]System.Object //同樣System.Object來自mscorlib程序集 { .field public string name .field public int32 age .field public string sex .field public string job //構造函數.ctor的實現 .method public void .ctor() { .maxstack 10 //定義各個成員變量 ldarg.0 ldstr "陳嘉棟" stfld string MurongXiaoPiFu::name ldarg.0 ldc.i4.s 0x19 //25歲 stfld int32 MurongXiaoPiFu::age ldarg.0 ldstr "男" stfld string MurongXiaoPiFu::sex ldarg.0 ldstr "程序猿" stfld string MurongXiaoPiFu::job ldarg.0 //1.將實例的引用壓棧 call instance void [mscorlib]System.Object::.ctor() //2.調用基類的構造函數 ret } //問好,靜態函數 .method static void SayHi() { .maxstack 1 ldstr "你好!" call void [mscorlib]System.Console::WriteLine(string) ret } //實例函數介紹自己 .method void Introduce() { .maxstack 7 ldstr "我叫{0},今年{1}歲,是一個{2}" ldarg.0 ldfld string MurongXiaoPiFu::name ldarg.0 ldfld int32 MurongXiaoPiFu::age box int32 //對int型的年齡有一個裝箱 ldarg.0 ldfld string MurongXiaoPiFu::job call void [mscorlib]System.Console::WriteLine(string, object, object, object) ret } //實例函數,虛函數,過年好 .method public virtual void HappyNewYear() { .maxstack 1 ldstr "過年好" call void [mscorlib]System.Console::WriteLine(string) ret } }