I already mentioned why every .NET dev should learn MSIL. So now it's time to get down and dirty. My goal is to be able to effectively use Reflection.Emit. With that in mind I'd like for my emitted IL to work and perform similarly to compiler created IL. I think the best approach to accomplishing this is by using the compiler as a reference. I'll start with writing simple programs in C#(or VB.NET, etc) and then taking a peep at the MSIL under the hood.
Here's a simple Hello World.
class Program
{
static void Main(string[] args)
{
Console.Write("Hello World");
}
}
Microsoft provides a nice tool for looking at IL in the windows SDK called IL Disassembler (or ILDASM for short). After opening up the compiled HelloWord exe in ILDASM. I get this little bit of IL.
.class private auto ansi beforefieldinit ILTests.Program
extends [mscorlib]System.Object
{
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Hello World"
IL_0006: call void [mscorlib]System.Console::Write(string)
IL_000b: nop
IL_000c: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Program::.ctor
} // end of class ILTests.Program
Not too terribly different but we are going to have to do some digging to figure out exactly what's going on. We can recognize some familiar constructs right away. There is a private class called Program that inherits from Object (which resides in mscorlib). We have a private static method called Main and what looks like a public constructor.
Let's take an in depth look at the class declaration:
.class private auto ansi beforefieldinit ILTests.Program
extends [mscorlib]System.Object
There are a few keywords here that are unfamiliar. Auto, ansi, and beforefieldinit in particular. From my research, it seems auto is used by the CLI (Common Language Infrastructure) Loader. It allows the loader to layout the class in anyway it sees fit. The other layout options are sequential and explicit. Beforefieldinit tells the CLI that it doesn't need to initialize the type before calling static methods. Ansi tells the CLI to marshall to and from Ansi strings.
The method declaration looks like:
.method private hidebysig static void Main(string[] args) cil managed
Again, breaking down the keywords that are likely unfamiliar:
hidebysig: Tells the CLI to hide any methods in the base class that match the signature of the method.
cil: Tells the CLI that the method contains normal CIL (Common Intermediate Language) code (the same as MSIL). Other options include: native and runtime.
managed: Lets the CLI know that the method is managed. Other options: unmanaged.
Now looking at the body of the method:
.entrypoint
// Code size 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Hello World"
IL_0006: call void [mscorlib]System.Console::Write(string)
IL_000b: nop
IL_000c: ret
Things start to get interesting here.
entrypoint: an ILASM keyword that designates a method as the entry point of the assembly.
maxstack: the number of stack slots required by the method.
nop: short for no operation. It does nothing.
ldstr: short for loadstring. Pushes a string onto the stack.
call: Invokes a method.
ret: short for return.
The first thing you'll notice is that IL is stack-based. We push "Hello World" onto the stack. Then we invoke System.Console.Write, passing "Hello World" as the argument. Last, it returns out of the main method.
You probably have noticed that there was no constructor in the original C# code. We know from general .NET knowledge that even if we do not explicitly define a constructor it can still be called. That's because the compiler creates the constructor for us.
Take a look at the constructor's code:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
specialname: indicates that the name of this item can have special significance to tools other than the CLI.
rtspecialname: indicates that the name of this item has special significance to the CLI. Any item marked rtspecialname shall also be marked specialname.
The contructor code is seems pretty straight forward now:
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
ldarg.n: loads argument n onto the stack. In this case ldarg.0 actually points to "this" (as is the case with instance methods).
The constructor loads its instance onto the stack and then calls the base class constructor.
So that's your basic Hello World app in IL.