-15-
A GUI Application in IL
So far, we have been writing
small programs and rendering explanations for specific concepts. The IL
documentation contains a very large program that demonstrates how to write a
small application. Their application spawns five files, creates four dll's and
has code that is spread over at least 30 pages.
We believe in what Newton once
said, that he could see very far because he stood on the shoulders of tall
people. We would like to emulate this thought. So, let us get down to hard work
in our last chapter. We have decided to use the same application to explain the
different concepts. We also expect you to read the original program yourselves.
We first created a batch file
a.bat as shown below. The batch file does everything for us, including running
the program. As far as possible, we have maintained the same variable and class
names that the original program contained. The only change incorporated is that
we have put everything into the following two files:
• countdown.il that becomes an
executable.
• aaa.il that contains the rest of the
code and becomes a dll.
a.bat
del *.dll
del *.exe
ilasm aaa.il /dll
ilasm countdown.il
countdown
countdown.il
.assembly CountDown {}
.assembly extern System.WinForms {}
.assembly extern System.Drawing {}
.file aaa.dll
.module extern aaa.dll
.class zzz extends
[System.WinForms]System.WinForms.Form
{
.field class [.module
aaa.dll]ctb counterBox
.field class [.module
aaa.dll]ssb button
.method public static
void Main()
{
.entrypoint
newobj instance void
zzz::.ctor()
call void
[System.WinForms]System.WinForms.Application::Run(class
[System.WinForms]System.WinForms.Form)
ret
}
.method instance void
.ctor()
{
ldarg.0
call instance void
[System.WinForms]System.WinForms.Form::.ctor()
ldarg.0
ldarg.0
newobj instance void
[.module aaa.dll]ssb::.ctor(class [System.WinForms]System.WinForms.Form)
stfld class [.module aaa.dll]ssb zzz::button
ldarg.0
dup
newobj instance void
[.module aaa.dll]ctb::.ctor(class zzz)
stfld class [.module
aaa.dll]ctb zzz::counterBox
.locals init (value class [System.Drawing]System.Drawing.Size
size)
ldloca size
initobj value class [System.Drawing]System.Drawing.Size
ldloca size
ldc.i4 425
ldc.i4 300
call instance void
[System.Drawing]System.Drawing.Size::.ctor(int32, int32)
ldarg.0
ldloc size
callvirt instance void zzz::set_Size(value class
[System.Drawing]System.Drawing.Size)
ldarg.0
call value class
[System.Drawing]System.Drawing.Color
[System.Drawing]System.Drawing.Color::get_CadetBlue()
callvirt instance void zzz::set_BackColor(value class
[System.Drawing]System.Drawing.Color)
ldarg.0
ldstr
"CountDown"
callvirt instance void zzz::set_Text(class System.String)
ret
}
}
aaa.il
.module aaa.dll
.assembly extern mscorlib {}
.assembly extern System.WinForms {}
.assembly extern System.Drawing {}
.file CountDown.exe
.module extern CountDown.exe
.class public ctb extends
[System.WinForms]System.WinForms.TextBox
{
.field class [.module CountDown.exe]zzz parent
.field static family int32 counterDefault at Vijay
.method instance void .ctor(class [.module CountDown.exe]zzz
parent)
{
ldarg.0
call instance void
[System.WinForms]System.WinForms.TextBox::.ctor()
ldarg.0
ldarg parent
stfld class [.module CountDown.exe]zzz ctb::parent
.locals (value class [System.Drawing]System.Drawing.Point point)
ldloca point
ldc.i4 75
ldc.i4 100
call instance void
[System.Drawing]System.Drawing.Point::.ctor(int32, int32)
ldarg.0
ldloc point
call instance void
[System.WinForms]System.WinForms.TextBox::set_Location(value class
[System.Drawing]System.Drawing.Point)
ldarg parent
call instance class
[System.WinForms]System.WinForms.Control$ControlCollection
[System.WinForms]System.WinForms.Form::get_Controls()
ldarg.0
callvirt instance void
[System.WinForms]System.WinForms.Control$ControlCollection::Add(class
[System.WinForms]System.WinForms.Control)
ldarg.0
ldsfld int32 ctb::counterDefault
call class System.String [mscorlib]System.Int32::ToString(int32)
callvirt instance void
[System.WinForms]System.WinForms.TextBox::set_Text(class System.String)
ret
}
}
.data Vijay = int32(3)
.class public ssb extends
[System.WinForms]System.WinForms.Button
{
.method instance void .ctor(class
[System.WinForms]System.WinForms.Form parent)
{
ldarg.0
call instance void
[System.WinForms]System.WinForms.Button::.ctor()
.locals init (value class [System.Drawing]System.Drawing.Size
size,value class [System.Drawing]System.Drawing.Point point)
ldloca point
initobj value class [System.Drawing]System.Drawing.Point
ldloca point
ldc.i4 100
ldc.i4 200
call instance void
[System.Drawing]System.Drawing.Point::.ctor(int32, int32)
ldarg.0
ldloc point
call instance void
[System.WinForms]System.WinForms.Button::set_Location(value class
[System.Drawing]System.Drawing.Point)
ldloca size
initobj value class [System.Drawing]System.Drawing.Size
ldloca size
ldc.i4 200
ldc.i4 50
call instance void [System.Drawing]System.Drawing.Size::.ctor(int32,
int32)
ldarg.0
ldloc size
callvirt instance void
[System.WinForms]System.WinForms.Button::set_Size(value class
[System.Drawing]System.Drawing.Size)
ldarg.0
call value class
[System.Drawing]System.Drawing.Color [System.Drawing]System.Drawing.Color::get_Gold()
callvirt instance void
[System.WinForms]System.WinForms.Button::set_BackColor(value class
[System.Drawing]System.Drawing.Color)
ldarg.0
call value class
[System.Drawing]System.Drawing.Color
[System.Drawing]System.Drawing.Color::get_Navy()
callvirt instance void
[System.WinForms]System.WinForms.Button::set_ForeColor(value class
[System.Drawing]System.Drawing.Color)
ldarg.0
ldstr "Arial"
ldc.r4 20
newobj instance void
[System.Drawing]System.Drawing.Font::.ctor(class System.String, float32)
callvirt instance void
[System.WinForms]System.WinForms.Button::set_Font(class
[System.Drawing]System.Drawing.Font)
ldarg.0
ldstr "Start"
callvirt instance void
[System.WinForms]System.WinForms.Button::set_Text(class System.String)
ldarg parent
call instance class
[System.WinForms]System.WinForms.Control$ControlCollection
[System.WinForms]System.WinForms.Form::get_Controls()
ldarg.0
callvirt instance void
[System.WinForms]System.WinForms.Control$ControlCollection::Add(class
[System.WinForms]System.WinForms.Control)
ret
}
}
We have repeated some of the
earlier explanations to refresh your memory. The explanation is also a summary
of what we have learnt so far.
When we run the above program, a
button and a text box are displayed on the screen. The button has a different
color and the window has a title.
This large program enlightens us
on designing a GUI application in IL. To write one ourselves, we start with the
main program which is countdown.il.
The assembly is named as
countdown for want of a better name. The code for the GUI classes resides in a
file called System.WinForms.dll and belongs to the namespace System.Winforms.
The type is prefaced with the dll name
which is optional in case of mscorlib.dll.
As we are referring to code in
an external assembly, the extern parameter is given after the directive
assembly. This is followed by the name of the assembly that contains the code.
The extern directive is supplied for System.Drawing also. No error is generated
if an extern directive is mentioned and the code within the file is not executed
Most of our code is contained in
classes and resides in a file aaa.il. The assembler converts the il file into a
dll hence the file name is aaa.dll. The
two directives used are:
• The first specifies the name of the
physical file using the file directive.
• The second is the assembly extern
directive.
We will not repeat the code
explanations that have been delved upon earlier.
We have created two fields:
.field class [.module
aaa.dll]ctb counterBox
.field class [.module
aaa.dll]ssb button
The first one is named
counterBox and typed ctb. The class ctb exists in aaa.dll file, hence the field
is prefaced with the name of the class alongwith the .module directive and the
name of the dll. This information is mandatory when referencing a type in an
external dll. In the same vein, the field named button that looks like class
ssb, resides in file aaa.dll.
.class zzz extends
[System.WinForms]System.WinForms.Form
The class zzz is derived from
the class System.Winforms.Form. To build a GUI applications, in Microsoft
parlance, WinForms, the class has to be
derived from Form in namespace System.Winforms.
The code execution begins in
Main as the entrypoint directive is placed in this function.Within this
function, a new object zzz is created and placed on the stack. This is to
facilitates the next function call Run
that requires a Forms object on the stack.
newobj instance void
zzz::.ctor()
call void
[System.WinForms]System.WinForms.Application::Run(class
[System.WinForms]System.WinForms.Form)
The object created by the newobj
instruction is all that we need to place an empty window on our screen. newobj
calls the constructor of class zzz i.e.
.ctor which populates the window with a variety of buttons, text boxes and
widgets.
A look at the constructor of
class zzz:
ldarg.0
call instance void
[System.WinForms]System.WinForms.Form::.ctor()
At first, the constructor of the base class Forms is
called. Prior to the call, the this pointer is the only value that is placed on
the stack as the constructor of the base class Forms, takes no parameters. The
this pointer is stored in the invisible first parameter to every non static
function and its value can be accessed using the instruction ldarg.0.
The next job is creating an
object, an instance of class ssb i.e. a button. The instruction newobj comes
into focus again and the constructor of the class ssb is called.
newobj instance void
[.module aaa.dll]ssb::.ctor(class [System.WinForms]System.WinForms.Form)
As explained earler, the this
pointer is placed on the stack but it is done twice. The second this pointer is
for the parameter to the constructor. A
reference to the newly created object on the stack is stored in the field
button, so that it can be used later.
stfld class [.module aaa.dll]ssb zzz::button
To store the return value of
newobj in a field, the first this pointer is used. This proves that to access a
field, we first need a reference to the object that contains the field.
The return value of newobj that
is placed on the stack can be stored in a local, if desired so. But, to store
it in a field, the this pointer must be loaded on the stack prior to calling newobj. newobj then removes
the second this pointer from the stack.
In short, newobj requres the
this pointer on the stack first and then calls the constructor. The constructor
is not called directly but to do so the this pointer reference must be placed
on the stack first. With newobj it is impractical to stack position the this
pointer as the object has not been created at all.
The instruction stfld not only
requires the name of the field, but also its data type, since the field can be
in another assembly. Incidentally, it takes the same effort to use an entity
from another assembly, as it takes to use the same entity located in our file.
The only difference is in the use of square brackets [] and in the name of the
module or assembly.
After the button, the next
widget to be created is a text box of type ctb.
newobj instance void
[.module aaa.dll]ctb::.ctor(class zzz)
The constructor to this class
needs a parameter as in the ssb class. Here, instead of repeating the ldarg.0
instruction twice, the dup instruction is used. This instruction simply
duplicates the value present on the stack. ldarg.0 places the this pointer on
the stack and dup creates one more copy of the this pointer.
You are free in choosing from
two ldarg.0 statements or using the dup instruction. We will focus on the role
of the constructors a little later after a brief synopsis of the program.
stfld class [.module
aaa.dll]ctb zzz::counterBox
The reference to the newly
created object on the stack, akin to the button is stored in the field
counterBox, so that it can be used later.
The directive .locals very often
used for creating variables can be placed anywhere in the function, as styles
evolve with time, but good programming style demands that we use it at the
beginning. A local variable called size of type Size within the namespaces System.Drawing is created.
.locals init (value class [System.Drawing]System.Drawing.Size
size)
Since it is a value type, we see
the modifier value in front of class.
The init keyword initializes the
locals to zero. ldloca places the address of size on the stack and then initobj
calls the constructor of the value class or structure.
ldloca size
initobj value class [System.Drawing]System.Drawing.Size
initobj is optional, but it is a
good idea to use it. We cannot use newobj on a value type.
Now to initialize this size
object to the size of our opening windows:
To accomplish this, on the stack
first is placed the address of the
size, followed by x and y co-ordinates of the window.
ldloca size
ldc.i4 425
ldc.i4 300
Thereafter, the constructor of
the Size class is called which initializes the size object.
call instance void
[System.Drawing]System.Drawing.Size::.ctor(int32, int32)
The Form class has a function or
property called set_Size that initializes the window size on the screen,
depending upon the size object passed.
In order to change the look and
feel of any GUI application, the parameters are first placed on the stack and
then the relevant functions are called . From now on, we will not comment upon
the unavoidable requirement of the this pointer on the stack.
To change the foreground and
background color of the window, a property called get_CadetBlue from the Color
Class, a value class, is used.
call value class
[System.Drawing]System.Drawing.Color
[System.Drawing]System.Drawing.Color::get_CadetBlue()
The return value is placed on
the stack and can either be an enum or a constant. Remember properties are
functions within a class.
The virtual function
set_BackColor changes the background color.
callvirt instance void zzz::set_BackColor(value class [System.Drawing]System.Drawing.Color)
We could have gone on endlessly
on changing the appearance of our window, however, we will stop with just one
more function, to change the title of our windows. To achieve this, the title
as a string is loaded on the stack using ldstr and then function set_Text is
called to change the title.
ldstr
"CountDown"
callvirt instance void zzz::set_Text(class System.String)
Now, to understanding the file
aaa.il.
aaa.il eventually gets converted
into a dll, hence the assembly directive is evaded. Instead, a module directive
stating the name of the module is inserted.
.module aaa.dll
.assembly extern mscorlib {}
.assembly extern System.WinForms {}
.assembly extern System.Drawing {}
.file CountDown.exe
.module extern CountDown.exe
This file references some fields
stored in the executable file countdown.exe, and thus, we need both the file
and assembly extern directive.
The first function to be called
in the dll is the constructor of class ssb, an instance of the Button class.
This is because countdown.il executes newobj on ssb i.e the Button class. The
Button class contains all the code needed to represent a Button object on the
screen. As usual, the first call is to the constructor of the base class
Button.
call instance void
[System.WinForms]System.WinForms.Button::.ctor()
Two locals are created
thereafter:
• One to store a width and height
dimension, called size,
• Another to represent a point on the
screen, in x and y coordinates, called point.
.locals init (value class [System.Drawing]System.Drawing.Size
size,value class [System.Drawing]System.Drawing.Point point)
The point member is initialized
in the same manner as the size member. point is then loaded on the stack and
set_Location from the Button class is called.
ldloc point
call instance void
[System.WinForms]System.WinForms.Button::set_Location(value class
[System.Drawing]System.Drawing.Point)
Remember, the this pointer in
this case is an ssb, a Button reference type.
Every Winforms widget has a
set_Size function that lets us set the size of the widget. The color can be set
as shown before. To create a font object, besides the this pointer, we need
only two parameters to the constructor:
• The first is the name of the font
• The second is a point size for the
font.
ldarg.0
ldstr "Arial"
ldc.r4 20
newobj instance void
[System.Drawing]System.Drawing.Font::.ctor(class System.String, float32)
callvirt instance void
[System.WinForms]System.WinForms.Button::set_Font(class [System.Drawing]System.Drawing.Font)
This newly created font object
is placed on the stack and the function set_Font is called.
ldstr "Start"
callvirt instance void
[System.WinForms]System.WinForms.Button::set_Text(class System.String)
To place the label
"Start" on the button, the string is loaded on the stack using ldstr
and thereafter the function set_Text is called. It is as simple as that.
The next instruction in the
sequence is loading the parameter parent on the stack.
ldarg parent
call instance class
[System.WinForms]System.WinForms.Control$ControlCollection
[System.WinForms]System.WinForms.Form::get_Controls()
ldarg.0
callvirt instance void
[System.WinForms]System.WinForms.Control$ControlCollection::Add(class
[System.WinForms]System.WinForms.Control)
If you remember, the constructor
was called with two identical parameters on the stack. Thus ldarg.0 and ldarg
parent are the same. The function get_Controls places a
Control$ControlCollection object on the stack. The this pointer is also loaded
and the virtual function Add is called. Add, adds the newly created control to
the list of controls that are finally to be displayed by Winforms. The function
Add belongs to the class Control$ControlCollection and hence a reference to this
class is required. It is easier in a language like C#, that shields us from
passing the this pointer.
The next widget to be displayed on the window is the text box..
The text box class is called ctb and derives from TextBox. As usual, the
constructor is called. We will repeat this aspect of the code, i.e. calling of
the constructor, for the last time. If you do not call the base class
constructor, you will be in serious trouble.
To set the location,
set_Location is used and then the control is added to the list of controls
using the Add function. How does Winforms display the number 3 in the text box
?
For this purpose, a field called
counterDefault is created which is
static and an int32. Tagged alongwith it is modifier at, and then a name Vijay.
.field static family int32 counterDefault at Vijay
This denotes that the variable
counterDefault will receive its initial value from Vijay.
There is a directive called
.data that creates a word "Vijay" and initialises it to the number 3
using int32.
.data Vijay = int32(3)
This directive called data
places the value 3 in the .data section in the PE Executable file. The PE file
is divided into smaller parts called sections. All the code goes into a section
called .text and all the data goes into a section called .data. We thus do not
have to initialize the variable in a constructor.
This number is placed on the
stack and a static function To_String is called, that converts this int32 into
a string. Then our good old function set_Text displays it in a text box.
Lets us now proceed to the next
example.
countdown.il
.assembly CountDown {}
.assembly extern System.WinForms {}
.file aaa.dll
.module extern aaa.dll
.class zzz extends
[System.WinForms]System.WinForms.Form
{
.field class [.module
aaa.dll]ssb button
.method public static
void Main()
{
.entrypoint
newobj instance void zzz::.ctor()
call void
[System.WinForms]System.WinForms.Application::Run(class
[System.WinForms]System.WinForms.Form)
ret
}
.method instance void
.ctor()
{
ldarg.0
call instance void
[System.WinForms]System.WinForms.Form::.ctor()
ldarg.0
ldarg.0
newobj instance void
[.module aaa.dll]ssb::.ctor(class [System.WinForms]System.WinForms.Form)
stfld class [.module aaa.dll]ssb zzz::button
ret
}
}
aaa.il
.module aaa.dll
.assembly extern mscorlib {}
.assembly extern System.WinForms {}
.assembly extern System.Drawing {}
.file CountDown.exe
.module extern CountDown.exe
.class public ssb extends
[System.WinForms]System.WinForms.Button
{
.field class
[mscorlib]System.EventHandler onClickEventHandler
.method instance void .ctor(class [System.WinForms]System.WinForms.Form
parent)
{
ldarg.0
call instance void
[System.WinForms]System.WinForms.Button::.ctor()
ldarg.0
ldstr "Start"
callvirt instance void
[System.WinForms]System.WinForms.Button::set_Text(class System.String)
ldarg parent
call instance class
[System.WinForms]System.WinForms.Control$ControlCollection
[System.WinForms]System.WinForms.Form::get_Controls()
ldarg.0
callvirt instance void
[System.WinForms]System.WinForms.Control$ControlCollection::Add(class
[System.WinForms]System.WinForms.Control)
ldarg.0
dup
dup
ldvirtftn instance void ssb::OnClick(class System.Object, class
[mscorlib]System.EventArgs)
newobj instance void
[mscorlib]System.EventHandler::.ctor(class System.Object, int32)
stfld class
[mscorlib]System.EventHandler ssb::onClickEventHandler
ldarg.0
dup
ldfld class
[mscorlib]System.EventHandler ssb::onClickEventHandler
call instance void
[System.WinForms]System.WinForms.Button::add_Click(class
[mscorlib]System.EventHandler)
ret
}
.method virtual newslot public instance void OnClick(class
System.Object, class [mscorlib]System.EventArgs)
{
ldc.i4.1
call int8 User32::MessageBeep(unsigned int32)
pop
ret
}
}
.class abstract sealed public auto autochar User32 extends
[mscorlib]System.Object
{
.method public static pinvokeimpl("user32.dll" cdecl)
int8 MessageBeep(unsigned int32) native unmanaged
{
}
}
In this example we simply
display a button sans all the bells and whistles. When we click on this button,
function OnClick merely gets called. In this function, we simply call another
function MessageBeep that rings a bell
or simply produces a beep sound. This function is present in a dll called
user32.dll. In the file countdown.il, no new code has been added. Modifications
have only been made in the file aaa.il.
After the control is registered
with WinForms, the this pointer is placed thrice on the stack. Also, the
address of the virtual function OnClick is loaded on the stack.
ldarg.0
dup
dup
ldvirtftn instance void ssb::OnClick(class System.Object, class
[mscorlib]System.EventArgs)
This function is called each
time the button is clicked on. The dup instruction is placed on the stack to
account for the parameters to the function OnClick.
Simultaneously, a new object is
created that is an instance of the class EventHandler.
newobj instance void
[mscorlib]System.EventHandler::.ctor(class System.Object, int32)
stfld class
[mscorlib]System.EventHandler ssb::onClickEventHandler
This newly created object is
stored in the field onClickEventHandler. Then function add_Click from the
button class registers this function with Winforms.
ldfld class
[mscorlib]System.EventHandler ssb::onClickEventHandler
call instance void
[System.WinForms]System.WinForms.Button::add_Click(class
[mscorlib]System.EventHandler)
Now, the function OnClick gets
called with a click on the button and a static function MessageBeep is
executed. The code for this function is not available as the attributes on the
function are native. This implies that
the developers have supplied the
code and the function will be executed in the unmanaged state. To call a
function from a dll, pinvokeimpl is used, stating the name of the dll and the
calling convention.
The program countdown.il remains
the same for the next program. The modified file aaa.il is given below.
aaa.il
.module aaa.dll
.assembly extern mscorlib {}
.assembly extern System.Timers {}
.assembly extern System.WinForms {}
.assembly extern System.Drawing {}
.file CountDown.exe
.module extern CountDown.exe
.class abstract sealed public auto autochar User32 extends
[mscorlib]System.Object
{
.method public static pinvokeimpl("user32.dll" cdecl)
int8 MessageBeep(unsigned int32) native unmanaged {}
}
.class public ssb extends
[System.WinForms]System.WinForms.Button
{
.field class
[mscorlib]System.EventHandler onClickEventHandler
.method instance void .ctor(class
[System.WinForms]System.WinForms.Form parent)
{
ldarg.0
call instance void [System.WinForms]System.WinForms.Button::.ctor()
ldarg.0
ldstr "Start"
callvirt instance void
[System.WinForms]System.WinForms.Button::set_Text(class System.String)
ldarg parent
call instance class
[System.WinForms]System.WinForms.Control$ControlCollection [System.WinForms]System.WinForms.Form::get_Controls()
ldarg.0
callvirt instance void
[System.WinForms]System.WinForms.Control$ControlCollection::Add(class
[System.WinForms]System.WinForms.Control)
ldarg.0
dup
dup
ldvirtftn instance void ssb::OnClick(class System.Object, class
[mscorlib]System.EventArgs)
newobj instance void
[mscorlib]System.EventHandler::.ctor(class System.Object, int32)
stfld class
[mscorlib]System.EventHandler ssb::onClickEventHandler
ldarg.0
dup
ldfld class
[mscorlib]System.EventHandler ssb::onClickEventHandler
call instance void
[System.WinForms]System.WinForms.Button::add_Click(class
[mscorlib]System.EventHandler)
ret
}
.method virtual newslot public instance void OnClick(class
System.Object, class [mscorlib]System.EventArgs)
{
.locals (class [System.Timers]System.Timers.Timer V_0)
newobj instance void
[System.Timers]System.Timers.Timer::.ctor()
stloc.0
ldloc.0
ldnull
ldftn void ssb::OnTimedEvent(class System.Object,class
[mscorlib]System.EventArgs)
newobj instance void [mscorlib]System.EventHandler::.ctor(class
System.Object,int32)
call instance void
[System.Timers]System.Timers.Timer::add_Tick(class
[mscorlib]System.EventHandler)
ldloc.0
ldc.r8 300.
call instance void
[System.Timers]System.Timers.Timer::set_Interval(float64)
ldloc.0
ldc.i4.1
callvirt instance void
[System.Timers]System.Timers.Timer::set_Enabled(bool)
ret
}
.method public hidebysig static void OnTimedEvent(class System.Object source,class
[mscorlib]System.EventArgs e) il managed
{
ldc.i4.1
call int8 User32::MessageBeep(unsigned int32)
pop
ret
}
}
In this program, when the button
is clicked on, for 3 seconds nothing happens. Thereafter, beeps are heard. Then
on, after every 3 seconds, a beep sound is heard. This is a program that does
nothing for three seconds and then activates some code.
These programs are timer-based.
The program aaa.il has only one
change incorporated in it within the OnClick function. A local is declared that
looks like class Timer and an object like class Timer is created using newobj.
.locals (class [System.Timers]System.Timers.Timer V_0)
newobj instance void
[System.Timers]System.Timers.Timer::.ctor()
The value returned is stored in
the local V_0. Thereafter, the Timer object is again loaded on the stack,
followed by a NULL reference and the address of a static function OnTimedEvent.
ldloc.0
ldnull
ldftn void ssb::OnTimedEvent(class System.Object,class
[mscorlib]System.EventArgs)
This function is repeatedly
called after a certain time period has elapsed.
Concurrently, an object that
looks like an EventHandler is created.
newobj instance void [mscorlib]System.EventHandler::.ctor(class
System.Object,int32)
This constructor, in addition to
the this pointer, needs two more parameters, the second being the address of
our function.
call instance void [System.Timers]System.Timers.Timer::add_Tick(class
[mscorlib]System.EventHandler)
ldloc.0
The add_Tick function then
incorporates these changes and stores the handle in the local variable.
So, the timeout period i.e. the
time duration after which the function is to be called, is placed on the stack.
This number is a float, and hence, 8 bytes are allocated on the stack for it.
The set_Interval function sets the timeout period and the set_Enabled function
sets the timer on, which runs periodically.
ldc.r8 300.
call instance void
[System.Timers]System.Timers.Timer::set_Interval(float64)
Add the following lines of code
to the end of the function OnClick, just before the ret instruction.
ldloc.0
ldc.i4.0
callvirt instance void
[System.Timers]System.Timers.Timer::set_AutoReset(bool)
This code calls the function
set_AutoReset with the number 1, so that, the timeout function gets called over
and over again.
countdown.il
.assembly CountDown {}
.assembly extern System.WinForms {}
.assembly extern System.Drawing {}
.file aaa.dll
.module extern aaa.dll
.class zzz extends
[System.WinForms]System.WinForms.Form
{
.field class [.module
aaa.dll]ctb counterBox
.method public static
void Main()
{
.entrypoint
newobj instance void
zzz::.ctor()
call void [System.WinForms]System.WinForms.Application::Run(class
[System.WinForms]System.WinForms.Form)
ret
}
.method instance void
.ctor()
{
ldarg.0
call instance void
[System.WinForms]System.WinForms.Form::.ctor()
ldarg.0
dup
newobj instance void
[.module aaa.dll]ctb::.ctor(class zzz)
stfld class [.module
aaa.dll]ctb zzz::counterBox
ldarg.0
ldarg.0
ldfld class [.module aaa.dll]ctb zzz::counterBox
newobj instance void
[.module aaa.dll]ssb::.ctor(class [System.WinForms]System.WinForms.Form, class
[.module aaa.dll]ctb)
pop
ret
}
}
aaa.il
.module aaa.dll
.assembly extern System.Timers {}
.assembly extern mscorlib {}
.assembly extern System.WinForms {}
.assembly extern System.Drawing {}
.file CountDown.exe
.module extern CountDown.exe
.class public ctb extends [System.WinForms]System.WinForms.TextBox
{
.field int32 count
.method instance void .ctor(class [.module CountDown.exe]zzz
parent)
{
ldarg.0
call instance void
[System.WinForms]System.WinForms.TextBox::.ctor()
ldarg.0
ldc.i4 3
stfld int32 ctb::count
.locals (value class [System.Drawing]System.Drawing.Point point)
ldloca point
ldc.i4 75
ldc.i4 100
call instance void
[System.Drawing]System.Drawing.Point::.ctor(int32, int32)
ldarg.0
ldloc point
call instance void [System.WinForms]System.WinForms.TextBox::set_Location(value
class [System.Drawing]System.Drawing.Point)
ldarg.0
ldarg.0
ldfld int32 ctb::count
call class System.String [mscorlib]System.Int32::ToString(int32)
callvirt instance void
[System.WinForms]System.WinForms.TextBox::set_Text(class System.String)
ldarg parent
call instance class
[System.WinForms]System.WinForms.Control$ControlCollection
[System.WinForms]System.WinForms.Form::get_Controls()
ldarg.0
callvirt instance void
[System.WinForms]System.WinForms.Control$ControlCollection::Add(class
[System.WinForms]System.WinForms.Control)
ret
}
.method virtual newslot instance void SetCount(int32 count1)
{
ldarg.0
ldarg count1
call class System.String [mscorlib]System.Int32::ToString(int32)
callvirt instance void [System.WinForms]System.WinForms.TextBox::set_Text(class
System.String)
ret
}
.method virtual newslot instance int32 GetCount()
{
ldarg.0
callvirt instance class System.String ctb::get_Text()
callvirt instance int32 [mscorlib]System.String::ToInt32()
ret
}
}
.class public ssb extends
[System.WinForms]System.WinForms.Button
{
.field class
[mscorlib]System.EventHandler onClickEventHandler
.field class ctb par1
.method instance void .ctor(class
[System.WinForms]System.WinForms.Form parent,class ctb aa)
{
ldarg.0
call instance void
[System.WinForms]System.WinForms.Button::.ctor()
ldarg.0
ldarg.2
stfld class ctb ssb::par1
ldarg.0
ldstr "Start"
callvirt instance void
[System.WinForms]System.WinForms.Button::set_Text(class System.String)
ldarg parent
call instance class
[System.WinForms]System.WinForms.Control$ControlCollection
[System.WinForms]System.WinForms.Form::get_Controls()
ldarg.0
callvirt instance void
[System.WinForms]System.WinForms.Control$ControlCollection::Add(class
[System.WinForms]System.WinForms.Control)
ldarg.0
dup
dup
ldvirtftn instance void ssb::OnClick(class System.Object, class
[mscorlib]System.EventArgs)
newobj instance void
[mscorlib]System.EventHandler::.ctor(class System.Object, int32)
stfld class
[mscorlib]System.EventHandler ssb::onClickEventHandler
ldarg.0
dup
ldfld class
[mscorlib]System.EventHandler ssb::onClickEventHandler
call instance void
[System.WinForms]System.WinForms.Button::add_Click(class
[mscorlib]System.EventHandler)
ret
}
.method virtual newslot public instance void OnClick(class
System.Object, class [mscorlib]System.EventArgs)
{
.locals ( int32 i)
ldarg.0
ldfld class ctb ssb::par1
callvirt instance int32 ctb::GetCount()
ldc.i4.1
sub
stloc.0
ldc.i4.0
ldloc.0
bgt a1
ldarg.0
ldfld class ctb ssb::par1
ldloc.0
callvirt instance void ctb::SetCount(int32)
a1:
ret
}
}
The above program simply
displays the number 3 in the edit box. With every click on the button, the
number decreases by 1. When the value becomes 0, the execution of the program
stops.
Let us now understand as to what
goes behind writing such a program.
In countdown.il, a field
counterBox is created that stores the reference of the text box. Since the
reference value is saved in a field, any method can access the text field if it
possesses this value. Consider that any call to a function in the text box
class ctb, or access to the value stored in the text box, needs the reference
of the text box on the stack.
At first, a text box object is
created and the value is stored in
counterBox. Then this reference to the text box is passed on to
the constructor of the button class, as the second parameter. The button can
now call any methods from the text box class by simply placing this reference
on the stack. The button constructor stores the address of this text box in a
field for later use. A point to note here is that all the contents of a method
die at the end of the method whereas, fields are perpetual.
The constructor of the text box
is well explained before. The first change incorporated is in the constructor
of the class button, i.e. ssb. Here, the this pointer is placed on the stack,
and then, using ldarg.2 the reference of the button is also placed on the
stack. Thereafter, the reference of the text box is stored in the field par1.
ldarg.0
ldarg.2
stfld class ctb ssb::par1
The rest of the code ensures
that the function OnClick gets called each time the button is clicked.
In function OnClick, a local
int32 is created to store the current value of the text box. After that, the
this pointer is placed on the stack, and the value of the field par1 is
retrieved. Using the reference to the text box on the stack, function GetCount
from class ctb is called.
.locals ( int32 i)
ldarg.0
ldfld class ctb ssb::par1
callvirt instance int32 ctb::GetCount()
This function places its this
pointer and par1 on the stack, and then calls the virtual function get_Text
from the textbox class.
.method virtual newslot instance int32 GetCount()
{
ldarg.0
callvirt instance class System.String ctb::get_Text()
callvirt instance int32 [mscorlib]System.String::ToInt32()
ret
}
This function places a string,
representing the text within the text box, on the stack. This string is
converted into a number by calling the function ToInt32, which resides in the
String class. GetCount returns this number on the stack as the value of the
text box.
After placing the number 1 on
the stack, sub is called.
ldc.i4.1
sub
stloc.0
This instruction now subtracts 1
from the value present earlier on the stack. The number happens to be the value
stored in the text box. This new value is stored in the local i to make our
programming easier.
Since IL has no equivalent of
the if statement, the bgt instruction is used to compare two values. 0 is
placed on the stack, followed by the value of the variable i.
ldc.i4.0
ldloc.0
bgt a1
If the value of i is zero, the
bgt instruction jumps to label a1, which is at the end of the function. If i
has a value of 2, then no jump takes place as the second value happens to be
larger than the first.
The this pointer or reference of
the text box is again pushed on the stack, followed by the new value of i and
then, the function SetCount is called. This function simply changes the value
displayed in the text box.
ldarg.0
ldfld class ctb ssb::par1
ldloc.0
callvirt instance void ctb::SetCount(int32)
This function loads the
parameter passed, i.e. count1, on the stack, and uses ToString from the int32
class to convert it into a string. The string is placed on the stack. Finally
set_Text is called to change the value displayed. This function is the reverse
of the function GetCount.
.method virtual newslot instance void SetCount(int32 count1)
{
ldarg.0
ldarg count1
call class System.String [mscorlib]System.Int32::ToString(int32)
callvirt instance void
[System.WinForms]System.WinForms.TextBox::set_Text(class System.String)
ret
}
The last part of the code only
gets called if the value of the local i is positive.
There is no change in program
countdown.il and aaa.il file resembles
as shown below.
aaa.il
.module aaa.dll
.assembly extern System.Timers {}
.assembly extern mscorlib {}
.assembly extern System.WinForms {}
.assembly extern System.Drawing {}
.file CountDown.exe
.module extern CountDown.exe
.class public ctb extends
[System.WinForms]System.WinForms.TextBox
{
.field int32 count
.method instance void .ctor(class [.module CountDown.exe]zzz
parent)
{
ldarg.0
call instance void
[System.WinForms]System.WinForms.TextBox::.ctor()
ldarg.0
ldc.i4 3
stfld int32 ctb::count
.locals (value class [System.Drawing]System.Drawing.Point point)
ldloca point
ldc.i4 75
ldc.i4 100
call instance void
[System.Drawing]System.Drawing.Point::.ctor(int32, int32)
ldarg.0
ldloc point
call instance void
[System.WinForms]System.WinForms.TextBox::set_Location(value class
[System.Drawing]System.Drawing.Point)
ldarg.0
ldarg.0
ldfld int32 ctb::count
call class System.String [mscorlib]System.Int32::ToString(int32)
callvirt instance void
[System.WinForms]System.WinForms.TextBox::set_Text(class System.String)
ldarg parent
call instance class [System.WinForms]System.WinForms.Control$ControlCollection
[System.WinForms]System.WinForms.Form::get_Controls()
ldarg.0
callvirt instance void
[System.WinForms]System.WinForms.Control$ControlCollection::Add(class
[System.WinForms]System.WinForms.Control)
ret
}
.method virtual newslot instance void SetCount(int32 count1)
{
ldarg.0
ldarg count1
call class System.String [mscorlib]System.Int32::ToString(int32)
callvirt instance void
[System.WinForms]System.WinForms.TextBox::set_Text(class System.String)
ret
}
.method virtual newslot instance int32 GetCount()
{
ldarg.0
callvirt instance class System.String ctb::get_Text()
callvirt instance int32 [mscorlib]System.String::ToInt32()
ret
}
}
.class public ssb extends
[System.WinForms]System.WinForms.Button
{
.field class
[mscorlib]System.EventHandler onClickEventHandler
.field class ctb par1
.field class [System.Timers]System.Timers.Timer timer
.method instance void .ctor(class
[System.WinForms]System.WinForms.Form parent,class ctb aa)
{
ldarg.0
call instance void
[System.WinForms]System.WinForms.Button::.ctor()
ldarg.0
ldarg.2
stfld class ctb ssb::par1
ldarg.0
ldstr "Start"
callvirt instance void
[System.WinForms]System.WinForms.Button::set_Text(class System.String)
ldarg parent
call instance class [System.WinForms]System.WinForms.Control$ControlCollection
[System.WinForms]System.WinForms.Form::get_Controls()
ldarg.0
callvirt instance void
[System.WinForms]System.WinForms.Control$ControlCollection::Add(class
[System.WinForms]System.WinForms.Control)
ldarg.0
dup
dup
ldvirtftn instance void ssb::OnClick(class System.Object, class
[mscorlib]System.EventArgs)
newobj instance void
[mscorlib]System.EventHandler::.ctor(class System.Object, int32)
stfld class
[mscorlib]System.EventHandler ssb::onClickEventHandler
ldarg.0
dup
ldfld class
[mscorlib]System.EventHandler ssb::onClickEventHandler
call instance void
[System.WinForms]System.WinForms.Button::add_Click(class
[mscorlib]System.EventHandler)
ret
}
.method virtual newslot public instance void OnClick(class System.Object,
class [mscorlib]System.EventArgs)
{
.locals (class [System.Timers]System.Timers.Timer V_0)
newobj instance void
[System.Timers]System.Timers.Timer::.ctor()
stloc.0
ldloc.0
ldarg.0
stfld class [System.Timers]System.Timers.Timer ssb::timer
ldloc.0
ldarg.1
ldftn instance void ssb::OnTimedEvent(class System.Object,class
[mscorlib]System.EventArgs)
newobj instance void
[mscorlib]System.EventHandler::.ctor(class System.Object,int32)
call instance void
[System.Timers]System.Timers.Timer::add_Tick(class
[mscorlib]System.EventHandler)
ldloc.1
ldc.r8 500.
call instance void
[System.Timers]System.Timers.Timer::set_Interval(float64)
ldloc.1
ldc.i4.1
callvirt instance void
[System.Timers]System.Timers.Timer::set_Enabled(bool)
ret
}
.method public instance void OnTimedEvent(class System.Object
source,class [mscorlib]System.EventArgs e) il managed
{
.locals ( int32 i)
ldarg.0
ldfld class ctb ssb::par1
callvirt instance int32 ctb::GetCount()
ldc.i4.1
sub
stloc.0
ldc.i4.0
ldloc.0
bgt a1
ldarg.0
ldfld class ctb ssb::par1
ldloc.0
callvirt instance void ctb::SetCount(int32)
br a2
a1:
ldarg.0
ldfld class [System.Timers]System.Timers.Timer ssb::timer
call instance void
[System.Timers]System.Timers.Timer::Stop()
a2:
ldc.i4.1
call int8 User32::MessageBeep(unsigned int32)
pop
ret
}
}
.class abstract sealed public auto autochar User32 extends
[mscorlib]System.Object
{
.method public static pinvokeimpl("user32.dll" cdecl)
int8 MessageBeep(unsigned int32) native unmanaged {}
}
In this program, the numbers
change automatically with every click on the button. The program stops when the
value becomes 0. The beep sound also stops. The class ctb and the constructor
of class ssb remains the same. It calls the function OnClick at the press of a
mouse.
The function OnClick does things
differently. Using stfld, the timer object is first stored in a field called
timer.
stfld class [System.Timers]System.Timers.Timer ssb::timer
ldloc.0
This is because, a function from
the timer class is to be called. The same value is saved in a local V_0. This
is a poor programming style, but nobody's looking. The timer object calls the
function OnTimedEvent periodically.
Earlier the function was static
but now it is an instance function and it is given the this pointer instead of
a NULL. The code for the timer tick in the earlier program has been assigned to
the button click. The only change is that the text box value on attaining ZERO
will stop the timer. This routine is employed with the function Stop from the
timer class. It is given the timer reference on the stack
ldfld class [System.Timers]System.Timers.Timer ssb::timer
call instance void
[System.Timers]System.Timers.Timer::Stop()
Let us put together all that we
have learnt so far and write the largest program in our book. This program
should be followed up by reading the same program in the IL documentation. It
is relatively larger and spread over more files. Let us start from the very
beginning.
countdown.il
.assembly CountDown {}
.assembly extern System.WinForms {}
.assembly extern System.Drawing {}
.file aaa.dll
.module extern aaa.dll
.class zzz extends
[System.WinForms]System.WinForms.Form
{
.field class [.module
aaa.dll]ctb counterBox
.field class [.module
aaa.dll]ssb button
.field class [.module
aaa.dll]Counter counter
.method public static
void Main()
{
.entrypoint
newobj instance void
zzz::.ctor()
call void
[System.WinForms]System.WinForms.Application::Run(class
[System.WinForms]System.WinForms.Form)
ret
}
.method instance void
.ctor()
{
.locals (class [.module
aaa.dll]Count count)
ldarg.0
call instance void
[System.WinForms]System.WinForms.Form::.ctor()
ldarg.0
ldarg.0
newobj instance void
[.module aaa.dll]ssb::.ctor(class [System.WinForms]System.WinForms.Form)
stfld class [.module aaa.dll]ssb zzz::button
ldarg.0
dup
newobj instance void
[.module aaa.dll]ctb::.ctor(class zzz)
stfld class [.module
aaa.dll]ctb zzz::counterBox
ldarg.0
ldfld class [.module aaa.dll]ctb zzz::counterBox
newobj instance void [.module aaa.dll]Count::.ctor(class
[.module aaa.dll]ICountDisplay)
stloc count
ldarg.0
dup
ldfld class [.module
aaa.dll]ssb zzz::button
ldloc count
newobj instance void
[.module aaa.dll]BeepingCounter::.ctor(class [.module aaa.dll]IStartStopEventSource,
class [.module aaa.dll]Count)
stfld class [.module
aaa.dll]Counter zzz::counter
ldarg.0
ldfld class [.module aaa.dll]ssb zzz::button
ldarg.0
ldfld class [.module aaa.dll]Counter zzz::counter
call instance void [.module aaa.dll]ssb::AddToTimeUp(class
[.module aaa.dll]Counter)
ret
}
}
aaa.il
.module aaa.dll
.assembly extern System.Timers {}
.assembly extern mscorlib {}
.assembly extern System.WinForms {}
.assembly extern System.Drawing {}
.file CountDown.exe
.module extern CountDown.exe
.class public ctb extends
[System.WinForms]System.WinForms.TextBox implements ICountDisplay
{
.field class [.module CountDown.exe]zzz parent
.method instance void .ctor(class [.module CountDown.exe]zzz
parent)
{
ldarg.0
call instance void
[System.WinForms]System.WinForms.TextBox::.ctor()
ldarg.0
ldarg parent
stfld class [.module CountDown.exe]zzz ctb::parent
.locals (value class [System.Drawing]System.Drawing.Point point)
ldloca point
ldc.i4 75
ldc.i4 100
call instance void
[System.Drawing]System.Drawing.Point::.ctor(int32, int32)
ldarg.0
ldloc point
call instance void
[System.WinForms]System.WinForms.TextBox::set_Location(value class
[System.Drawing]System.Drawing.Point)
ldarg parent
call instance class [System.WinForms]System.WinForms.Control$ControlCollection
[System.WinForms]System.WinForms.Form::get_Controls()
ldarg.0
callvirt instance void
[System.WinForms]System.WinForms.Control$ControlCollection::Add(class
[System.WinForms]System.WinForms.Control)
ret
}
.method virtual newslot instance void SetCount(int32 count)
{
ldarg.0
ldarg count
call class System.String [mscorlib]System.Int32::ToString(int32)
callvirt instance void
[System.WinForms]System.WinForms.TextBox::set_Text(class System.String)
ret
}
.method virtual newslot instance int32 GetCount()
{
ldarg.0
callvirt instance class System.String ctb::get_Text()
callvirt instance int32 [mscorlib]System.String::ToInt32()
ret
}
}
.data COUNTER_DEFAULT = int32(3)
.class interface abstract public auto autochar ICountDisplay
{
.method virtual abstract public hidebysig instance void
SetCount(int32 count) il managed {}
.method virtual abstract public hidebysig instance int32
GetCount() il managed {}
}
.class interface abstract auto autochar public IStartStopEventSource
{
.method virtual abstract public hidebysig instance void
add_StartStopEvent(class StartStopEventHandler) il managed {}
}
.class public Count extends [mscorlib]System.Object
{
.field int32 count
.field static family int32 counterDefault at COUNTER_DEFAULT
.field class
ICountDisplay display
.method public instance void .ctor(class ICountDisplay display)
{
ldarg.0
call instance void [mscorlib]System.Object::.ctor()
ldarg.0
ldarg display
stfld class
ICountDisplay Count::display
ldarg.0
ldsfld int32
Count::counterDefault
callvirt instance void Count::set_Count(int32)
ret
}
.property int32 Count()
{
.backing int32 count
.get instance int32 get_Count()
.set instance void set_Count(int32)
.other instance void refresh_Count()
}
.method virtual newslot instance int32 get_Count()
{
ldarg.0
ldfld int32
Count::count
ret
}
.method virtual newslot instance void set_Count(int32 newCount)
synchronized
{
ldarg.0
ldarg newCount
stfld int32
Count::count
ldarg.0
ldfld class
ICountDisplay Count::display
ldarg newCount
callvirt instance void ICountDisplay::SetCount(int32)
ret
}
.method virtual newslot instance void refresh_Count()
synchronized
{
ldarg.0
dup
ldfld class
ICountDisplay Count::display
callvirt instance int32 ICountDisplay::GetCount()
stfld int32
Count::count
ret
}
}
.class public Counter extends [mscorlib]System.Object
{
.field class [System.Timers]System.Timers.Timer timer
.field class
[mscorlib]System.EventHandler timerEventHandler
.field class Count count
.field class
IStartStopEventSource startStopEventSource
.field class
StartStopEventHandler startStopEventHandler
.field class
TimeUpEventHandler timeUpEventHandler
.method instance void .ctor(class IStartStopEventSource startStopEventSource,
class Count count)
{
ldarg.0
call instance void [mscorlib]System.Object::.ctor()
ldarg.0
ldarg
startStopEventSource
stfld class
IStartStopEventSource Counter::startStopEventSource
ldarg.0
ldarg count
stfld class Count
Counter::count
ldarg.0
callvirt instance void Counter::SetupTimer()
ldarg.0
callvirt instance void Counter::SetupStartStopEvent()
ret
}
.method virtual newslot instance void SetupTimer()
{
ldarg.0
ldc.r8 1000
newobj instance void
[System.Timers]System.Timers.Timer::.ctor(float64)
stfld class
[System.Timers]System.Timers.Timer Counter::timer
ldarg.0
ldfld class
[System.Timers]System.Timers.Timer Counter::timer
ldc.i4.1
call instance void
[System.Timers]System.Timers.Timer::set_AutoReset(bool)
ldarg.0
dup
dup
ldvirtftn instance void Counter::OnTick(class System.Object,
class [mscorlib]System.EventArgs)
newobj instance void
[mscorlib]System.EventHandler::.ctor(class System.Object, int32)
stfld class [mscorlib]System.EventHandler
Counter::timerEventHandler
ldarg.0
ldfld class
[System.Timers]System.Timers.Timer Counter::timer
ldarg.0
ldfld class
[mscorlib]System.EventHandler Counter::timerEventHandler
call instance void
[System.Timers]System.Timers.Timer::add_Tick(class
[mscorlib]System.EventHandler)
ret
}
.method virtual newslot instance instance void
SetupStartStopEvent()
{
ldarg.0
dup
ldftn instance void
Counter::OnStartStop(int32)
newobj instance void
StartStopEventHandler::.ctor(class System.Object, int32)
stfld class
StartStopEventHandler Counter::startStopEventHandler
ldarg.0
ldfld class
IStartStopEventSource Counter::startStopEventSource
ldarg.0
ldfld class
StartStopEventHandler Counter::startStopEventHandler
callvirt instance void
IStartStopEventSource::add_StartStopEvent(class StartStopEventHandler)
ret
}
.method instance void OnStartStop(int32 action)
{
ldarg action
brtrue start
ldarg.0
call instance void
Counter::Stop()
br done
start:
ldarg.0
call instance void
Counter::Start()
done:
ret
}
.method private hidebysig instance void Start() il managed {
ldarg.0
ldfld class Count Counter::count
callvirt instance void Count::refresh_Count()
ldarg.0
ldfld class Count Counter::count
callvirt instance int32 Count::get_Count()
ldc.i4.0
ble do_not_start
ldarg.0
ldfld class [System.Timers]System.Timers.Timer Counter::timer
call instance void [System.Timers]System.Timers.Timer::Start()
br done
do_not_start:
ldarg.0
callvirt instance void Counter::fire_TimeUpEvent()
done:
ret
}
.method private hidebysig instance void Stop()
{
ldarg.0
ldfld class
[System.Timers]System.Timers.Timer Counter::timer
call instance void
[System.Timers]System.Timers.Timer::Stop()
ret
}
.method virtual newslot family hidebysig instance void
OnTick(class System.Object, class [mscorlib]System.EventArgs) il managed {
ldarg.0
ldfld class Count
Counter::count
dup
callvirt instance int32 Count::get_Count()
ldc.i4.1
sub
callvirt instance void Count::set_Count(int32)
ldarg.0
ldfld class Count
Counter::count
callvirt instance int32 Count::get_Count()
ldc.i4.0
ble time_up
br done
time_up:
ldarg.0
call instance void
Counter::Stop()
ldarg.0
callvirt instance void Counter::fire_TimeUpEvent()
done:
ret
}
.event TimeUpEventHandler TimeUpEvent
{
.addon instance void add_TimeUp(class TimeUpEventHandler
'handler')
.removeon instance void remove_TimeUp(class TimeUpEventHandler
'handler')
.fire instance void fire_TimeUpEvent()
}
.method virtual newslot instance void add_TimeUp(class
TimeUpEventHandler 'handler') il managed {
ldarg.0
dup
ldfld class
TimeUpEventHandler Counter::timeUpEventHandler
ldarg 'handler'
call
class[mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class
[mscorlib]System.Delegate, class [mscorlib]System.Delegate)
castclass TimeUpEventHandler
stfld class
TimeUpEventHandler Counter::timeUpEventHandler
ret
}
.method virtual newslot instance void remove_TimeUp(class
TimeUpEventHandler 'handler') il managed {ret}
.method virtual newslot instance void fire_TimeUpEvent()
{
ldarg.0
ldfld class
TimeUpEventHandler Counter::timeUpEventHandler
callvirt instance void TimeUpEventHandler::Invoke()
ret
}
}
.class public BeepingCounter extends Counter
{
.method instance void .ctor(class IStartStopEventSource
startStopEventSource, class Count count) il managed {
ldarg.0
ldarg
startStopEventSource
ldarg count
call instance void
Counter::.ctor(class IStartStopEventSource, class Count)
ret
}
.method virtual instance void OnTick(class System.Object object,
class [mscorlib]System.EventArgs eventArgs)
{
ldarg.0
ldarg object
ldarg eventArgs
call instance void Counter::OnTick(class System.Object, class
[mscorlib]System.EventArgs)
ldarg.0
dup
ldfld class Count
Counter::count
callvirt instance int32 Count::get_Count()
ldc.i4.0
ble final_beep
ldc.i4.0
br beep_it
final_beep:
ldc.i4.1
beep_it:
callvirt instance void BeepingCounter::Beep(bool)
ret
}
.method virtual newslot instance void Beep(bool finalBeep)
{
ldarg finalBeep
brtrue 'final'
ldc.i4.0
br continue
'final':
ldc.i4 48
continue:
call int8
User32::MessageBeep(unsigned int32)
pop
ret
}
}
.class abstract sealed public auto autochar User32 extends
[mscorlib]System.Object {
.method public static pinvokeimpl("user32.dll" cdecl)
int8 MessageBeep(unsigned int32) native unmanaged {}
}
.class private sealed auto autochar StartStopEventHandler
extends [mscorlib]System.MulticastDelegate {
.method public specialname rtspecialname hidebysig instance void
.ctor(class System.Object object, int32 'method') runtime managed {}
.method virtual newslot public hidebysig instance void
Invoke(int32 action) runtime managed {}
.method virtual newslot public hidebysig instance class
['mscorlib']System.IAsyncResult BeginInvoke(int32 action,class
['mscorlib']System.AsyncCallback callback, class System.Object object) runtime
managed {}
.method virtual newslot
public hidebysig instance void EndInvoke(class
['mscorlib']System.IAsyncResult result) runtime managed {}
}
.class private sealed auto autochar TimeUpEventHandler extends
[mscorlib]System.MulticastDelegate {
.method public specialname rtspecialname hidebysig instance void
.ctor(class System.Object object, int32 'method') runtime managed {}
.method virtual newslot public hidebysig instance void Invoke()
runtime managed {}
.method virtual newslot public newslot hidebysig instance class
['mscorlib']System.IAsyncResult BeginInvoke(class
['mscorlib']System.AsyncCallback callback, class System.Object object) runtime
managed {}
.method virtual newslot public hidebysig instance void
EndInvoke(class ['mscorlib']System.IAsyncResult result) runtime managed {}
}
.class public ssb extends
[System.WinForms]System.WinForms.Button implements IStartStopEventSource
{
.field class
[mscorlib]System.EventHandler onClickEventHandler
.field class
TimeUpEventHandler timeUpEventHandler
.field class
StartStopEventHandler startStopEventHandler
.field bool state
.method instance void .ctor(class
[System.WinForms]System.WinForms.Form parent)
{
ldarg.0
call instance void
[System.WinForms]System.WinForms.Button::.ctor()
ldarg.0
ldc.i4.0
stfld bool ssb::state
ldarg.0
ldstr "Start"
callvirt instance void
[System.WinForms]System.WinForms.Button::set_Text(class System.String)
ldarg parent
call instance class
[System.WinForms]System.WinForms.Control$ControlCollection
[System.WinForms]System.WinForms.Form::get_Controls()
ldarg.0
callvirt instance void
[System.WinForms]System.WinForms.Control$ControlCollection::Add(class
[System.WinForms]System.WinForms.Control)
ldarg.0
dup
dup
ldvirtftn instance void ssb::OnClick(class System.Object, class
[mscorlib]System.EventArgs)
newobj instance void
[mscorlib]System.EventHandler::.ctor(class System.Object, int32)
stfld class
[mscorlib]System.EventHandler ssb::onClickEventHandler
ldarg.0
dup
ldfld class
[mscorlib]System.EventHandler ssb::onClickEventHandler
call instance void
[System.WinForms]System.WinForms.Button::add_Click(class
[mscorlib]System.EventHandler)
ret
}
.method virtual newslot instance void SetState(int32 newState)
{
ldarg.0
ldarg newState
stfld bool ssb::state
ldarg newState
ldc.i4.0
beq stop_state
ldarg.0
ldstr "Stop"
callvirt instance void ssb::set_Text(class System.String)
br done
stop_state:
ldarg.0
ldstr "Start"
callvirt instance void ssb::set_Text(class System.String)
done:
ret
}
.method virtual newslot public instance void OnClick(class
System.Object, class [mscorlib]System.EventArgs)
{
ldarg.0
callvirt instance void ssb::fire_StartStopEvent()
ret
}
.method public instance void AddToTimeUp(class Counter counter)
{
ldarg.0
dup
ldftn instance void
ssb::OnTimeUp()
newobj instance void
TimeUpEventHandler::.ctor(class System.Object, int32)
stfld class
TimeUpEventHandler ssb::timeUpEventHandler
ldarg counter
ldarg.0
ldfld class
TimeUpEventHandler ssb::timeUpEventHandler
call instance void
Counter::add_TimeUp(class TimeUpEventHandler)
ret
}
.method virtual newslot instance void OnTimeUp()
{
ldarg.0
ldc.i4.0
callvirt instance void ssb::SetState(int32)
ret
}
.event StartStopEventHandler StartStopEvent
{
.addon instance void add_StartStopEvent(class
StartStopEventHandler 'handler')
.fire instance void fire_StartStopEvent()
}
.method virtual newslot instance void add_StartStopEvent(class
StartStopEventHandler 'handler')
{
ldarg.0
dup
ldfld class
StartStopEventHandler ssb::startStopEventHandler
ldarg 'handler'
call
class[mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class
[mscorlib]System.Delegate, class [mscorlib]System.Delegate)
castclass StartStopEventHandler
stfld class
StartStopEventHandler ssb::startStopEventHandler
ret
}
.method virtual newslot instance void fire_StartStopEvent()
{
ldarg.0
ldfld bool ssb::state
brtrue stop_it
ldarg.0
ldc.i4.1 // start
counter
callvirt instance void ssb::SetState(int32)
br continue
stop_it:
ldarg.0
ldc.i4.0 // stop
counter
callvirt instance void ssb::SetState(int32)
continue:
ldarg.0
ldfld class
StartStopEventHandler ssb::startStopEventHandler
ldarg.0
ldfld bool ssb::state
callvirt instance void StartStopEventHandler::Invoke(int32)
ret
}
}
Let us first start with the file
countdown.il. We will proceed extremely cautiously, in a step by step manner,
so that you can understand how to write a large IL program. Fields will be explained
only just prior to using them.
We start with the following
directives viz assembly, assembly extern, file and module.
In aaa.dll, class zzz extends
from the class Form as it is a Winforms application. The entrypoint method is
Main. An object that looks like zzz is created using newobj and it is then
placed on the stack. Using the function Run, that accepts a parameter that
looks like Form, a window is displayed. This function keeps executing until the
user closes the window.
But before this, the constructor
of class zzz puts in a lot of hard work.
The constructor of class zzz
first calls the constructor of class Form. It is necessary to call the base
class constructor here as there may be some initial routine to be executed. Why
take a chance?
While calling the constructor of
the base class or super class, the this pointer is placed on the stack, unlike
the instruction newobj.
Now to place a button on the
screen:
A new object that looks like the button class ssb is created. Even
though the constructor of this class needs only one parameter, the this pointer
is placed twice. This is so because the reference of the ssb object is to be
stored in a field button using the stfld instruction.
A quick look at the code of the
constructor of class ssb present in aaa.dll at runtime and file aaa.il.
The class ssb implements the
interface IStartStopEventSource. It is also derived from the Button class. This
interface has one function called add_StartStopEvent. The question that comes
to mind is: Why should we have an interface at all? The only advantage of
having it is that, an ssb object can now be referred to as an ssb object, or a
Button class or an interface. Any object that looks like IStartStopEventSource
will now be supplied with an ssb
object. This too can be overridden. Since ssb is derived from Button, it
contains all the functionality of a button object.
There is no bool data type in
IL. This data type is converted to a variable that will store either a 0 or 1.
Thus, the field state indicates whether the timer is on or off. As its value is
set to zero, the timer is currently off.
The label of the button is set to Start. Then using the Add function,
the button is registered with Winforms, to be displayed later.
With every click on the button,
the function OnClick from the ssb class is to be called. The function add_Click
is used to accomplish this task.
The next call made is to the
constructor of the ctb class and this value is stored in the field counterBox
within the class zzz. The class ctb represents a text box in the program. It is
derived from the class TextBox and it implements the interface ICountDisplay.
This interface has two members, SetCount and GetCount, whose job is to deal
with the number displayed in the text box.
We seem to be digressing from
the topic. Back again, the function set_Location from the TextBox class is
used to position the text box on the
screen and eventually the text box too is registered with Winforms.
Next, an object, an instance of
the class Count is created by passing the constructor a textbox reference,
disguised as ICountDisplay. This object is stored in the local count. The class
Count is a stand-alone class, since it is derived only from Object. Every class
may not explicitly derive from Object
A field display, that looks like
interface ICountDisplay is created and the textbox reference is saved in it.
Thus, the class Count uses this field, display, to get and set the text box at
will, since it now holds a reference to it.
We have also created a field
called counterDefault, that is passed a value directly from the data section of
the PE file. The .data directive uses the same words after the at, i.e.
COUNTER_DEFAULT, which we have initialized to the value 3. We could have also
used the instruction ldc to initialize this variable. We put its current value
3 on the stack and call the function set_Count from the class Count itself. The
number 3 is passed as a parameter newCount, to this function.
There is an int32 type field
called count in the class Count. At first the parameter newCount is saved in
this field and then the reference to the text box is stored in the field
display. Eventually, the function SetCount from class ctb is called; to be
precise it is ICountDisplay. This displays a number 3 in the textbox. The field
count stores the current displayed value.
Thus, function set_Count does
little work. It internally calls
SetCount from class ctb to do the real work. The function SetCount first loads
the parameter 3, passed to it, on the stack. It then converts it into a string
using the static function ToString from the int class, which places the string
on the stack. This string on the stack is used up by the set_Text function from
the TextBox class, to actually display the string in the text box.
While we are at it, let us also
understand the corresponding get functions. The function get_Count from the
Count class simply returns the value of the field count. GetCount from class
ctb, first uses function get_Text to place the string stored in the text box on
the stack. Then, the static function ToInt32 from the String class is called to
convert it into a number. In either case, the value is left on the stack. The
last function in class Count is refresh_Count. This function first places the
field display on the stack, and directly calls the virtual function GetCount
from the class ctb. The return value is stored in the field count.
The directive property is for
illustrative purposes only. It is used by tools to document the property. To
refresh your memory, in IL, a property is simply a function, but in other
languages like C#, properties have much more significance and make programming
simpler. The get and set directives have already been explained earlier.
The backing directive denotes a
field that will store the value of the property. In this case, we use count.
The other directive is for functions that are part of a property, but do not
cleanly fit in a get set world, like refresh_Count. As mentioned earlier, the
directive property is optional.
An object that looks like
BeepingCounter is the next in sequence to be created. Two parameters are given
to the constructor, one a button and the other a variable that represents a
count. The return value is held in a Counter field called counter. Note the
type is not a BeepingCounter.
The class BeepingCounter is
derived from Counter, which in turn is derived from Object. In the constructor
of BeepingCounter, both the entities: one is the button or
IStartStopEventSource interface and the other being count is loaded on the
stack. The constructor in the base class Counter is the subsequent routine to
be executed.
In the constructor:
A field called
startStopEventSource stores the button reference and count stores the count
reference. The function SetupTimer from the Counter class is called that
creates a timer object. The constructor of this object accepts the timer click
as a float parameter. This timer object is stored in a field called timer.
The property set_AutoReset to
set to true to ensure that a function called OnTick is called when the timer is
enabled. This is done by the function add_Tick of the timer class. All this
code has been explained in detail in the earlier programs.
The last function that this
constructor calls is SetupStartStopEvent, from the Counter class. In this function,
at first the address of a function called OnStartStop is placed on the stack. A
new object is created that is an instance of a class
StartStopEventHandler. This class is
derived from MulticastDelegate and is passed the address of a function that is
to be executed whenever the function Invoke is called.
This class is nothing but a
delegate and contains no code at all, since all the code of the functions is to
be supplied by the runtime. Thus, invoke will call the function OnStartStop.
The field startStopEventHandler now contains the reference of this delegate.
Two more parameters are placed on the stack, a button reference to call a
function add_StartStopEvent from class ssb or the interface it extends and the
next is a a parameter that the event handler just created above.
The function add_StartStopEvent
registers the earlier function with the runtime. If you recollect, in the
delegate chapter we had explained the importance of the function Combine. A
reserved word, when it is to be used as a parameter has to be placed in single inverted quotes, as in the case of
handler. It is casted to the correct class and stored in a field
startStopEventHandler within ssb.
To avoid any more confusion,
remember that a delegate simply calls a function indirectly. Thus, using this
reference, the function OnStartStop can be called.
The last act of the zzz
constructor is to place the button reference on the stack and call function
AddToTimeUp with a counter reference as a parameter. In this function, the address
of a function OnTimeUp is placed on the stack. Then an object is created which
is an instance of TimeUpEventHandler. This is a class derived from
MulticastDelegate, thus incorporating two delegates.
The function OnTimeUp is to be
called when Invoke is executed. This delegate is stored in field
TimeUpEventHandler in the ssb class. Finally add_TimeUp function is called with
a counter as a parameter. This function completes the delegate handling by
calling the Combine function and storing the reference in field
TimeUpEventHandler. Two functions have been registered so far. Every delegate
that is added, can also be removed. The function remove_TimeUp removes the
delegate.
Like a property, an event also
has a directive that has effect on compilers and tools only An event called
TimeUpEvent that is an instance of class TimeUpEventHandler is available which
has the usual .addon and .removeon directives and also a fire directive. The
latter one supplies information as to
which function would be called by invoke. This is used for documentation
purposes only. All the code that gets called, sets up the actual framework. The
action starts only when the button is clicked on. On doing so, the function
OnClick from class ssb gets called.
In function OnClick, another
function fire_StartStopEvent is called from the same class. In this function, a
check on the value of the field called state is maded. If you flip back a few
pages, it was given an initial value of zero or false. As its value is false,
the brtrue is not executed and a function called Set_State is called from class
ssb with the value 1 as a parameter.
In the function, the parameter
received becomes the new value of field state, whose value now changes from 0
to 1. This value and zero are placed on the stack and the instruction beq is
executed. A jump is made to the label
if the two parameters are equal or, in other words, the value of state is zero.
Being unequal for the moment, the string Stop is placed on the stack and
set_Text is called to change the label to Stop. The course proceeds to the
label done.
A round about turn to the
function fire_StartStopEvent and back to the label continue. The delegate
startStopEventHandler and the state of the button i.e. enabled or 1, is placed
on the stack and the Invoke function is called. This calls the function
OnStartStop in the class Counter.
This function checks the value
of the parameter passed to it. Since the value of field state is 1 on the
stack, the instruction brtrue jumps to the label start: and calls the function
Start. In other circumstances, the function Stop is called. Thus, OnStartStop
is called through Invoke and it will in turn either call the functions Start or
Stop, depending upon the parameter value passed, either a 1 or 0, respectively.
In the function Start,
refresh_Count refreshes the counter giving it a new value. If the value is less
than zero, the program is to be terminated, so, the label do_not_start: is
executed where the function fire_TimeUpEvent is called that ceases everything.
Presently, the timer is fired,
so that the function OnTick is called every 1000 milliseconds. To achieve this,
the function Start is called from the timer class.
The function OnTick is called
from the class BeepingCounter and not from the class Counter. Next, the
function OnTick is called from the class Counter. In function OnTick, which is
called very second, the new value of the counter is displayed. This is achieved
using the get_Count function. The value is decremented by 1 and set_Count
function restores this new value back to the variable.
A check is performed on the
value of count using ble. When zero, the timer is stopped by calling the
function Stop from the timer class.
The class BeepingCounter.
The function Beep is called
where the parameter is given to change the occurrence of beep sound. Obviously
it should beep only once and terminate when the value of count is 0.
In the function Beep, a value of
0 or 48 is placed , depending upon whether the parameter is 0 or 1. Then, the
actual MessageBeep function from class User32 is called for. This class is an
abstract class and has a static function MessageBeep that is of type
pinvokeimpl. This specifies that the code of this function is in user32.dll.
Finally, the value returned by get_Count evaluates to zero. This will result in
a call to Stop of the timer, so that the function OnTick is not called
thereafter. The OnTimeUp function calls function SetState with a value of 0.
This triggers off the shutdown procedure as explained above.
This is the most exhaustive
explanation of any program we have given so far.