CLR基础
CLR是供.NET应用程序使用的运行时环境,它在.NET应用程序和底层操作系统之间提供了一个操作层。
CLS公共语言规范,限制了命名约定、数据类型、函数类型以及某些其他元素,为不同的语言提供了一个公共标准。
在CLR环境下,.NET应用程序的抽象的中间表示包括了两个主要组件:元数据和托管代码。
元数据是应用程序的所有结构项(类、类的成员和特性、全局项等)的描述符以及它们的关系所组成的一套系统。托管代码表示了应用程序的方法(函数)的功能,它们以中间语言的抽象二进制形式进行编码,我们简称为IL。
简单示例
//---------程序头
.assembly extern mscorlib { }
.assembly OddOrEven { }
.module OddOrEven.exe
//---------类声明
.namespace Odd.or {
.class public auto ansi Even extends [mscorlib]System.Object {
//---------字段声明
.field public static int32 val
//---------方法声明
.method public static void check() cil managed {
.entrypoint
.locals init (int32 Retval) //初始化局部变量Retval,如果有多个则用逗号分开,例如:init(int32 a,string b)
AskForNumber:
ldstr "Enter a number"
call void [mscorlib]System.Console::WriteLine(string)
call string [mscorlib]System.Console::ReadLine()
ldsflda valuetype CharArray8 Format //载入valuetype CharArray8的静态字段Format的"地址",这个地址不是真实的地址或者说c/c++的指针,而是引用。IL有单独的指令载入实例和静态字段(ldfld和ldsfld)或他们的地址(ldflda和ldsflda)
ldsflda int32 Odd.or.Even::val
call vararg int32 sscanf(string,int8*,...,int32*)
stloc Retval //从栈上获取sscanf的调用结果存储至局部变量Retval中
ldloc Retval //将Retval压入栈
brfalse Error //从栈顶取出一项,如果该项为0则跳转至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
}//方法结束
}//类结束
}//命名空间结束
//全局项
.field public static valuetype CharArray8 Format at FormatData
//-------------数据声明
.data FormatData = bytearray(25 64 00 00 00 00 00 00) //%d
//--------------作为占位符的值类型
.class public explicit CharArray8 extends [mscorlib]System.ValueType {.size 8}
//--------------调用非托管方法
.method public static pinvokeimpl("msvcrt.dll" cdecl)
vararg int32 sscanf(string,int8*) cil managed {}
程序头
//---------程序头
.assembly extern mscorlib { auto }
.assembly OddOrEven { }
.module OddOrEven.exe
.assembly extern mscorlib { auto } 定义了一个名为AssemblyRef的元素据项,它标识了这个程序中使用的外部托管应用程序(程序集)。在这里使用的是Mscorlib.dll,这是.NET Framework类库的主程序集。
.assembly OddOrEven {} 定义了一个名为Assembly(程序集)的元数据项。
.module OddOrEven.exe定义了一个名为Module(模块)的元数据项,它标识了当前的模块。
类声明
//---------类声明
.namespace Odd.or {
.class public auto ansi Even extends [mscorlib]System.Object {
......
}
}
.namesace Odd.or { ... } 声明了命名空间。
.class public auto ansi Even extends [mscorlib]System.Object { ... } 定义了一个名为TypeDef(类型定义)的元数据项。
类的名称是Even。关键字public定义了类的可见性。auto定义了类的布局风格(自动布局,默认值),指出加载程序可以采用它认为合适的方式进行布局。ansi定义了类和其他非托管代码进行交互操作时,类中字符串转换的模式。
extends定义了类的父类。
字段声明
//---------字段声明
.field public static int32 val
定义了一个名为FieldDef(字段定义)的元数据项。
public和static定义了FieldDef的标识。public标志了可访问性,static表示这个字段是静态的。
方法声明
//---------方法声明
.method public static void check() cil managed {
.entrypoint
.locals init (int32 Retval)
......
}
定义了一个名为MethodDef(方法声明)的元数据项。
public和static定义了MethodDef的标识。
关键字void 定义了方法的返回类型。
cil和managed 定义了MethodDef中所谓的实现标识,指出了方法体是用IL表示的。使用本地代码而不是IL来表示的方法,会带有实现标志native unmanaged。
方法体通常包括三种项:指令、标注了指令的标号、伪指令。
.entrypoint将当前方法标识为应用程序(程序集)的入口点。
.locals init(int32 Retval)定义了当前方法的唯一一个局部变量。变量类型是int32 ,名称是Retval。
关键字init 表示这个局部变量将会在方法执行之前在运行期间被初始化。
AskForNumber:
ldstr "Enter a number"
call void [mscorlib]System.Console::WriteLine(string)
AskForNumber:是一个标号。标号不会被编译为元数据或IL,而只是在编译期间用于标识IL代码中特定的偏移量。
ldstr "Enter a number"这条指令从指定字符串常量创建了字符串对象,并把指向这个对象的引用载入到栈上。
call void [mscorlib]System.Console::WriteLine(string) 这条指令调用了.NET Framework类库的控制台输出方法。
call string [mscorlib]System.Console::ReadLine()
ldsflda valuetype CharArray8 Format
ldsflda int32 Odd.or.Even::val
call vararg int32 sscanf(string,int8*,...,int32*)
call string [mscorlib]System.Console::ReadLine() 这条指令调用了.NET Framework类库的控制台输入方法。没有从栈上取出任何值,并把一个字符串作为这次调用的结果放到栈上。
ldsflda valuetype CharArray8 Format 将类型为valuetype CharArray8的静态字段Format的地址载入。
ldsflda int32 Odd.or.Even::val这条指令加载了静态字段val的地址。
call vararg int32 sscanf(string,int8*,...,int32*) 这条指令调用了全局静态方法sscanf。这个方法从当前栈上取出三个项(Readline返回的字符串、对全局字段Format的引用、对字段val的引用),并把结果放回到栈上。
stloc Retval //从栈上获取sscanf的调用结果存储至局部变量Retval中
ldloc Retval //将Retval压入栈
brfalse Error //从栈顶取出一项,如果该项为0则跳转至Error标记
ldsfld int32 Odd.or.Even::val
ldc.i4 1
and
brfalse ItsEven
ldstr "odd!"
br PrintAndReturn
ldsfld int32 Odd.or.Even::val 将val的值加载到栈上。
ldc.i4 1这条指令将1加载到栈上。
指令and从栈上获取两个项,val和1,然后执行AND位操作并把结果放回到栈上。
brfalse ItsEven 从栈上获取一个项(AND位操作的结果),如果它是0,程序就会跳转到ItsEven这个标号上。如果val的值是偶数,那么前一个指令的结果就是0,否则就是1。
ldstr "odd!"这条指令将字符串"odd!"加载到栈上。
br PrintAndReturn这条指令触及不到栈,它会无条件跳转到PrintAndReturn标号上。
全局项
//全局项
.field public static valuetype CharArray8 Format at FormatData
声明了类型为valuetype CharArry8、名为Format的静态字段。
数据声明
//-------------数据声明
.data FormatData = bytearray(25 64 00 00 00 00 00 00) //%d
定义了带有符号FormatData的数据段。这个段有8字节的长度。这个数据段称为字节数组。
作为占位符的值类型
//--------------作为占位符的值类型
.class public explicit CharArray8 extends [mscorlib]System.ValueType {.size 8}
声明了没有成员的值类型,但是具有显式指定的大小——8个字节。声明这个值类型通常是为了声明“仅仅需要一块内存”。
调用非托管代码
//--------------调用非托管方法
.method public static pinvokeimpl("msvcrt.dll" cdecl)
vararg int32 sscanf(string,int8*) cil managed {}
声明了一个非托管方法,它是从托管代码中调用的。特性pinvokeimpl("msvcrt.dll" cdecl)指出这是一个非托管方法,要使用P/Invoke(平台调用)的机制调用,它还指出这个方法驻留于Msbcrt.dll中,并拥有调用约定cdecl。
这个方法需要两个必须的参数,类型为string和int8*(char*),返回int32类型的值。
vararg指定sscanf可以有任意数量任意类型的可选参数。