|Getting Started With .NET IL|
|Written by Mike James|
|Friday, 10 December 2021|
Page 2 of 3
What is interesting about this simple example is that it illustrates all of the major characteristics of the assembler.
Assembler directives begin with a dot and the first directive:
informs the assembler that we are going to be using objects and methods within the mscorlib assembly, i.e. the console class and its WriteLine method.
Notice that already we have objects and the .NET Framework involved in our assembler.
The next two directives simply give the program that we are creating an assembly and module name. Without these the assembler and the runtime don’t really know what to do with our program – declaring it to be an assembly means that it can be run.
The next line declares a class and states that it inherits from Object
We then define a CIL managed static method:
All more evidence of object orientation and use of the Framework.
The entrypoint directive marks where the program should be started from and every runnable program has to have one:
Finally we get to some IL instructions, and it’s all over very quickly!
The ldstr, i.e. LoadString, instruction loads the string “Hello World” onto the stack. The call instruction calls the WriteLine method of the static Console class.
The method picks up its parameters from the stack and what looks like a parameter definition, i.e. (string), is a type definition that says that the top of stack item is to be a string. The void return type means that the method doesn’t leave a return value on the stack. The ret, or Return, completes the method and our program.
As you can see even this simple example demonstrates how stack- and object-oriented the language is and how it uses both typing and the Framework.
Of all of the features of IL, the one that high level language programmers tend to find strange is the central role the stack plays.
In this case the stack is a little more sophisticated than a simple block of memory with a pointer. You need to think of it in terms of a strongly typed stack made up of “slots” that hold a complete data type.
When you push data onto and pop data off the stack it always works in terms of a complete data type. Nearly all IL instructions work by popping input data from the stack and pushing their results on the stack.
As an example, let’s add two numbers together.
The first instruction is ldc or LoaD Constant
This pushes the 4-byte integer constant, i.e. an Int32, onto the stack. The ldc part of the instruction tells you what is to happen and the code after the dot tells you the data type - i4 is a four byte integer in this case. There are ldc instructions for all the standard data types - for example ldc.r8 pushes a float32 on the stack.
To perform an add we need two values on the stack so we need to push another int32 and then give the add command:
The add command takes the top two items on the stack adds them together and pushes the result back on the top of the stack.
Now we have the result of adding 1 and 2, i.e. 3, on the top of the stack and we can use WriteLine again to display the result and issue a return to complete the job:
Notice that everything is still strongly typed and the add instruction can discover the type of the two items on the top of the stack and push an appropriate type back on the stack – you can try the same with floating point numbers:
The range of primitive data types available to you is similar to those in C# or VB with some changes to the names used.
|Last Updated ( Friday, 10 December 2021 )|