This article requires knowledge of IL or at least desire to learn it. My intention is to give few illustrations where usage of IL is possible or only option. I will start with 'possible'.
Disassembling and modifying
One of the most popular techniques for learning IL is writing code in high level language i.e. C# or VB.NET and examining result of compilation in ILDASM. To be honest this is if not the only one then at least part of any other available method for learning IL. So let's go to example: imagine that you want to create simple windows form and attach event handler to it. If you are using visual studio any time it will deliver to you boilerplate WinForm if you select type of the project to be Windows Application. This one you can use as it is or add some functionality, in other words use as base for your application. Only problem is if you want to use it to find out how to write GUI application from IL this one is too complicated. Our recipe for baking windows form needs to be much simpler. Simplest possible one looks like this:
using System;
using System.Windows.Forms;
class SimpleGUI
{
public static void Main()
{
Application.Run(new Form());
}
}
Compile it and examine it in ILDASM.
// Microsoft (R) .NET Framework IL Disassembler. Version 1.0.2914.16
// Copyright (C) Microsoft Corp. 1998-2001. All rights reserved.
// VTableFixup Directory:
// No data.
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:2411:0
}
.assembly extern System.Windows.Forms
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:2411:0
}
.assembly SimpleGUI
{
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(bool,
// bool) = ( 01 00 01 00 00 00 )
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module SimpleGUI.exe
// MVID: {A51EEE85-FBC7-4CAD-B56D-A921BBF55C5A}
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000001
// Image base: 0x03080000
.class private auto ansi beforefieldinit SimpleGUI
extends [mscorlib]System.Object
{
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 11 (0xb)
.maxstack 8
IL_0000: newobj instance void [System.Windows.Forms]System.Windows.Forms.Form::.ctor()
IL_0005: call void [System.Windows.Forms]System.Windows.Forms.Application::Run(class [System.Windows.Forms]System.Windows.Forms.Form)
IL_000a: ret
} // end of method SimpleGUI::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 SimpleGUI::.ctor
} // end of class SimpleGUI
//*********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file C:\Documents and Settings\Dile\Desktop\opet isto\s1.res
Remove comments to reduce size of disassembly. Next thing which you can remove is reference to 'mscorlib', compiler will include it anyway so throw it away. Very important is: don't remove reference to 'System.Windows.Forms', also your version might be different than mine (I'm still using Beta 2). What else can we remove? Since IL works fine with global functions, there is no need to wrap everything in class like in C#. We will throw away class declaration and constructor. Finally we have the following:
.assembly extern System.Windows.Forms
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 1:0:2411:0}
.assembly SimpleGUI{}
.subsystem 0x00000002
.method public static void Main() cil managed
{
.entrypoint
newobj instance void [System.Windows.Forms]System.Windows.Forms.Form::.ctor()
call void [System.Windows.Forms]System.Windows.Forms.Application::Run(class [System.Windows.Forms]System.Windows.Forms.Form)
ret
}
Instruction 'subsystem' and accompanied value representing instruction to run application as GUI is the same as /t:winexe option for C# command line compiler. Resulting form is without any kind of text or anything else, but you can resize it, minimize it and finally close it using system menu. To set some properties we will return to C#. SharpDevelop offers following as template for windows application:
using System;
using System.Windows.Forms;
class MainForm : Form
{
public MainForm()
{
Text = "This is my form";
}
public static void Main(string[] args)
{
Application.Run(new MainForm());
}
}
But since I want to remove constructor in IL this one won't work for me (here constructor sets window title). So here is a slightly different (more in spaghetti style) version:
using System;
using System.Windows.Forms;
class NiceDay:Form
{
public static void Main()
{
NiceDay t=new NiceDay();
t.Text="Hello World Form";
t.Click += new System.EventHandler(t.NiceDay_Click);
Application.Run(t);
}
private void NiceDay_Click(object sender, System.EventArgs e)
{
this.Text=this.Text+" was clicked";
}
}
Of course I added also one event handler to make all more interesting. Compile it and disassemble it, result should look like this:
// Microsoft (R) .NET Framework IL Disassembler. Version 1.0.2914.16
// Copyright (C) Microsoft Corp. 1998-2001. All rights reserved.
// VTableFixup Directory:
// No data.
.assembly extern System.Windows.Forms
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:2411:0
}
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:2411:0
}
.assembly celaprica
{
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(bool,
// bool) = ( 01 00 01 00 00 00 )
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module celaprica.exe
// MVID: {91C92264-2BC2-4C7B-ADE9-9EBD553E6A7D}
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000001
// Image base: 0x03080000
.class private auto ansi beforefieldinit NiceDay
extends [System.Windows.Forms]System.Windows.Forms.Form
{
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 42 (0x2a)
.maxstack 4
.locals ([0] class NiceDay t)
IL_0000: newobj instance void NiceDay::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: ldstr "Hello World Form"
IL_000c: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
IL_0011: ldloc.0
IL_0012: ldloc.0
IL_0013: ldftn instance void NiceDay::NiceDay_Click(object,
class [mscorlib]System.EventArgs)
IL_0019: newobj instance void [mscorlib]System.EventHandler::.ctor(object,
native unsigned int)
IL_001e: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::add_Click(class [mscorlib]System.EventHandler)
IL_0023: ldloc.0
IL_0024: call void [System.Windows.Forms]System.Windows.Forms.Application::Run(class [System.Windows.Forms]System.Windows.Forms.Form)
IL_0029: ret
} // end of method NiceDay::Main
.method private hidebysig instance void
NiceDay_Click(object sender,
class [mscorlib]System.EventArgs e) cil managed
{
// Code size 23 (0x17)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.0
IL_0002: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_0007: ldstr " was clicked"
IL_000c: call string [mscorlib]System.String::Concat(string, string)
IL_0011: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
IL_0016: ret
} // end of method NiceDay::NiceDay_Click
.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 [System.Windows.Forms]System.Windows.Forms.Form::.ctor()
IL_0006: ret
} // end of method NiceDay::.ctor
} // end of class NiceDay
//*********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file C:\Documents and Settings\Dile\Desktop\opet isto\finaleFRM.res
Now default constructor doesn't do any useful work and looks as a waste of time during execution so I will delete it and call base constructor from Main(). So in code above delete:
.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 [System.Windows.Forms]System.Windows.Forms.Form::.ctor()
IL_0006: ret
} // end of method NiceDay::.ctor
and replace first line (IL_0000) in Main() which is originally:
IL_0000: newobj instance void NiceDay::.ctor()
with this:IL_0000: newobj instance void [System.Windows.Forms]System.Windows.Forms.Form::.ctor()
Compile it and run it.
Those were the examples where we code in C# and change resulting IL code to make it smaller, what I want to say, we are making changes to the code which already works fine.
Another example could be array and foreach statement where C# likes to introduce additional variables on IL level.
Overcoming limitations of high level language
Despite all nice features which are included in C# there is also a number of limitations. We can make a difference between limitations imposed by writers of C# compiler like that you can't have global functions or overload return type of method, and limitations which are actually result of IL limitations like lack of support for templates. Obviously second case requires modifications of IL or some other workaround which is not object of our interest at the moment.
As I mentioned C# doesn't allow you to overload return type and IL allows you to do so. Attempt to compile following will result in compilation error.
using System;
class T
{
static double A2(object a,object b)
{
return (double)a+(double)b;
}
static string A2(object a,object b)
{
return Convert.ToString(a)+ " " + Convert.ToString(b);
}
public static void Main()
{
double d=A2(1.2,1.8);
string s=A2(1.2,1.8);
Console.WriteLine(d);
Console.WriteLine(s);
Console.Read();
}
}
To make it compile change the name of the second method (and appropriate call also). When you disassemble it make names the same and compile it again but this time as IL code. If you like you can make it as three functions like this:
.assembly second{}
.method private hidebysig static float64
A2(object a, object b) cil managed
{
.maxstack 8
ldarg.0
unbox [mscorlib]System.Double
ldind.r8
ldarg.1
unbox [mscorlib]System.Double
ldind.r8
add
ret
}
.method private hidebysig static string
A2(object a, object b) cil managed
{
.maxstack 8
ldarg.0
call string [mscorlib]System.Convert::ToString(object)
ldstr " "
ldarg.1
call string [mscorlib]System.Convert::ToString(object)
call string [mscorlib]System.String::Concat(string, string, string)
ret
}
.method public hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 2
.locals ([0] float64 d, [1] string s)
ldc.r8 1.2
box [mscorlib]System.Double
ldc.r8 1.8
box [mscorlib]System.Double
call float64 A2(object, object)
stloc.0
ldc.r8 1.2
box [mscorlib]System.Double
ldc.r8 1.8
box [mscorlib]System.Double
call string A2(object, object)
stloc.1
ldloc.0
call void [mscorlib]System.Console::WriteLine(float64)
ldloc.1
call void [mscorlib]System.Console::WriteLine(string)
call int32 [mscorlib]System.Console::Read()
pop
ret
}
When compiled and executed it will show sum of these two doubles as double and as sum of two strings (as a concat) containing again these numbers and space in-between. So that was more than easy. If you like to use this from another assembly change attribute private to public for the methods and leave enveloping class (including constructor) declaration. Also don't try to call it from client written in C# it won't compile, you must use IL again.
Conclusion
In order to better understand your favorite high level language try to examine results of compilation, if you find it interesting try changing it in IL, it should be even more interesting. If you don't understand something try to use documents from "C:\Program Files\Microsoft.NET\FrameworkSDK\Tool Developers Guide\docs". For the end there is link for one nice site http://www.vijaymukhi.com there you can find few books on line and one of them is dedicated to IL (unfortunately it uses Beta 1 but you still can learn a lot from it).