Managed Execution and the Common Language Infrastructure
The processor cannot directly interpret an assembly. .NET assemblies consist mainly of a second language known as Common Intermediate Language(CIL), or IL for short.14 The C# compiler transforms the C# source file into this intermediate language. An additional step, usually performed at execution time, is required to change the CIL code into machine code that the processor can understand. This involves an important element in the execution of a C# program: the Virtual Execution System (VES). The VES, also casually referred to as the runtime, compiles CIL code as needed (a process known as just-in-time compilation or jitting). The code that executes under the context of an agent such as the runtime is termed managed code, and the process of executing under control of the runtime is called managed execution. The code is “managed” because the runtime controls significant portions of the program’s behavior by managing aspects such as memory allocation, security, and just-in-time compilation. Code that does not require the runtime to execute is called native code (or unmanaged code).
The specification for a runtime is included in a broader specification known as the Common Language Infrastructure (CLI) specification.15 An international standard, the CLI includes specifications for the following:
The term runtime can refer to either execution time or the VES. To help clarify the intended meaning, this book uses the term execution time to indicate when the program is executing, and it uses the term runtime when discussing the agent responsible for managing the execution of a C# program while it executes.
The VES or runtime
A type system that supports language interoperability, known as the Common Type System (CTS)
Guidance on how to write libraries that are accessible from CLI-compatible languages (available in the Common Language Specification [CLS])
Metadata that enables many of the services identified by the CLI (including specifications for the layout or file format of assemblies)
Running within the context of a runtime execution engine enables support for several services and features that programmers do not need to code for directly, including the following:
This section provides a brief synopsis of the CLI to familiarize you with the context in which a C# program executes. It also provides a summary of some of the terms that appear throughout this book. Chapter 24 is devoted to the topic of the CLI and its relevance to C# developers. Although the chapter appears last in the book, it does not depend on any earlier chapters, so if you are eager to become more familiar with the CLI, you can jump to it at any time.
Language interoperability: Interoperability between different source languages. This is possible because the language compilers translate each source language to the same intermediate language (CIL).
Type safety: Checks for conversion between types, ensuring that only conversions between compatible types occurs. This helps prevent the occurrence of buffer overruns, a leading cause of security vulnerabilities.
Code access security: Certification that the assembly developer’s code has permission to execute on the computer.
Garbage collection: Memory management that automatically de-allocates memory previously allocated by the runtime.
Platform portability: Support for running the same assembly on a variety of operating systems.
Base Class Library (BCL): Provides a foundation of code that developers can depend on so that they do not have to develop the code themselves.
Common Intermediate Language and Ildasm
As mentioned in the introduction of this section, the C# compiler converts C# code to CIL code and not to machine code. The processor can directly understand machine code; therefore, CIL code needs to be converted before the processor can execute it. Given an assembly, it is possible to view the CIL code using a CIL disassembler utility to deconstruct the assembly into its CIL representation. (The CIL disassembler is affectionately referred to by its Microsoft .NET Framework–specific filename, Ildasm, which stands for IL Disassembler.) Ildasm disassembles an assembly and extracts the CIL generated by the C# compiler into text.
The output that results from disassembling a .NET assembly is significantly easier to understand than machine code. For many developers, this may raise a concern because it is easier for programs to be decompiled and algorithms understood without explicitly redistributing the source code. As with any program, CLI based or not, the only foolproof way of preventing disassembly is to disallow access to the compiled program altogether (e.g., hosting a program only on a website instead of distributing it to a user’s machine). However, if decreased accessibility to the source code is all that is required, there are several obfuscators available. Obfuscators read the IL code and transform it so that it does the same thing but in a way that is much more difficult to understand. This technique prevents the casual developer from accessing the code and creates assemblies that are much more difficult and tedious to decompile into comprehensible code. Unless a program requires a high degree of algorithm security, obfuscators are generally sufficient.
CIL Output for HelloWorld.exe
The exact command used for the CIL disassembler depends on which implementation of the CLI is used. Instructions are available at http://itl.tc/ildasm. Listing 1.22 shows the CIL code created from running Ildasm.
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
// Code size 8
IL_0001: call instance void [System.Runtime]
} // End of method System.Void HelloWorld.Program::.ctor()
} // End of class HelloWorld.Program
The beginning of the listing is the manifest information. It includes not only the full name of the disassembled module (HelloWorld.dll) but also all the assemblies it depends on, along with their version information.
Perhaps the most interesting thing that you can glean from such a listing is how relatively easy it is to follow what the program is doing compared to trying to read and understand machine code (assembler). In the listing, an explicit reference to Console.WriteLine() appears. There is a lot of peripheral information to the CIL code listing, but if a developer wanted to understand the inner workings of a C# assembly (or any CLI-based program) without having access to the original source code, it would be relatively easy unless an obfuscator is used. In fact, several free tools are available (such as Red Gate’s Reflector, ILSpy, JustDecompile, dotPeek, and CodeReflect) that can decompile from CIL to C# automatically.
A third term for CIL is Microsoft IL (MSIL). This book uses the term CIL because it is the term adopted by the CLI standard. IL is prevalent in conversation among people writing C# code because they assume that IL refers to CIL rather than other types of intermediate languages.
Miller, J., and S. Ragsdale. 2004. The Common Language Infrastructure Annotated Standard. Boston: Addison-Wesley.