微软中间语言 (MSIL) 是一种语言,是许多编译器 (C#,VB.NET 等 ) 的输出 . ILDasm ( 中间语言反汇编器 ) 程序和 .Net Framework SDK(FrameworkSDK/Bin/ildasm.exe) 打包在一起,让用户以人可阅读的格式查看 MSIL 代码。通过该工具,我们可以打开任何 .net 可执行文件 (exe 或 dll) 并查看其 MSIL 代码。 ILAsm 程序 ( 中间语言编译器 ) 从 MSIL 语言生成可执行文件。我们可以在 WINNT / Microsoft.NET /Framework /vn.nn.nn 目录中找到这个程序。 许多Visual C++ 程序员开始.net 开发是因为他们对.NET 框架的底层发生了一些什么感兴趣。学习MSIL 给了用户理解某些对C# 程序员或VB.NET 程序员来说是透明的东西的机会。通晓MSIL 给.NET 程序员更多的能力。我们从不需要直接用MSIL 编写程序,但是在某些情况下是非常有用的,我们可以用ILDasm 打开程序的MSIL 代码,查看它到底做了一些什么。 一个 Doc 格式的 MSIL 参考对 .NET 开发人员来说比较有用,它也许可以在 Framework SDK 目录下找到: * FrameworkSDK/Tool Developers Guide/docs/Partition II Metadata.doc ( 元数据定义和术语) . 在这个文件中,我发现了所有MSIL 指令的说明,例如.entrypoint,.locals 等. * FrameworkSDK/Tool Developers Guide/docs/Partition III CIL.doc (CIL 命令集) 包含了一个MSIL 命令的完整列表。 在工作中,我也用到了一个MSDN 的ILDAsm 教程,一篇2001 年5 月由 John Robbins 发表在MSDN 杂志的优秀的文章: "ILDASM is Your New Best Friend" 。 我想学习一门语言最好的途径就是用它写一些程序,所以我决定写一些小的 MSIL 程序。实际上,我们有写这些代码——是 C# 编译器生成的,我只是做一了一些小的更改,并加了许多注释以描述 MSIL 是如何工作的。 通过阅读附在本文的例子可以帮助.NET 程序员理解中间语言,帮助其在需要的时候更易读懂MSIL 代码。 一般信息 在 MSIL 中,所有的操作都在栈上完成。当调用一个函数的时候,其参数和局部变量都被分配到栈上。函数的代码从该栈开始,把一些值压入栈,对这些值进行一些操作,从栈上取出值。 执行 MSIL 名利和函数由 3 个步骤完成: 1. 把命令操作数和函数参数压入栈。 2. 执行命令或者调用函数。命令或函数从栈中取出他们的操作数(参数)并把他们压入结果栈 (返回值)。 3. 从栈中读取结果值。 步骤 1~3 是可选的,例如, void 函数不会压入一个结果值到栈。 栈包含值类型对象和引用类型对象的引用。引用类型对象本身保存在堆中。 用来把一个值压入栈中的MSIL 命令是ld... (装载),用来从栈中取出值的命令是st... (存储),因为值都存在变量中。我们可以把入栈操作叫做装载,出栈操作叫做存储。 示例项目 本文附上的代码中包含了许多用 MSIL 写的控制台程序 . 如果需要编译他们,请确定 ILAsm 程序可以通过 PATH 访问。每个项目都是一个 Visual Studio 解决方案, IL 源文件可以用 VS 的文本编辑器打开, Build 命令运行 ILAsm 程序在项目所在目录生成 exe 文件, run 命令执行该文件。在每个程序的末尾,我加了几行代码,他们可以用 C# 来写: Console.WriteLine("Press Enter to continue"); Console.Read(); 这样,当从 Windows Explorer 运行的时候,就可以看到程序的输出。 下面是所含项目的列表: 1. 打印字符串 — 打印字符传到控制台。 2. 赋值 — 给一个int 变量赋值并把它打印到控制台。 3. 运算 — 从控制台读取2 个数字,惊醒+ ,- 和乘的操作,并显示结果。 4. 数组 — 分配一个int 类型的数组,给他的元素赋值,打印其元素和数组的长度。 5. 比较 — 输入2 个数字并打印出最小的那个。 6. 数组2 — 用循环填充数组元素并打印某些元素。 7. 不安全代码 — 使用unsafe 指针访问数组元素。 8. PInvoke — 调用Win32 API 。 9. 类 — 和类一起工作。 10. 异常 — 异常处理。 我假设你以在这所说的顺序阅读这些项目。在下面的项目描述中,我用程序来解释每一条MSIL 命令,并给出一些代码片段。 打印字符串 PrintString 就是MSIL 版的 Hello,World 在代码中用到的MSIL 指令如下: l .entrypoint — 定义程序的入口点(该函数在程序启动的时候由 .NET 运行库调用) l .maxstack — 定义函数代码所用堆栈的最大深度。 C# 编译器可以对每个函数设置准确的值, 在例子中,我把他设为 8 。 用到的MSIL 命令如下: * ldstr string — 把一个字符串常量装入堆栈。 * call function(parameters) — 调用静态函数。函数的参数必须在函数调用前装入堆栈。 * pop — 取出栈顶的值。当我们不需要把值存入变量时使用。 * ret — 从一个函数中返回。 调用静态函数很简单。我们把函数的参数压入堆栈,调用函数,然后从堆栈中读取函数的返回值(如果是非 void 函数)。 Console.WriteLine 就是一个这样的函数。 下面是代码: .assembly PrintString {} /* Console.WriteLine("Hello,World)" */ .method static public void main() il managed { .entrypoint // 该函数是程序的入口 .maxstack 8 // ***************************************************** // Console.WriteLine("Hello,World)"; // ***************************************************** ldstr "Hello,World" // 把字符串压入堆栈 // 调用静态的System.Console.Writeline 函数 // ( 函数移除栈顶的字符串) call void [mscorlib]System.Console::WriteLine ( class System.String) // ***************************************************** ldstr "Press Enter to continue" call void [mscorlib]System.Console::WriteLine ( class System.String) // 调用 System.Console.Read 函数 call int32 [mscorlib]System.Console::Read() // pop 指令移除栈顶元素 // ( 移除由Read() 函数返回的数字 pop // ***************************************************** ret } 赋值 该程序给一个变量赋与int 值并把它打印到控制台窗口。 命令: * ldc.i4. n — 把一个 32 位的常量(n 从0 到8 )装入堆栈 * stloc. n — 把一个从堆栈中返回的值存入第n (n 从0 到8 )个局部变量 代码: .assembly XequalN {} // int x; // x = 7; // Console.WriteLine(x); .method static public void main() il managed { .entrypoint .maxstack 8 .locals init ([0] int32 x) // 分配一个局部变量 // ***************************************************** // x = 7; // ***************************************************** ldc.i4.7 // 把常量装入堆栈 stloc.0 // 把堆栈中的值存入第0 个变量 // ***************************************************** // Console.WriteLine(x); // ***************************************************** ldloc.0 // 把第0 个变量转入堆栈 call void [mscorlib]System.Console::WriteLine(int32) ret } 数据运算 本程序从控制台读取2 个数字,对它们进行简单的运算,然后显示结果。 命令: * add —2 个值相加。命令的参数必须在调用前装入堆栈,该函数从堆栈中移除参数并把运算后的结果压入堆栈。 * sub — 2 个值相减。 * mul — 2 个值相乘。 代码片段: .assembly Operations {} /* // 程序的C# 代码: 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); z = x + y; Console.Write("x + y = "); Console.Write(z); Console.WriteLine(""); z = x - y; Console.Write("x - y = "); Console.Write(z); Console.WriteLine(""); z = x * y; Console.Write("x * y = "); Console.Write(z); Console.WriteLine(""); */ .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 个变量装入堆栈 // 调用 System.Int32::Parse(string) 函数 // 把字符串从堆栈中移除并把解析的结果——int 值压入堆栈 call int32 [mscorlib]System.Int32::Parse( string ) stloc.0 // 把值存入第0 个变量 // ***************************************************** // 和变量y 的一些运算 // ***************************************************** ldstr "Enter y:" // 装入字符串 call void [mscorlib]System.Console::WriteLine( string ) // 调用 call string [mscorlib]System.Console::ReadLine() // 调用 stloc.3 // 把值存入第3 个变量 ldloc.3 // 把第3 个变量装入堆栈 call int32 [mscorlib]System.Int32::Parse( string ) // 调用 stloc.1 // 把值存入第1 个变量 // ***************************************************** // z = x + y; // ***************************************************** ldloc.0 // 把第0 个变量装入堆栈 ldloc.1 // 把第1 个变量装入堆栈 // 把这2 个值从堆栈中移除,把结果压入堆栈 add stloc.2 // 把值存入第2 个变量 // ***************************************************** // Console.Write("x + y = "); // ***************************************************** ldstr "x + y = " // load string onto stack call void [mscorlib]System.Console::Write( string ) // ***************************************************** // Console.Write(z); // ***************************************************** ldloc.2 // 把第2 个变量装入堆栈 call void [mscorlib]System.Console::Write(int32) // ***************************************************** // Console.WriteLine(""); // ***************************************************** ldstr "" // 装入字符串 call void [mscorlib]System.Console::WriteLine( string ) // 相减和相乘运算过程与上面相同 ret }