
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf
484 C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S
nop
ldstr "Hello CIL code!"
call void [mscorlib]System.Console::WriteLine(string) nop
call string [mscorlib]System.Console::ReadLine() pop
ret
}
Interacting with CIL: Modifying an *.il File
Now that you have a better understanding of how a basic CIL file is composed, let’s complete our round-tripping experiment. The goal here is to update the CIL within the existing *.il file as so:
•Add a reference to the System.Windows.Forms.dll assembly.
•Load a local string within Main().
•Call the System.Windows.Forms.MessageBox.Show() method using the local string variable as an argument.
The first step is to add a new .assembly directive (qualified with the extern attribute) that specifies you are using System.Windows.Forms.dll. To do so, simply update the *.il file with the following logic after the external reference to mscorlib:
.assembly extern System.Windows.Forms
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89)
.ver 2:0:0:0
}
Be aware that the value assigned to the .ver directive may differ depending on which version of the .NET platform you have installed on your development machine. Here, you see that System.Windows. Forms.dll version 2.0.0.0 is used and has the public key token of B77A5C561934E089. If you open the GAC (see Chapter 11) and locate your version of the System.Windows.Forms.dll assembly, you can simply copy the correct version and public key token value via the assembly’s Properties page.
Next, you need to alter the current implementation of the Main() method. Locate this method within the *.il file and remove the current implementation code (the .maxstack and .entrypoint directives should remain intact; I’ll describe them later):
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
// ToDo: Write new CIL code!
}
Again, the goal is to push a new string onto the stack and call the MessageBox.Show() method (rather than the Console.WriteLine() method). Recall that when you specify the name of an external type, you must make use of the type’s fully qualified name (in conjunction with the friendly name of the assembly). Keeping this in mind, update the Main() method as follows:

C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S |
485 |
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
ldstr "CIL is way cool"
call valuetype [System.Windows.Forms] System.Windows.Forms.DialogResult [System.Windows.Forms] System.Windows.Forms.MessageBox::Show(string)
pop ret
}
In effect, you have just updated the CIL code to correspond to the following C# class definition:
public class Program
{
static void Main(string[] args)
{
System.Windows.Forms.MessageBox.Show("CIL is way cool");
}
}
Compiling CIL Code Using ilasm.exe
Assuming you have saved this modified *.il file, you can compile a new .NET assembly using the ilasm.exe (CIL compiler) utility. Perhaps surprisingly, the CIL compiler has far fewer command-line flags than the C# compiler. Table 15-1 shows the core flags of interest.
Table 15-1. Common ilasm.exe Command-Line Flags
Flag |
Meaning in Life |
/debug |
Includes debug information (such as local variable and argument names, as |
|
well as line numbers). |
/dll |
Produces a *.dll file as output. |
/exe |
Produces an *.exe file as output. This is the default setting and may be omitted. |
/key |
Compiles the assembly with a strong name using a given *.snk file. |
/noautoinherit |
Prevents class types from automatically inheriting from System.Object when |
|
a specific base class is not defined. |
/output |
Specifies the output file name and extension. If you do not make use of the |
|
/output flag, the resulting file name is the same as the name of the first source file. |
|
|
To compile your updated simplehelloclass.il file into a .NET *.exe, you can issue the following command within a Visual Studio 2005 command prompt:
ilasm /exe HelloProgram.il
Assuming things have worked successfully, you will see the report shown in Figure 15-1.

486 C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S
Figure 15-1. Compiling *.il files using ilasm.exe
At this point, you can run your new application. Sure enough, rather than pumping a message to the Console window, you will now see a message box displaying your message (see Figure 15-2).
Figure 15-2. The result of the round-trip
Compiling CIL Code Using SharpDevelop
When working with *.il files, you may wish to make use of the freely available SharpDevelop IDE (see Chapter 2). When you create a new combine (via the File New Combine menu option), one of your choices is to create a CIL project workspace. While SharpDevelop does not have IntelliSense support for CIL projects, CIL tokens are color-coded, and you are able to compile and run your application directly within the IDE (rather than running ilasm.exe from a command prompt).
Compiling CIL Code Using ILIDE#
If you’re truly interested in experimenting with the CIL programming language, I also recommend downloading the latest version of a free open source CIL editor named ILIDE#. This tool, like SharpDevelop, provides color-coding, ilasm.exe integration, and various related tools. Unlike SharpDevelop, the latest version of ILIDE# now supports CIL IntelliSense! You can download ILIDE# from http://ilide.aspfreeserver.com/default-en.aspx (of course, this URL is subject to change). Figure 15-3 shows ILIDE# in action.

C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S |
487 |
Figure 15-3. ILIDE# is a free CIL IDE.
The Role of peverify.exe
When you are building or modifying assemblies using CIL code, it is always advisable to verify that the compiled binary image is a well-formed .NET image using the peverify.exe command-line tool:
peverify HelloProgram.exe
This tool will examine all opcodes within the specified assembly for valid CIL code. For example, in terms of CIL code, the evaluation stack must always be empty before exiting a function. If you forget to pop off any remaining values, the ilasm.exe compiler will still generate a valid assembly (given that compilers are concerned only with syntax). peverify.exe, on the other hand, is concerned with semantics. If you did forget to clear the stack before exiting a given function, peverify.exe will let you know.
■Source Code The HelloProgram.il file is included under the Chapter 15 subdirectory.
Understanding CIL Directives and Attributes
Now that you have seen how ildasm.exe and ilasm.exe can be used to perform a round-trip, we can get down to the business of checking out the syntax and semantics of CIL itself. The next sections will walk you through the process of authoring a custom namespace containing a set of types. However, to keep things simple, these types will not contain any implementation logic for their members. Once you understand how to create empty types, you can then turn your attention to the process of providing “real” members using CIL opcodes.

488 C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S
Specifying Externally Referenced Assemblies in CIL
Create a new file named CilTypes.il using your editor of choice. First, you need to list the set of external assemblies used by the current assembly. For this example, you will only make use of types found within mscorlib.dll. To do so, the .assembly directive will be qualified using the external attribute. When you are referencing a strongly named assembly, such as mscorlib.dll, you’ll want to specify the .publickeytoken and .ver directives as well:
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
■Note Strictly speaking, you are not required to explicitly reference mscorlib.dll as an external reference, as ilasm.exe will do so automatically.
Defining the Current Assembly in CIL
The next order of business is to define the assembly you are interested in building using the .assembly directive. At the simplest level, an assembly can be defined by specifying the friendly name of the binary:
// Our assembly.
.assembly CILTypes { }
While this indeed defines a new .NET assembly, you will typically place additional directives within the scope of the assembly declaration. For this example, update your assembly definition to include a version number of 1.0.0.0 using the .ver directive (note that each numerical identifier is separated by colons, not the C#-centric dot notation):
// Our assembly.
.assembly CILTypes
{
.ver 1:0:0:0
}
Given that the CILTypes assembly is a single-file assembly, you will finish up the assembly definition using a single .module directive, which marks the official name of your .NET binary, CILTypes.dll:
.assembly CILTypes
{
.ver 1:0:0:0
}
// The module of our single-file assembly.
.module CILTypes.dll
In addition to .assembly and .module are CIL directives that further qualify the overall structure of the .NET binary you are composing. Table 15-2 lists a few of the more common assembly-level directives.

C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S |
489 |
Table 15-2. Additional Assembly-Centric Directives
Directive |
Meaning in Life |
.mresources |
If your assembly makes use of internal resource (such as bitmaps or string |
|
tables), this directive is used to identify the name of the file that contains the |
|
resources to be embedded. Chapter 20 examines .NET resources in detail. |
.subsystem |
This CIL directive is used to establish the preferred UI that the assembly wishes |
|
to execute within. For example, a value of 2 signifies that the assembly should |
|
run within a Forms-based GUI, whereas a value of 3 denotes a console application. |
|
|
Defining Namespaces in CIL
Now that you have defined the look and feel of your assembly (and the required external references), you can create a .NET namespace (MyNamespace) using the .namespace directive:
// Our assembly has a single namespace.
.namespace MyNamespace {}
Like C#, CIL namespace definitions can be nested within an outer namespace. For the sake of argument, assume you wish to create a root namespace named IntertechTraining:
.namespace IntertechTraining
{
.namespace MyNamespace {}
}
Like C#, CIL allows you to define a nested namespace as so:
// Defining a nested namespace.
.namespace IntertechTraining.MyNamespace{}
Defining Class Types in CIL
Empty namespaces are not very interesting, so let’s now check out the process of defining a class type using CIL. Not surprisingly, the .class directive is used to define a new class type. However, this simple directive can be adorned with numerous additional attributes, to further qualify the nature of the type. To illustrate, add a simple public class named MyBaseClass. As in C#, if you do not specify an explicit base class, your type will automatically be derived from System.Object:
.namespace MyNamespace
{
// System.Object base class assumed.
.class public MyBaseClass {}
}
When you are building a class type that derives from any class other than System.Object, you make use of the extends attribute. Whenever you need to reference a type defined within the same assembly, CIL demands that you also make use of the fully qualified name (however, if the base type is within the same assembly, you can omit the assembly’s friendly name prefix). Therefore, the following attempt to extend MyBaseClass results in a compiler error:
// This will not compile!
.namespace MyNamespace
{
.class public MyBaseClass {}
.class public MyDerivedClass extends MyBaseClass {}
}


C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S |
491 |
// Extending interfaces in terms of CIL.
.class public interface IMyInterface {}
.class public interface IMyOtherInterface implements MyNamespace.IMyInterface {}
Defining Structures in CIL
The .class directive can be used to define a CTS structure if the type extends System.ValueType. As well, the .class directive is qualified with the sealed attribute (given that structures can never be a base structure to other value types). If you attempt to do otherwise, ilasm.exe will issue a compiler error.
// A structure definition is always sealed.
.class public sealed MyStruct
extends [mscorlib]System.ValueType{}
Do be aware that CIL provides a shorthand notation to define a structure type. If you use the value attribute, the new type will derive the type from [mscorlib]System.ValueType and be marked as sealed automatically. Therefore, you could define MyStruct as so:
// Shorthand notation for declaring a structure.
.class public value MyStruct{}
Defining Enums in CIL
.NET enumerations (as you recall) derive from System.Enum, which is a System.ValueType (and therefore must also be sealed). When you wish to define an enum in terms of CIL, simply extend
[mscorlib]System.Enum:
// An enum.
.class public sealed MyEnum
extends [mscorlib]System.Enum{}
Like a structure definition, enumerations can be defined with a shorthand notation using the enum attribute:
// Enum shorthand.
.class public enum MyEnum{}
■Note The other fundamental .NET type, the delegate, also has a specific CIL representation. See Chapter 8 for full details.
Compiling the CILTypes.il file
Even though you have not yet added any members or implementation code to the types you have defined, you are able to compile this *.il file into a .NET DLL assembly (which you must do, as you have not specified a Main() method). Open up a command prompt and enter the following command to ilasm.exe:
ilasm /dll CilTypes.il
Once you have done so, you are able to open your binary into ildasm.exe (see Figure 15-4).

492 C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S
Figure 15-4. The CILTypes.dll assembly
Once you have confirmed the contents of your assembly, run peverify.exe against it. Notice that you are issued a number of errors, given that all your types are completely empty. To understand how to populate a type with content, you first need to examine the fundamental data types of CIL.
■Source Code The CilTypes.il file is included under the Chapter 15 subdirectory.
.NET Base Class Library, C#, and CIL Data Type
Mappings
Table 15-4 illustrates how a .NET base class type maps to the corresponding C# keyword, and how each C# keyword maps into raw CIL. As well, Table 15-4 documents the shorthand constant notations used for each CIL type. As you will see in just a moment, these constants are often referenced by numerous CIL opcodes.
Table 15-4. Mapping .NET Base Class Types to C# Keywords, and C# Keywords to CIL
.NET Base Class Type |
C# Keyword |
CIL Representation |
CIL Constant Notation |
System.SByte |
sbyte |
int8 |
I1 |
System.Byte |
byte |
unsigned int8 |
U1 |
System.Int16 |
short |
int16 |
I2 |
System.UInt16 |
ushort |
unsigned int16 |
U2 |
System.Int32 |
int |
int32 |
I4 |
System.UInt32 |
uint |
unsigned int32 |
U4 |
System.Int64 |
long |
int64 |
I8 |
System.UInt64 |
ulong |
unsigned int64 |
U8 |
System.Char |
char |
char |
CHAR |
System.Single |
float |
float32 |
R4 |
System.Double |
double |
float64 |
R8 |

C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S |
493 |
.NET Base Class Type |
C# Keyword |
CIL Representation |
CIL Constant Notation |
System.Boolean |
bool |
bool |
BOOLEAN |
System.String |
string |
string |
N/A |
System.Object |
object |
object |
N/A |
System.Void |
void |
void |
VOID |
|
|
|
|
Defining Type Members in CIL
As you are already aware, .NET types may support various members. Enumerations have some set of name/value pairs. Structures and classes may have constructors, fields, methods, properties, static members, and so on. Over the course of this book’s first 14 chapters, you have already seen partial CIL definitions for the items previously mentioned, but nevertheless, here is a quick recap of how various members map to CIL primitives.
Defining Field Data in CIL
Enumerations, structures, and classes can all support field data. In each case, the .field directive will be used. For example, let’s breathe some life into the skeleton MyEnum enumeration and define three name/value pairs (note the values are specified using a parentheses syntax):
.class public auto ansi sealed MyEnum extends [mscorlib]System.Enum
{
.field public static literal valuetype MyNamespace.MyEnum NameOne = int32(0)
.field public static literal valuetype MyNamespace.MyEnum NameTwo = int32(1)
.field public static literal valuetype MyNamespace.MyEnum NameThree = int32(2)
}
Fields that reside within the scope of a .NET System.Enum-derived type are qualified using the static and literal attributes. As you would guess, these attributes set up the field data to be a fixed value accessible from the type itself (e.g., MyEnum.NameOne).
■Note The values assigned to an enum value may also be in hexadecimal.
Of course, when you wish to define a point of field data within a class or structure, you are not limited to a point of public static literal data. For example, you could update MyBaseClass to support two points of private, instance-level field data:
.class public MyBaseClass
{
.field private string stringField
.field private int32 intField
}
As in C#, class field data will automatically be assigned to the correct default value. If you wish to allow the object user to supply custom values at the time of creation for each of these points of private field data, you (of course) need to create custom constructors.