《Expert .NET 2.0 IL Assembler》 第一章 简单示例 1.2 简单示例(一)

返回目录

 

简单示例: The Code

     下面这个示例并不是“Hello ,World!”。这个示例是一个简单的托管控制台应用程序,先提示用户输入一个整数,然后识别出这是一个奇数还是偶数。当用户输入的不是一个整数时,应用程序会响应“How rude!”并终止。(访问Apress网站:http://www.apress.com/.,获取源文件Simple.il

     这个示例,如列表1-1所示,使用了托管的.NET Framework类库的控制台的API来负责控制台的输入和输出,同时它使用了非托管的函数sscanf,这条语句来自C语言的运行期类库,用于将输入的字符串转换为一个整数。

     注意:为了增加本书的可读性,所有的ILAsm关键字,在代码清单中都表示为粗体。

 

清单1-1.OddOrEven 示例应用程序

// ----------- Program header
.assembly   extern  mscorlib {  auto  }
.assembly  OddOrEven  { }
.module  OddOrEven.exe
// ----------- Class declaration
.namespace  Odd.or {
     
.class   public   auto   ansi  Even  extends  [mscorlib]System.Object {
// ----------- Field declaration
          
.field   public  static  int32  val
// ----------- Method declaration
          
.method   public  static  void  check( )  cil   managed  {
               
.entrypoint
               
.locals   init ( int32  Retval)
          
AskForNumber:
               
ldstr   " Enter a number "
               
call   void  [mscorlib]System.Console:: WriteLine( string )
               
call   string  [mscorlib]System.Console::ReadLine ()
               
ldsflda  valuetype CharArray8 Format
               
ldsflda   int32  Odd.or.Even::val
               
call  vararg  int32  sscanf( string int8 *,  int32 *)
               
stloc  Retval
               
ldloc  Retval
               
brfalse  Error
               
ldsfld   int32  Odd.or.Even::val
               
ldc.i4   1
               
and
               
brfalse  ItsEven
               
ldstr   " odd! "
               br PrintAndReturn
          
ItsEven:
               
ldstr   " even! "
               br PrintAndReturn
          
Error:
               
ldstr   " How rude! "
          
PrintAndReturn:
               
call   void  [mscorlib]System.Console::WriteLine( string )
               
ldloc  Retval
               
brtrue  AskForNumber
               
ret
          
//  End of method
     
//  End of class
//  End of namespace
//
----------- Global items
.field   public  static valuetype CharArray8 Format at FormatData
// ----------- Data declaration
.data  FormatData = bytearray( 25   64   00   00   00   00   00   00 //  % d . . . . . .
//
----------- Value type as placeholder
.class   public  explicit CharArray8
                    
extends  [mscorlib]System.ValueType { .size  8   }
// ----------- Calling unmanaged code
.method   public  staticpinvokeimpl( " msvcrt.dll "  cdecl)
     vararg 
int32  sscanf( string int8 *)  cil   managed { }

在接下来的章节,我将会带你逐行分析这些代码。

 

程序头

以下是OddOrEven应用程序的程序头:

.assembly   extern  mscorlib{  auto  }
.assembly  OddOrEven  { }
.module  OddOrEven.exe

 

.assembly extern mscorlib { auto }
     这一句定义了一个名为Assembly Reference(又名AssemblyRef)的元数据项,识别了在这个程序中的外部托管应用程序(程序集)。在这个例子中,外部应用程序是Mscorlib.dll,这是.NET Framework类库的主程序集。(关于.NET Framework类库本身的话题超过了本书的范围;为获取更多信息,请参考.NET Framework类库,作为ECMA国际ISO标准的第四部分发布的详细规范。)
     Mscorlib.dll程序集包括了所有基类的声明,其它所有子类都从中派生。虽然理论上你可以写一个应用程序而不使用派生于Mscorlib.dll的任何子类,但是我对这样的应用程序有什么用处而持怀疑态度。(一个明显的例外就是Mscorlib.dll本身。)因此,在ILAsm的程序的开始,带有一个指向Mscorlib.dll的AssemblyRef声明,这是一个好的习惯。随后是指向其它AssemblyRef的声明——如果有的话。
     AssemblyRef声明的范围(在两个大括号之间)可以包括额外的信息来识别引用到的程序集,例如文化(culture,之前被称为locale)的版本。因为这样的信息与理解本文这个示例不相关,我就只说到这里。(第5章会详细描述这些信息。)替代地,我使用了关键字:auto,这个关键字会告诉ILAsm自动侦测到被引用程序集的最新版本。
     注意到,自动侦测程序集的特性,只适用于ILASM2.0以及更新的版本。版本1.0和1.1并没有这个特性,但是他们(只)可以指向Mscorlib.dll并携带额外的识别信息。因此当使用ILASM的老版本时,只需要保持AssemblyRef的范围为空。
还要注意到,虽然代码引用到了Mscorlib.dll程序集,AssemblyRef只是由文件名声明的,而不需要扩展名。如果包括了扩展名,就会引起加载器寻找Mscorlib.dll.dll或者Mscorlib.dll.exe,导致一个运行期错误。

.assembly OddOrEven { }
     这一句定义了一个名为Assembly的元数据项,令人吃惊的是,它可以识别当前应用程序(程序集)。此外,你可以在程序集声明中包括额外的信息来识别程序集,但是在这里是不必要的。(参见第6章获取更多详情。)和AssemblyRef一样,程序集是通过他的文件名来识别的,而不需要扩展名。
     为什么需要将一个应用程序识别为一个程序集呢?如果你不需要,那么它就可以不是一个应用程序,而是一个次要的模块,作为其它应用程序(程序集)的一部分,同时也就不能独立的执行。为这样的模块添加一个.exe扩展名并不会改变什么,只有程序集会被执行。

.module OddOrEven.exe
     这一句定义了一个名为Module的元数据项,识别了当前的模块。每一个模块,无论是主要的或者次要的,都会在它的元数据中携带识别信息。注意到这个模块通过他的全文件名来识别,包括扩展名。路径,是不可以包括在内的。

 

类声明

以下是OddOrEven应用程序的类声明:

.namespace  Odd.or {
     
.class   public   auto   ansi  Even  extends  [mscorlib]System.Object {
          
     }
     
}

 

.namespace Odd.or { … }
     这一句声明了一个命名空间。命名空间并不代表一个单独的元数据项,而是一个声明在命名空间声明这个范围的所有的类的完整名称的公共的前缀。

.class public auto ansi Even extends [mscorlib]System.Object { ... }
     这一句定义了一个名为Type Definition(又名TypeDef)的元数据项。每一个定义在当前模块的类、结构或枚举都在元数据中有相应的TypeDef记录。类的名称是Even。因为它们声明在命名空间Odd.or中,它的完整名称(可以通过这个完整名称,将其在别的地方引用,而加载器也是据此来识别一个类)是Odd.or.Even。你可以放弃命名空间的声明和只通过它的完整名称来声明类;这是没有区别的。

     关键字public auto和ansi定义了TypeDef项的标记。

     关键字public,定义了类的可见性,意味着这个类在当前程序集的外部是可见的。(类的可见性的另一个关键字是private,是默认值,这意味着这个类是只能在内部使用的而不能被外界引用到。)

    关键字auto,在这个上下文中,定义了类的外观样式(默认值automatic),指示加载器为这个类安排最适合的布局。有两种选择:sequential(保持字段的指定顺序)和explicit(显示地详细说明每个字段的偏移量,给加载器下达精确的指令对类进行布局)。

    关键字ansi定义了在与非托管代码进行互操作时,在类中进行字符串转换的模式。这个关键字,默认地指定了字符串将会在常规C语言样式——由字节组成的字符串之间进行转换。可选关键字还有unicode(字符串在UTF-16编码间转换)和autochar(底层平台决定字符串转换的模式)。

     extends [mscorlib]System.Object子句定义了Odd.or.Even的父类(或基类)。这行代码表示了一个名为Type Reference(又名TypeRef)的元数据项。这个特别的TypeRef的命名空间,具有命名空间System和名称Object,并且以mscorlib这个AssemblyRef作为名称的解析范围。每个定义在当前模块外部的类都将地址记录在TypeRef中。你也可以使用TypeRef替代TypeDef,来记录这些定义在当前模块内部类的地址,除了不是很美观,基本可以认为是没有差异的。

     默认地,所有的类都派生于定义在Mscorlib.dll程序集中的System.Object类。只有System.Object本身和接口没有基类,这将在第7章进行说明。

     结构——在.NET术语中称作“值类型”,派生于[mscorlib] System.ValueType这个类。枚举则派生于[mscorlib] System.Enum这个类。因为这两种独特的TypeDef类型被认为是独立于扩展出它们的类,所以每次声明一个值类型或者枚举的时候,必须使用extends子句。

     你可能已经注意到,示例中TypeDef的声明包括了三种默认项:auto标记、ansi标记和extend子句。实际上,我可能已经声明了与.class public Even { ... }相同的TypeDef,但是接下来我还不能讨论这个TypeDef标记以及extends子句。

     最后,我必须强调一个重要的事实——关于在ILAsm中类的声明。(请注意,不要说我没有提醒过哦!)一些语言要求类的所有的特性和成员定义在这个类的词法范围内,将一个类作为一个整体定义在一个地方。而在ILAsm中,类可以分散定义在不同地方。

     在ILAsm中,你可以声明一个TypeeDef,带有它的一些特性和成员,关闭这个TypeeDef的范围,并随后再次打开这个TypeeDef,在其中声明更多它的特性和成员。这个技术称为“类的修正”(class amendment)。

     包包译注:“类的修正”(class amendment),就是部分类,在C#中表现为partial这个关键字。

    当你修正一个TypeDef的时候,extends子句和implements子句都会被忽略(在这个示例中不予讨论)。你应该在第一次声明TypeDef的时候,定义它的这些特征。

     这里对TypeDef修正的次数和没有限制,对TypeDef声明可能跨越的源文件数量也没有限制。然而,你只能在一个模块中完全定义一个TypeDef。因此,在其他程序集中,或者在同一程序集中的其他模块中,对TypeDef修正的,都是不可以的。

     第7章对ILAsm类声明提供了更细节的信息。

 

使用Pseudoflages声明值类型和枚举

     你可能想了解一些“欺骗”从而允许你绕开重复extend子句的必要性。ILAsm有两个关键字,valueenum,可以放置在类标记之中,从而当你忽略extends子句时,各自识别值类型和枚举。(如果你包括了extends子句,这两个关键字将会被忽略。)。当然,这并不是一个合适的方式来表示元数据,因为它给出了一个不正确的印象:值类型和枚举是通过某些TypeDef标记来识别的。我很遗憾ILAsm中存在这么低级的“诡计”,但是我的懒惰以至于不想反复敲出extends子句。ILDASM从不依赖这些“欺骗”,而总是如实地打印出extends子句,这也是ILDASM具有成为一款软件工具的优势。

 

你可能感兴趣的:(.net)