转自:http://www.cnblogs.com/Yahong111/archive/2007/08/16/857574.html
续上文【翻译】MSIL 教程(一) ,本文继续讲解数组、分支、循环、使用不安全代码和如何调用Win32 API
数组
本程序分配一个int型的数组并给他的元素赋值,然后打印出元素和数组的长度。
命令:
- newarr type— 生成一个元素类型为type 的数组。数组的大小必须在调用该命令前装入堆栈。该命令会把一个数组的引用装入堆栈。
- stelem.i4— 给一个数组成员赋值。数组的引用、下标和值必须在调用该命令前装入堆栈。
- ldelema type— 把数组元素的地址装入堆栈。数组的引用和下标必须在调用该命令前装入堆栈。地址用来调用非静态函数(参见后面)。
- ldlen—把数组的长度装入堆栈。数组的引用必须在调用该命令前装入堆栈。
- ldloca.s variable— 把变量的地址装入堆栈。
- ldc.i4.s value— 把一个Int32的常量装入堆栈(用于大于8位的数)。
- conv.i4— 把堆栈中值转换成Int32类型。
- call instance function(arguments)— 调用类的非静态函数。在调用一个非静态函数之前,我们必须把某个类的实例的地址和函数的参数装入堆栈。在本例中,地址通过ldelema和ldloca 命令装入。
在本例的某些代码片段中,我写了一些注释,以说明堆栈在最后一个变量后的状态。在本例中,我们看到变量由编译器生成,该变量用来调用类的非静态函数。
代码:
.assembly Array1 {} /* // This program works as C# code: int[] x = new int[5]; x[0] = 10; x[1] = 20; Console.WriteLine("x[0] = " + x[0].ToString()); Console.WriteLine("x[1] = " + x[1].ToString()); Console.WriteLine("Array length = " + x.Length.ToString()); */ .method static public void main() il managed { .entrypoint .maxstack 8 .locals init ([0] int32[] x, [1] int32 tmp) // 由编译器生成 // ***************************************************** // x = new int[5]; // ***************************************************** ldc.i4.5 // 把常量装入堆栈。 // 生成数组,并把他的引用压入堆栈 newarr [mscorlib]System.Int32 // 把数组从堆栈中取出,存入第0个局部变量中 stloc.0 // ***************************************************** // x[0] = 10; // ***************************************************** ldloc.0 // 把第0个局部变量装入堆栈(数组) ldc.i4.0 // 把常量0装入堆栈(下标) ldc.i4.s 10 // 把常量10装入堆栈(值) stelem.i4 // array[index] = value // 对数组的其余元素进行同样的操作…… // *************************************************** // Console.WriteLine("x[0] = " + x[0].ToString()); // *************************************************** ldstr "x[0] = " // 堆栈:"x[0] = " (堆栈由局部变量表示) ldloc.0 // 把第0个变量装入堆栈 ldc.i4.0 // 把第1个变量装入堆栈 // 堆栈: "x[0] = " -> x -> 0 // 把元素的地址装入堆栈 ldelema [mscorlib]System.Int32 // 堆栈: "x[0] = " -> 指向一个Int32的指针 // 10 // 调用实例函数System.Int32::ToString(). call instance string [mscorlib]System.Int32::ToString() // 堆栈: "x[0] = " -> "10" // 调用静态函数System.String::Concat(string, string) call string [mscorlib]System.String::Concat (string, string) // 堆栈: "x[0] = 10" // 调用静态函数 System.Console::WriteLine(string) call void [mscorlib]System.Console::WriteLine(string) // 堆栈: 空 //对数组的其余元素进行同样的操作…… // ***************************************************** // Console.WriteLine("Array length = " + x.Length.ToString()); // ***************************************************** ldstr "Array length = " // 堆栈: "Array length = " ldloc.0 // 把第0个变量装入堆栈 // 堆栈: "Array length = " -> x Ldlen // 把数组的长度装入堆栈 // 堆栈: "Array length = " -> 5 conv.i4 // 把栈顶的值转换为Int32,并把他装入堆栈 // 堆栈: "Array length = " -> 5 stloc.1 // 把刚才的值存入第1个局部变量(tmp) // 堆栈: "Array length = " ldloca.s tmp //把变量tmp的地址装入堆栈 // 堆栈: "Array length = " -> &tmp call instance string [mscorlib]System.Int32::ToString() // 堆栈: "Array length = " -> "5" call string [mscorlib]System.String::Concat (string, string) // 堆栈: "Array length = 5" call void [mscorlib]System.Console::WriteLine(string) // 堆栈: 空 ret }
比较
本程序读取2个数字并打印其最小值。
命令:
- bge.s label—跳转至label 如果value1≥value 2. Values 1和 2 必须在调用本命令前装入堆栈。
- br.s label—跳转至label。
- box value type— 把一个值类型转成一个Object,并把该Object的引用装入堆栈。
本程序的装箱由如下C#程序引起: Console.WriteLine("{0:d}", z);
用这种形式就不会引起装箱: Console.WriteLine(z.ToString());.
代码:
.assembly Compare {} /* int x, y, z; string s; Console.WriteLine("Enter x:"); s = Console.ReadLine(); x = Int32.Parse(s); Console.WriteLine("Enter y:"); s = Console.ReadLine(); y = Int32.Parse(s); if ( x < y ) z = x; else z = y; Console.WriteLine("{0:d}", z); */ .method static public void main() il managed { .entrypoint .maxstack 8 .locals init ([0] int32 x, [1] int32 y, [2] int32 z, [3] string s) // ***************************************************** // Console.WriteLine("Enter x:"); // ***************************************************** ldstr "Enter x:" // 把字符串装入堆栈 call void [mscorlib]System.Console::WriteLine(string) // ***************************************************** // s = Console.ReadLine(); // ***************************************************** call string [mscorlib]System.Console::ReadLine() stloc.3 // 保存到第3个变量 // ***************************************************** // x = Int32.Parse(s); // ***************************************************** ldloc.3 // 把第3个变量装入堆栈 call int32 [mscorlib]System.Int32::Parse(string) stloc.0 // 保存到第0个变量 // 对y进行相同的操作…… // ***************************************************** // 分支 // if ( x >= y ) goto L_GR; // ***************************************************** ldloc.0 // 把x装入堆栈(value 1) ldloc.1 // 把y装入堆栈(value 2) bge.s L_GR // 跳转到 L_GR 如果value1≥value2 // ***************************************************** // z = x // ***************************************************** ldloc.0 // 把第0个变量装入堆栈 stloc.2 // 保存到第2个变量 br.s L_CONTINUE // 跳转至 L_CONTINUE L_GR: // ***************************************************** // z = y // ***************************************************** ldloc.1 // 把第1个变量装入堆栈 stloc.2 // 保存到第2个变量 L_CONTINUE: // ***************************************************** // Console.WriteLine("{0:d}", z); // 注意:这一行引起装箱操作 // ***************************************************** ldstr "{0:d}" // 把字符串装入堆栈 ldloc.2 // 把第2个变量装入堆栈 (z) box [mscorlib]System.Int32 // 把Int32变为Object call void [mscorlib]System.Console::WriteLine(string, object) ret }
数组2(循环)
本程序用循环填充一个数组并打印其元素。这一次,我们增加一个静态函数ShowNumber(int), 它在main函数中调用。
命令:
- blt.s label—跳转到label 如果value 1小于 value 2. Values 1 和 2 必须在调用本命令之前装入堆栈。
- ldelem.i4— 把一个数组元素装入堆栈。数组引用和下标必须在调用本命令之前装入堆栈。
- ldarga.s argument— 把函数参数的地址装入堆栈。
我们可以看到,在本程序中,for 循环在MSIL中用标签来实现。
代码:
.assembly Array2 {} /* int[] px = new int[100]; int i; for ( i = 1; i < 100; i++ ) { px[i] = i + 1; } ShowNumber(px[5]); ShowNumber(px[10]); static void ShowNumber(int n) { Console.WriteLine(n.ToString()); } */ .method static public void main() il managed { .entrypoint .maxstack 8 .locals init ([0] int32[] px, [1] int32 i) // ***************************************************** // x = new int[100] // ***************************************************** ldc.i4.s 100 // 把常量装入堆栈 newarr [mscorlib]System.Int32 // 分配一个Int32型的数组 stloc.0 // 把它存入第0个变量 // ***************************************************** // i = 1 // ***************************************************** ldc.i4.1 //把常量装入堆栈 stloc.1 //把它存入第1个变量 br.s CHECK_COUNTER // 跳转到 CHECK_COUNTER START_LOOP: // ***************************************************** // px[i] = i + 1; // ***************************************************** ldloc.0 // 把第0个变量装入堆栈 // 堆栈: px ldloc.1 // 把第1个变量装入堆栈 //堆栈; px -> i ldloc.1 //把第1个变量装入堆栈 //堆栈: px -> i -> i ldc.i4.1 //把常量装入堆栈 //堆栈: px -> i -> i -> 1. add // 2个值相加 //堆栈: px -> i -> i+1 // (array,index,value) stelem.i4 // 把值存入数组元素 //堆栈[index] = value //堆栈: 空 // ***************************************************** // i = i + 1 // ***************************************************** ldloc.1 //把第1个变量装入堆栈 ldc.i4.1 //把常量装入堆栈 add // 相加 stloc.1 // 把值存入把第1个变量 CHECK_COUNTER: // ***************************************************** // 如果 i < 100 跳转到循环开始的地方 // ***************************************************** ldloc.1 // 把第1个变量装入堆栈 ldc.i4.s 100 // 把常量装入堆栈 blt.s START_LOOP // 如果value1<value2调转至START_LOOP // ***************************************************** // ShowNumber(px[5] // ***************************************************** ldloc.0 // 把第0个变量装入堆栈 // (array) ldc.i4.5 // 把常量装入堆栈 // (index) ldelem.i4 // 把数组元素装入堆栈 call void ShowNumber(int32) // 调用 ShowNumber // ***************************************************** // ShowNumber(px[10] // ***************************************************** ldloc.0 ldc.i4.s 10 ldelem.i4 call void ShowNumber(int32) ret } .method static public void ShowNumber(int32 n) il managed { .maxstack 1 ldarga.s n // 把第n个参数的地址装入堆栈 call instance string [mscorlib]System.Int32::ToString() call void [mscorlib]System.Console::WriteLine(string) ret }
不安全代码
本程序通过unsafe指针填充和打印一个int型数组。
在本程序中,我们将看到新的类型:int32* 和 int32&。使用关键字pinned 可以阻止GC移动由局部指针变量指向的对象。
命令:
- dup—在堆栈上复制一个值。
- stind.i4—存储值的地址。地址和值必须在调用本命令之前装入堆栈。
Code:
.assembly Unsafe {} /* int[] nArray = new int[5]; int i; int* pCurrent; fixed ( int* pArray = nArray ) { pCurrent = pArray; for ( i = 0; i < 5; i++ ) { *pCurrent++ = i + 1; } } for ( i = 0; i < 5; i++ ) { Console.WriteLine(nArray[i].ToString()); } */ .method static public void main() il managed { .entrypoint .maxstack 8 .locals ([0] int32[] nArray, [1] int32 i, [2] int32* pCurrent, [3] int32& pinned pArray) // GC不会移动该指针指向的对象 // ***************************************************** // nArray = new int[5]; // ***************************************************** ldc.i4.5 // 把常量5装入堆栈 newarr [mscorlib]System.Int32 // 生成数组 Int32[5] stloc.0 // 存入第0个变量 // ***************************************************** // pArray = nArray (pArray = &nArray[0]) // ***************************************************** ldloc.0 //把第0个变量装入堆栈(array) ldc.i4.0 //把常量0装入堆栈(index) ldelema [mscorlib]System.Int32 // 把array[index]装入堆栈 stloc.3 //存入第3个局部变量 // ***************************************************** // pCurrent = pArray; // ***************************************************** ldloc.3 //把第3个变量装入堆栈 conv.i // 转变为int stloc.2 //存入第2个变量 // ***************************************************** // i = 0 // ***************************************************** ldc.i4.0 //把常量0装入堆栈 stloc.1 //存入第1个变量 // ***************************************************** // 跳转到 CHECK_COUNTER // ***************************************************** br.s CHECK_COUNTER START_LOOP: // ***************************************************** // *pCurrent++ = i + 1 // ***************************************************** // 1) 保存pCurrent到堆栈,然后累加pCurrent ldloc.2 //把第2个变量装入堆栈 [pCurrent] dup // 复制栈顶的值 // [pCurrent pCurrent] ldc.i4.4 // 把常量4装入堆栈 [pCurrent pCurrent 4] add // 相加 [pCurrent pCurrent + 4] stloc.2 // 存入第2个变量 [pCurrent] // 译注:因为int型指针是4位的,所以加pCurrent+4==*pCurrent++ // 2) 把 (i+1) 保存到pCurrent ldloc.1 // 把第1个变量装入堆栈 [pCurrent i] ldc.i4.1 //把常量1装入堆栈 [pCurrent i 1] add // 相加 [pCurrent i+1] // 地址 值 stind.i4 // 把i+1的值的地址存入pCurrent [empty] // ***************************************************** // i = i + 1 // ***************************************************** ldloc.1 // 把第1个变量装入堆栈 ldc.i4.1 // 把常量1装入堆栈 add // 相加 stloc.1 // 存入第1个变量 CHECK_COUNTER: // ***************************************************** // 如果i < 5 跳转至 START_LOOP; // ***************************************************** ldloc.1 // 把第1个变量装入堆栈 ldc.i4.5 // 把常量5装入堆栈 blt.s START_LOOP // 如果i<5跳转至START_LOOP // ***************************************************** // pArray = 0 fixed 块结束 // ***************************************************** ldc.i4.0 // 把常量0装入堆栈 conv.u // 转变为unsigned int,并压入堆栈 stloc.3 // 存入第3个变量 // 打印数组元素…… ret }
PInvoke
本程序使用Win32 API GetComputerName 和 MessageBox 显示计算机的名字。API的MSIL声明形式如下:
.method public hidebysig static pinvokeimpl("kernel32.dll" autochar winapi) int32 GetComputerName( class [mscorlib]System.Text.StringBuilder marshal( lptstr) buffer, int32& size) cil managed preservesig { } .method public hidebysig static pinvokeimpl("User32.dll" autochar winapi) int32 MessageBox(native int hWnd, string marshal( lptstr) lpText, string marshal( lptstr) lpCaption, int32 uType) cil managed preservesig { }
其调用规则与其他函数一致。