既然了解了CIL是什么、长什么样子,那么下面就让我们写出第一个CIL语言的程序吧!无论哪种语言,入门程序都是输出Hello World,那么我们就用CIL实现一个输出Hello World的小程序。
我是在Windows系统中使用Notepad++来写CIL代码的,CIL文件的后缀名是.il,所以我们先新建一个名为HelloWorld.il的文本文件,然后就能在HelloWorld.il文件中编写CIL代码了。
与C#不同,CIL并不要求方法必须属于某个类,也不要求类必须属于某个命名空间,所以我们无需定义一个类,只需要声明一个主函数即可。在CIL中我们管这种函数叫做”entrypoint”,即入口函数,只要定义为entrypoint,函数名叫不叫Main无所谓,为了演示这一点,我们的函数名就叫做Show。
.method public static void Show()
{
.entrypoint
.maxstack 1
ldstr "Hello World"
call void [mscorlib]System.Console::WriteLine(string)
call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
ret
}
上面就是我定义的Show方法,它和一般的语言一样,包括方法签名和方法体。但在CIL语言中,方法的定义有以下需要注意的地方:
- 方法的定义以.method作为标识,方法可以属于某个类,也可以不属于某个类。
- 和C#一样,CIL程序的入口也必须是静态的,也就是意味着调用这个入口函数并不需要某个类的实例。当然,需要使用static关键字来标识。
- 程序的入口以.entrypoint作为标识,它表明了该方法是CIL程序的入口,程序的入口只能有一个。
- .maxstack表明了预计使用的堆栈槽个数,在本例中是1,因为我们只是把”Hello World”这个字符串压栈。举个例子,如果我们需要实现2数相减的减法,则需要2个堆栈槽,首先需要将2个数压栈,之后sub操作符将栈顶的2个数弹出并求差值,最后将结果压栈,所以最多需要2的栈槽。
- ldstr操作符将”Hello World”压栈,供之后的WriteLine方法使用。
- call调用了mscorlib程序集中System.Console类中的WriteLine方法。这里call指明了WriteLine的完整签名(void [mscorlib]System.Console::WriteLine(string)),所以运行时能正确地加载WriteLine方法。
- 如果方法有返回值的话,ret操作符会将结果返回给调用者;如果方法没有返回值,执行ret操作意味着方法的结束。
- 有一些同学可能看过很多CIL语言的代码,发现它们每一条语句之前都有一个类似”IL_0000:”这样的东西,其实IL_XXXX的作用是表示行号,没有它也不会影响程序的运行。
一个简单的Hello World的确能带来一些基本的知识点,但是这个HelloWorld.il文件编译之后还不能运行。因为本例中调用了mscorlib程序集中的WriteLine方法和ReadKey方法,所以我们还要加入一些程序集的信息才行,完整的代码如下:
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 4:0:0:0
}
.assembly GuoAssembly
{
.ver 0:0:0:0
}
.module GuoModule
.class public GuoNameSpace.Program extends [mscorlib]System.Object
{
.method public static void Show()
{
.entrypoint
.maxstack 1
ldstr "Hello World"
call void [mscorlib]System.Console::WriteLine(string)
call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
ret
}
}
请注意一下本例中类的名字为GuoNameSpace.Program,它表示创建了一个Program类,并且这个Program类属于GuoNameSpace命名空间。除此之外还有一种显式指定命名空间的写法,代码如下:
.namespace GuoNameSpace
{
.class public Program extends [mscorlib]System.Object
{
.method public static void Show()
{
.entrypoint
.maxstack 1
ldstr "Hello World"
call void [mscorlib]System.Console::WriteLine(string)
call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
ret
}
}
}
无论是显式指定命名空间还是隐式指定命名空间,在调用类时都要写类的全名称,即namespaceName+className。利用ilasm.exe对HelloWorld.il文件进行编译,生成HelloWorld.exe,双击运行可以看到屏幕输出了”Hello World”,第一个CIL程序圆满完成。