Free mag vol1 | Page 711

CHAPTER 18  UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES The act of adding two numbers is expressed in terms of the CIL opcode 0X58. In a similar vein, subtracting two numbers is expressed using the opcode 0X59, and the act of allocating a new object on the managed heap is achieved using the 0X73 opcode. Given this reality, understand that the “CIL code” processed by a JIT compiler is actually nothing more than blobs of binary data. Thankfully, for each binary opcode of CIL, there is a corresponding mnemonic. For example, the add mnemonic can be used rather than 0X58, sub rather than 0X59, and newobj rather than 0X73. Given this opcode/mnemonic distinction, realize that CIL decompilers such as ildasm.exe translate an assembly’s binary opcodes into their corresponding CIL mnemonics. For example, here would be the CIL presented by ildasm.exe for the previous C# Add() method: .method private hidebysig static int32 Add(int32 x, int32 y) cil managed { // Code size 9 (0x9) .maxstack 2 .locals init ([0] int32 CS$1$0000) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldarg.1 IL_0003: add IL_0004: stloc.0 IL_0005: br.s IL_0007 IL_0007: ldloc.0 IL_0008: ret } Unless you’re building some extremely low-level .NET software (such as a custom managed compiler), you’ll never need to concern yourself with the literal numeric binary opcodes of CIL. For all practical purposes, when .NET programmers speak about “CIL opcodes,” they’re referring to the set of friendly string token mnemonics (as I’ve done within this text, and will do for the remainder of this chapter) rather than the underlying numerical values. Pushing and Popping: The Stack-Based Nature of CIL Higher-level .NET languages (such as C#) attempt to hide low-level CIL grunge from view as much as possible. One aspect of .NET development that is particularly well hidden is the fact that CIL is a stackbased programming language. Recall from the examination of the collection namespaces (see Chapter 9) that the Stack class can be used to push a value onto a stack as well as pop the topmost value off of the stack for use. Of course, CIL developers do not literally use an object of type Stack to load and unload the values to be evaluated; however, the same pushing and popping mind-set still applies. Formally speaking, the entity used to hold a set of values to be evaluated is termed the virtual execution stack. As you will see, CIL provides a number of opcodes that are used to push a value onto the stack; this process is termed loading. As well, CIL defines a number of additional opcodes that transfer the topmost value on the stack into memory (such as a local variable) using a process termed storing. In the world of CIL, it is impossible to access a point of data directly, including locally defined variables, incoming method arguments, or field data of a type. Rather, you are required to explicitly load the item onto the stack, only to then pop it off for later use (keep this point in mind, as it will help explain why a given block of CIL code can look a bit redundant). 654