Wednesday, November 29, 2006 7:30 AM bart

ILASM - #define and .typedef

Two little-known features of ILASM (the IL assembler for .NET) are #define and .typedef which can reduce typing significantly, just as these do in a classic programming language. Often people do "round-tripping", i.e. they write an application in C#, ildasm it, make some slight modification, and ilasm it again. Now, in such a cycle you won't see all of the features of IL, such as #define and .typedef. So, if you're interested in IL, read on.

A few weeks ago I was creating a simple sample of "IL from scratch", meaning you take Notepad, write some IL and feed it to the ILASM tool. This was what I produced:

1 .assembly extern mscorlib {} 2 .assembly Demo {} 3 4 .namespace Bar.Foo 5 { 6 .class Demo 7 { 8 .field private static class Bar.Foo.Base printer 9 10 .method public static void Main() 11 { 12 .entrypoint 13 ldstr "Hello World" 14 newobj instance void Bar.Foo.Test::.ctor(string) 15 stsfld class Bar.Foo.Base Bar.Foo.Demo::printer 16 ldsfld class Bar.Foo.Base Bar.Foo.Demo::printer 17 call instance void Bar.Foo.Test::Print() 18 ret 19 } 20 } 21 22 .class abstract Base 23 { 24 .field family string name 25 26 .method public void .ctor(string name) 27 { 28 ldarg.0 //this 29 ldarg.1 //name 30 stfld string Bar.Foo.Base::name 31 ret 32 } 33 34 .method abstract virtual family void Print() 35 { 36 } 37 } 38 39 .class sealed Test extends Bar.Foo.Base 40 { 41 .method public void .ctor(string name) 42 { 43 ldarg.0 //this 44 ldarg.1 //name 45 call instance void Bar.Foo.Base::.ctor(string) 46 ret 47 } 48 49 .method public virtual void Print() 50 { 51 .override Bar.Foo.Base::Print 52 ldarg.0 //this 53 ldfld string Bar.Foo.Base::name 54 call [mscorlib]System.Console::WriteLine(string) 55 ret 56 } 57 } 58 }

As you can guess, this piece of code illustrates object-orientation in MSIL by creating some type with virtual methods, overriding, a family (~ protected) field, etc. However, there's quite a lot of typing to be done. For example, look at line 15 and 16 where the printer field is referenced including its type and its location. Basically, there is no such thing as namespaces in IL (line 4 is just syntactical sugar to say that all the classes below - line 6 and 39 - have to be prefixed with <namespace_name>. yielding Bar.Foo.Demo and Bar.Foo.Base. This same rule results in things like on line 54 where the assembly (mscorlib), the namespace (System.), the class (Console) and the method (::WriteLine) plus its arguments ((string)) have to written down to make a method call to the right overload. It would be much nicer to abbreviate these if you have to type it a lot.

#define will help us to reduce typing on lines 15 and 16. It's a precompiler directive that drives string replacement in what follows (just like the #ifdef...#else...#endif is a typical control directive). So, we can do something like this:

#define PRINTER "class Bar.Foo.Base Bar.Foo.Demo::printer"

.typedef is used for module-wide aliases. It allows to write stuff like:

.typedef [mscorlib]System.Console as Console .typedef method void Console::WriteLine(string) as WriteLine

So, we can just write call WriteLine instead of call [mscorlib]System.Console::WriteLine(string). The difference with a similar definition using a #define is the fact that aliases are more restrictive and can only be used for classes, methods, fields and attributes. Also, #define hasn't any meaning to the MSIL compiler itself since it's done in the pre-compilation phase. On the other hand, aliases are a part of the MSIL language and result in metadata, which means they can survive round-tripping. In other words, an alias defined in a piece of IL will remain in there even after a ILASM-ILDASM cycle of operations, whileas a #define will vanish during this process.

The end-result looks like this:

1 #define PRINTER "class Bar.Foo.Base Bar.Foo.Demo::printer" 2 3 .typedef [mscorlib]System.Console as Console 4 .typedef method void Console::WriteLine(string) as WriteLine 5 6 .assembly extern mscorlib {} 7 .assembly Demo {} 8 9 .namespace Bar.Foo 10 { 11 .class Demo 12 { 13 .field private static class Bar.Foo.Base printer 14 15 .method public static void Main() 16 { 17 .entrypoint 18 ldstr "Hello World" 19 newobj instance void Bar.Foo.Test::.ctor(string) 20 stsfld PRINTER 21 ldsfld PRINTER 22 call instance void Bar.Foo.Test::Print() 23 ret 24 } 25 } 26 27 .class abstract Base 28 { 29 .field family string name 30 31 .method public void .ctor(string name) 32 { 33 ldarg.0 //this 34 ldarg.1 //name 35 stfld string Bar.Foo.Base::name 36 ret 37 } 38 39 .method abstract virtual family void Print() 40 { 41 } 42 } 43 44 .class sealed Test extends Bar.Foo.Base 45 { 46 .method public void .ctor(string name) 47 { 48 ldarg.0 //this 49 ldarg.1 //name 50 call instance void Bar.Foo.Base::.ctor(string) 51 ret 52 } 53 54 .method public virtual void Print() 55 { 56 .override Bar.Foo.Base::Print 57 ldarg.0 //this 58 ldfld string Bar.Foo.Base::name 59 call WriteLine 60 ret 61 } 62 } 63 }

In name of all IL freaks, enjoy MSIL 2.0!

kick it on DotNetKicks.com

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

No Comments