(翻译) 《C# to IL》第一章 IL入门

本文翻译自《C# to IL》第一章,介绍了IL的基础知识。IL是.NET框架中所有语言编译的目标,理解IL有助于理解.NET技术。通过示例程序演示了IL的编译和执行过程,包括函数、入口点、.NET程序集概念,并讨论了IL指令如.method、.entrypoint、.ret等。最后,通过对比C#和VB.NET的编译结果,强调了IL带来的语言无关性。
摘要由CSDN通过智能技术生成

(翻译) 《C# to IL》第一章 IL入门

clip_image002

-1-

      我们用C#、VB.NET语言编写的代码最终都会被编译成程序集或IL。因此用VB.NET编写的代码可以在C#中修改,随后在COBOL中使用。因此,理解IL是非常有必要的。

      一旦熟悉了IL,理解.NET技术就不会有障碍了,因为所有的.NET语言都会编译为IL。IL是一门中性语言。IL是先发明的,随后才有了C#、VB.NET等语言。

      我们将在一个短而精辟的程序中展示IL。我们还假设读者至少熟悉一门.NET语言。

a.il

.method   void  vijay() 
{

}

      随后,我们用IL编写了一个非常短小的IL程序——它显然是不能工作的,并将它命名为a.il。那么我们怎么才能把它编译为一个可执行程序呢?不需要为此而焦急,Microsoft提供了一个ilasm程序,它的唯一任务就是从IL文件中创建可执行文件。

      在允许这个命令之前,要确保你的变量路径被设置为framework中的bin子目录。如果不是,请输入命令如下:

            set path=c:/progra~1/microsoft.net/frameworksdk/bin;%PATH%

      现在,我们使用如下命令:

            c:/il>ilasm /nologo /quiet a.il

      这样做会生成下面的错误:

            Source file is ANSI

            Error: No entry point declared for executable

            ***** FAILURE *****

      将来,我们将不会显示由ilasm生成的输出的第一行和最后一行。我们还将移除非空白行之间的空白行。

      在IL中,允许我们使用句点.作为一行的开始,这是一条指令,要求编译器执行某个功能,如创建一个函数或类,等等。任何开始于句点的语句都是一条实际俄编译器指令。

      .method表示创建一个名为vijay的函数(或方法),并且这个函数返回void,即它不返回任何值。因为缺少较好的命名法则,函数名称vijay显得很随意。

      汇编器显然理解不了这个程序,从而会显示“no entry point”的消息。这个错误信息的生成是因为IL文件能够包括无数的函数,而汇编器无法区分哪个会被首先被执行。

      在IL中,首先被执行的函数被称为进入点(entrypoint)函数。在C#中,这个函数是Main。函数的语法是,名称之后是一对圆括号()。函数代码的开始和结束用花括号{}来表示。

a.il

.method   void  vijay() 
{
      .entrypoint
}

      c:/il>ilasm /nologo /quiet a.il

      Source file is ANSI

      Creating PE file

      Emitting members:

      Global Methods: 1;

      Writing PE file

      Operation completed successfully

      现在不会生成任何错误了。伪指令(directive)entrypoint表示程序执行必须开始于这个函数。在这个例子中,我们不得不使用这个伪指令,虽然事实上这个程序只有一个函数。当在DOS提示符中给出dir命令后,我们看到有3个文件会被创建。a.exe是一个可执行文件,现在可以执行它来看到程序的输出。

      C:/il>a

      Exception occurred: System.BadImageFormatException: Exception from HRESULT: 0x8007000B. Failed to load C:/IL/A.EXE.

      当我们试图执行上面的程序时,我们的运气似乎不太好,因为会生成上面的运行时错误。一个可能的原因是,这个函数是不完整的,每个函数都应当具有一个“函数结束”指令在函数体中。我们匆忙之中显然没有注意到这个事实。

a.il

.method   void  vijay() 
{
      .entrypoint
      ret
}

      “函数结束”指令被称为ret。前面所有的函数都必须以这个指令作为结束。

Output

      Exception occurred: System.BadImageFormatException: Exception from HRESULT: 0x8007000B. Failed to load C:/IL/A.EXE.

      在执行这个程序时,我们再次得到了相同的错误。这次我们的问题又在哪里呢?

a.il

.assembly  mukhi {}
.method   void  vijay() 
{
      .entrypoint
      ret
}

      错误在于我们忘记在名称后面使用必不可少的伪指令assembly。我们将其合成在上面的代码中,并在一对空的花括号之后使用了名称mukhi。这个程序集伪指令用于给出程序的名称。它又被称为一个部署单元。

      上面的代码是可以汇编而没有任何错误的最小的程序,虽然它在执行时并没有做什么有用的事情。它没有任何名为Main的函数。它只有一个带有entrypoint伪指令的函数vijay。现在汇编这个程序并运行而根本不会有任何错误。

      在.NET中,程序集的概念是极其重要的,应该对其有彻底的认识。我们将在本章后半部分使用这个伪指令。

a.il

.assembly  mukhi {}
.method   void  vijay() 
{
      .entrypoint
      ret
}
.method   void  vijay1() 
{
      .entrypoint
      ret
}

Error

      ***** FAILURE *****

      上面错误信息的原因是,上面的程序有2个函数,vijay和vijay1,每个函数都包括了.entrypoint伪指令。正如前面提到的那样,这个指令指定了关于那个函数会被首先执行。

      因此,在功能上,它类似于C#中的Main函数。当C#代码被转换为IL代码时,在Main函数中包含的代码会被转换为IL中的函数中并包括.entrypoint伪指令。例如,如果在COBOL程序中执行的第一个函数被称为abc,那么在IL中生成的代码就会在这个函数中插入.entrypoint伪指令。

      在常规的程序语言中,首先被执行的函数必须有一个特定的名称,例如Main,但是在IL中,只需要一个.entrypoint伪指令。因此,因为一个程序只能由一个开始点,所以在IL代码中只允许一个函数包括.entrypoint伪指令。

      迫切地看到,没有生成任何错误消息编号或说明,使得调试这个错误非常困难。

a.il

.assembly  mukhi {}
.method   void  vijay() 
{
      ret
      .entrypoint
}

      .entrypoint伪指令需要被定位为函数中的第一个指令或最后一个指令。它仅出现在函数体中,从而将它的状态宣布为第一个被执行的函数。伪指令不是程序集指令,甚至可以被放置在任何ret指令之后。提醒你一下,ret表示函数代码的结束。

a.il

.assembly  mukhi {}
.method   void  vijay() 
{
      .entrypoint
      call   void  System.Console::WriteLine()
      ret
}

      我们可能有一个用C#、VB.NET编写的函数,但是在IL中执行这个函数的机制是相同的。如下所示:

      我们必须使用汇编指令调用。调用指令之后,按照给定的顺序,为以下详细内容:

  •  
    • 函数的返回类型(void)
    • 命名空间(System)
    • 类 (Console)
    • 函数名称 (WriteLine())

      函数被调用但不会生成任何输出。因为,我们传递一个参数到WriteLine函数中。

a.il

.assembly  mukhi {}
.method   void  vijay() 
{
      .entrypoint
      call   void  System.Console::WriteLine( class  System.String)
      ret
}

      上面的代码有一处“闪光点”。当一个函数在IL中被调用时,除了它的返回类型之外,被传递的参数的数据类型,也必须被指定。我们将Writeline设置为——希望得到一个System.String类型作为参数,但是由于没有字符串被传递到这个函数中,所以它会生成一个运行时错误。

      因此,在调用一个函数时,在IL和其他程序语言之间有一个明显的区别。在IL中,当我们调用一个函数,我们必须指定关于该函数我们所知道的任何内容,包括它的返回类型和它的参数的数据类型。通过在运行期间进行恰当的检查,保证了汇编器能够在语法上验证代码的有效性。

      现在我们将看到如何将参数传递到一个函数中。

a.il

.assembly  mukhi {}
.method   void  vijay() 
{
      .entrypoint
      ldstr   " hell "
      call   void  System.Console::WriteLine( class  System.String)
      ret
}

Output

      hell

      汇编器指令ldstr把字符串放到栈上。Ldstr的名称是文本"load a string on the stack"的缩写版本。栈是一块内存区域,它用来传递参数到函数中。所有的函数从栈上接收它们的参数。因此,像ldstr这样的指令是必不可少的。

a.il

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值