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!
Del.icio.us |
Digg It |
Technorati |
Blinklist |
Furl |
reddit |
DotNetKicks
Filed under: .NET Framework v2.0