Constructor, Destructor and ILASM


I find C# a very interesting language and it also comes handy to analyze ILASM code.To examine destructors and how to write custom destructor we will compile from command line the following:

class makecall
{
void print()
{
System.Console.WriteLine("Executing print.");
throw new System.NotImplementedException();
}
~makecall()
{
print();
}
}
class test
{
public static void Main()
{
makecall obj=
new makecall();
obj=
null;
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
}

That is familiar from C++. Few words about GC. If we comment out lines:

System.GC.Collect();
System.GC.WaitForPendingFinalizers();

and reassign new value to obj (insert that straight at the end):

obj =
new makecall();

during execution we will notice that both constructors are executed and only in the end both destructors. The idea comes from famous web site "How to write unmaintainable code" chapter about recycling variable names. Of course "noisy" constructor is needed to see that. Something like:

public makecall ()
{
System.Console.WriteLine("New instance created.");
}

Uncomment now GC lines and you will see proper order of execution ,first constructor and then the belonging destructor. And finally we can go to assembler. Send first version to ILDASM.EXE and you will notice that destructor have catch-finally shape. Something like:

.method family hidebysig virtual instance void
Finalize() cil managed
{
// Code size 20 (0x14)
.maxstack 8
.
try
{
IL_0000: ldstr "Instance destroyed."
IL_0005: call
void [mscorlib]System.Console::WriteLine(string)
IL_000a: leave.s IL_0013
}
// end .try
finally
{
IL_000c: ldarg.0
IL_000d: call instance
void [mscorlib]System.Object::Finalize()
IL_0012: endfinally
}
// end handler
IL_0013: ret
}
// end of method Recycle::Finalize

ILASM does not trust too much our capabilities to write custom destructor!

For more information about GC, Object.Finalize and Dispose, please check article "Gozer the Destructor" by Bobby chmidt in MSDN .NET documentation. Of course you will have to compile code and disassemble exe file. Also there you see in action  System.IDisposable (other way to work with GC).

But my goal is not to review that article and repeat what is already written there, I want to show you how to insert attach block and handle exception according to your own needs. Why would we want to do that? If we are writing component and we are going to do tests of that component and application which is using it, it will require logging of errors, warnings and other useful information. Standard quality assurance process.

To implement catch we are doing the same as with C# code. If we are dealing with exception it will be at top of the stack. This is the code:

.assembly extern mscorlib{}
.assembly 'custom'{}
.
class private auto ansi beforefieldinit makecall
extends [mscorlib]System.Object
{
.method
private hidebysig instance void
print() cil managed
{
ldstr "Executing print."
call
void [mscorlib]System.Console::WriteLine(string)
newobj instance
void [mscorlib]System.NotImplementedException::.ctor()
throw
}
.method family hidebysig
virtual instance void
Finalize() cil managed
{
.
try
{
.
try
{
ldarg.0
call instance
void makecall::print()
leave.s ex
}
catch [mscorlib]System.Exception
{
callvirt instance
string [mscorlib]System.Exception::get_Message()
call
void [mscorlib]System.Console::WriteLine(string)
leave.s ex
}
}
finally
{
ldarg.0
call instance
void [mscorlib]System.Object::Finalize()
endfinally
}
ex: ret
}
.method
public hidebysig specialname rtspecialname
instance
void .ctor() cil managed
{
ldarg.0
call instance
void [mscorlib]System.Object::.ctor()
ret
}
}
.method
public static void Main() cil managed
{
.entrypoint
newobj instance
void makecall::.ctor()
pop
call
void [mscorlib]System.GC::Collect()
call
void [mscorlib]System.GC::WaitForPendingFinalizers()
ret
}

Note nested try block. As always if you are careful to not unbalance stack ILASM is very friendly and helpful.

You will probably prefer to log error to file. Tip: declare static void where you will do logging and pass to it error description in string. Try it, it's not difficult and could be useful in your future work with .NET platform.