4. The Other Tables

 

This chapter goes on to delineate the remaining tables that have not been touched upon and elucidated in the previous chapter. For this purpose, separate programs have been created to explicate each of the disparate tables. Finally, in the last chapter of the book they have all been put in a single program, wherein each one of them has been cross-referenced.

 

 

The first program in this chapter explores the Fields table. Enter the following code in the file b.cs and then compile it.

 

Fields

 

b.cs

public class zzz

{

public int i = 10;

protected internal string vijay = "hi";

public static void Main()

{

}

}

namespace nnn

{

public class yyy

{

protected long k = 100;

}

}

 

a.cs

using System.Reflection;

using System;

using System.IO;

using System.Configuration.Assemblies;

 

public class zzz

{

public void DisplayGuid(int st)

{

Console.Write("{");

Console.Write("{0}{1}{2}{3}", guid[st+2].ToString("X") , guid[st+1].ToString("X") , guid[st].ToString("X") ,

guid[st-1].ToString("X"));

Console.Write("-{0}{1}-",guid[st+3].ToString("X") , guid[st+4].ToString("X"));

Console.Write("{0}{1}-",guid[st+6].ToString("X") , guid[st+5].ToString("X"));

Console.Write("{0}{1}-",guid[st+7].ToString("X") , guid[st+8].ToString("X"));

Console.Write("{0}{1}{2}{3}{4}{5}",guid[st+9].ToString("X"),

guid[st+10].ToString("X"),guid[st+11].ToString("X"),

guid[st+12].ToString("X"),guid[st+13].ToString("X"),

guid[st+14].ToString("X"));

Console.Write("}");

}

public string GetString(int starting)

{

int i = starting;

while (strings[i] != 0 )

{

i++;

}

System.Text.Encoding e = System.Text.Encoding.UTF8;

string  s = e.GetString(strings, starting , i - starting  );

return s;

}

public static void Main ()

{

zzz a = new zzz();

a.abc();

}

string [] tablenames=new String[]{"Module" , "TypeRef" , "TypeDef" ,"FieldPtr","Field", "MethodPtr","Method","ParamPtr" , "Param", "InterfaceImpl", "MemberRef", "Constant", "CustomAttribute", "FieldMarshal", "DeclSecurity", "ClassLayout", "FieldLayout", "StandAloneSig" , "EventMap","EventPtr", "Event", "PropertyMap", "PropertyPtr", "Properties","MethodSemantics",

"MethodImpl","ModuleRef","TypeSpec","ImplMap","Field

RVA","ENCLog","ENCMap","Assembly","AssemblyProcessor",

"AssemblyOS","AssemblyRef","AssemblyRefProcessor",

"AssemblyRefOS","File","ExportedType","ManifestResource",

"NestedClass","TypeTyPar","MethodTyPar"};

int tableoffset ;

int [] rows;

int [] offset;

int [] ssize ;

byte [] metadata;

byte [] strings;

byte [] us;

byte [] guid;

byte [] blob;

long valid ;

byte [][] names;

long sm;

public void abc()

{

long startofmetadata;

FileStream s = new FileStream("C:\\mdata\\b.exe",FileMode.Open);

BinaryReader r = new BinaryReader (s);

s.Seek(360, SeekOrigin.Begin);

int rva,size;

rva = r.ReadInt32();

size = r.ReadInt32();

int where = rva%0x2000 + 512;

s.Seek(where + 4 + 4, SeekOrigin.Begin);

rva = r.ReadInt32();

where = rva%0x2000 + 512;

s.Seek(where, SeekOrigin.Begin);

startofmetadata = s.Position;

sm=startofmetadata ;

s.Seek(4 + 2 + 2 + 4 + 4 + 12 + 2, SeekOrigin.Current);

int streams = r.ReadInt16();

offset = new int[5];

ssize  = new int[5];

names = new byte[5][];

names[0] = new byte[10];

names[1] = new byte[10];

names[2] = new byte[10];

names[3] = new byte[10];

names[4] = new byte[10];

int i = 0; int j ;

for ( i = 0 ; i < streams ; i++)

{

offset[i] = r.ReadInt32();

ssize[i] = r.ReadInt32();

j = 0;

byte bb ;

while ( true )

{

bb = r.ReadByte();

if ( bb == 0)

break;

names[i][j] = bb;

j++;

}

names[i][j] = bb;

while ( true )

{

if ( s.Position % 4 == 0 )

break;

byte  b = r.ReadByte();

if ( b != 0)

{

s.Seek(-1, SeekOrigin.Current);

break;

}

}

}

for ( i = 0 ; i < streams ; i++)

{

if ( names[i][1] == '~' )

{

metadata = new byte[ssize[i]];

s.Seek(startofmetadata + offset[i] , SeekOrigin.Begin);

for ( int k = 0 ; k < ssize[i] ; k ++)

metadata[k] = r.ReadByte();

}

if ( names[i][1] == 'S' )

{

strings = new byte[ssize[i]];

s.Seek(startofmetadata + offset[i] , SeekOrigin.Begin);

for ( int k = 0 ; k < ssize[i] ; k ++)

strings[k] = r.ReadByte();

}

if ( names[i][1] == 'U' )

{

us = new byte[ssize[i]];

s.Seek(startofmetadata  + offset[i] , SeekOrigin.Begin);

for ( int k = 0 ; k < ssize[i] ; k ++)

us[k] = r.ReadByte();

}

if ( names[i][1] == 'G' )

{

guid = new byte[ssize[i]];

s.Seek(startofmetadata  + offset[i] , SeekOrigin.Begin);

for ( int k = 0 ; k < ssize[i] ; k ++)

guid[k] = r.ReadByte();

}

if ( names[i][1] == 'B' )

{

blob = new byte[ssize[i]];

s.Seek(startofmetadata  + offset[i] , SeekOrigin.Begin);

for ( int k = 0 ; k < ssize[i] ; k ++)

blob[k] = r.ReadByte();

}

}

valid = BitConverter.ToInt64(metadata, 8);

tableoffset = 24;

rows = new int[64];

Array.Clear (rows, 0, rows.Length);

int cnt = 0;

for ( int k = 0 ; k <= 63 ; k++)

{

int tablepresent = (int)(valid >> k ) & 1;

if ( tablepresent == 1)

{

cnt = cnt+1;

rows[k] = BitConverter.ToInt32(metadata , tableoffset);

Console.WriteLine ("Table {0} present at ind {1} - {2}, Rows in table {3}",cnt,k,tablenames[k],rows[k]);

tableoffset += 4;

}

}

xyz();

}

 

public bool tablepresent(byte i)

{

int p = (int)(valid >> i) & 1;

byte [] sizes = {10,6,14,2,6,2,14,2,6,4,6,6,6,4,6,8,6,2,4,2,6,4,2,6,6,6,2,2,8,6,8,4,22,4,12,20,6,14,8,14,12,4};

for ( int j = 0 ; j < i ; j++)

{

int o = sizes[j] * rows[j];

tableoffset = tableoffset + o;

}

if ( p == 1)

return true;

else

return false;

}

 

public void xyz()

{

int new1 = tableoffset;

bool b = tablepresent(4);

int offs = tableoffset;

tableoffset=new1;

if ( b )

{

Console.WriteLine("");

Console.WriteLine("Field Details");

for ( int k = 1 ; k <= rows[4] ; k++)

{

FieldAttributes flags = (FieldAttributes )BitConverter.ToInt16 (metadata, offs);

offs += 2;

int name = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int sig = BitConverter.ToInt16 (metadata, offs);

offs += 2;

Console.WriteLine("Row {0}",k );

Console.WriteLine("Flags: {0}", flags);

Console.WriteLine("Name : {0}", GetString(name));

int count = blob[sig];

Console.Write("Signature [{0}]:Count={1} ", sig , count);

if ( blob[sig+1] == 0x06)

{

Console.Write("Type {0}" , GetType(blob[sig+2]));

}

Console.WriteLine();

}

}

}

public string GetType(int b)

{

if ( b == 0x01)

return "void";

if ( b == 0x02)

return "boolean";

if ( b == 0x03)

return "char";

if ( b == 0x04)

return "byte";

if ( b == 0x05)

return "ubyte";

if ( b == 0x06)

return "short";

if ( b == 0x07)

return "ushort";

if ( b == 0x08)

return "int";

if ( b == 0x09)

return "uint";

if ( b == 0x0a)

return "long";

if ( b == 0x0b)

return "ulong";

if ( b == 0x0c)

return "float";

if ( b == 0x0d)

return "double";

if ( b == 0x0e)

return "string";

return "unknown";

}

}

 

 

Output

Table 1 present at ind 0 - Module, Rows in table 1

Table 2 present at ind 1 - TypeRef, Rows in table 2

Table 3 present at ind 2 - TypeDef, Rows in table 3

Table 4 present at ind 4 - Field, Rows in table 3

Table 5 present at ind 6 - Method, Rows in table 3

Table 6 present at ind 10 - MemberRef, Rows in table 2

Table 7 present at ind 12 - CustomAttribute, Rows in table 1

Table 8 present at ind 32 - Assembly, Rows in table 1

Table 9 present at ind 35 - AssemblyRef, Rows in table 1

 

Field Details

Row 1

Flags: Public

Name : i

Signature [10]:Count=2 Type int

Row 2

Flags: FamORAssem

Name : vijay

Signature [13]:Count=2 Type string

Row 3

Flags: Family

Name : k

Signature [24]:Count=2 Type long

 

An instance variable is also known as a field. The Field table holds an index of 4 in the valid table. The output shows a count of 3 rows, since the file contains 3 fields spread over 2 classes named zzz and yyy.

 

The Field table comprises of the following columns:

 

 

The first column in the Field table is the FieldAttributes flags. The enum of FieldAttributes displays the string assigned to the number. The second column in the table refers to the name of the field. It is an index to the data contained in the strings stream. The output clearly displays the fact that the fields i and vijay of class zzz are placed earlier, suffixed with the field k from the class yyy in the namespace nnn. This sequence is of utmost significance, as shall be demonstrated by us shortly.

 

The last field is an index into the Blob heap. It starts with a count byte of 2, thereby indicating that the field signature has a size of 2 bytes. The first byte in the signature of a method establishes the calling convention. Similarly, the first byte in the signature is always 0x06, thereby indicating that it is a field signature. This primarily serves as a sanity check.

 

This value is followed by the signature byte. It refers to the type of the field. To ascertain its value, we have introduced the function GetType, which returns the data type. As a consequence of this function, the output appropriately reflects the data type of the fields. Section 22.1.15 describes the bits representation of each type. We have saved up the explanation of the especially complicated ones for a rainy day.

 

 

Let us revert to the flags byte. The 'protected' accessibility modifier, which allows access only to derived classes, is known as 'Family' in the IL world. The 'internal' access modifier, which restricts access to the same assembly, is christened as 'Assembly' in the IL word.

 

Life surely would have been significantly more cushy if C# had also resorted to terminology similar to that of IL.

 

 

Had we called off our explanation at this juncture, it would have become impossible for us to expose you to the cross-linkages between the tables. So, we have augmented the program with the requisite code essential for the Typedef details.

 

After calling the xyz function, call the aaa function also, as in xyz(); aaa();

 

Place the following code prior to the GetType function.

 

a.cs

public void aaa()

{

int new1 = tableoffset;

bool b = tablepresent(2);

int offs = tableoffset;

tableoffset = new1;

if ( b )

{

Console.WriteLine("TypeDef Details");

for ( int k = 1 ; k <= rows[2] ; k++)

{

 TypeAttributes flags = (TypeAttributes)BitConverter.ToInt32 (metadata, offs);

offs += 4;

int name = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int nspace = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int cindex = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int findex = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int mindex = BitConverter.ToInt16 (metadata, offs);

offs += 2;

Console.WriteLine("Row:{0}" , k);

Console.WriteLine("Flags     : {0}" , flags);

Console.WriteLine("Name      : {0}" , GetString(name));

Console.WriteLine("NameSpace : {0}" , GetString(nspace));

Console.Write("Extends:");

int u = cindex & 3;

if (u == 0)

Console.Write("TypeDef");

if (u == 1)

Console.Write("TypeRef");

if (u == 2)

Console.Write("TypeSpec");

Console.Write("[{0}]", cindex >> 2);

Console.WriteLine();

Console.WriteLine("FieldList Field[{0}]", findex);

Console.WriteLine("MethodList Method[{0}]", mindex);

}

}

}

 

Output

TypeDef Details

Row:1

Flags     : Class

Name      : <Module>

NameSpace :

Extends:TypeDef[0]

FieldList Field[1]

MethodList Method[1]

Row:2

Flags     : AutoLayout, AnsiClass, NotPublic, Public, BeforeFieldInit

Name      : zzz

NameSpace :

Extends:TypeRef[1]

FieldList Field[1]

MethodList Method[1]

Row:3

Flags     : AutoLayout, AnsiClass, NotPublic, Public, BeforeFieldInit

Name      : yyy

NameSpace : nnn

Extends:TypeRef[1]

FieldList Field[3]

MethodList Method[3]

The output of the above program now exhibits the data contained in the TypeDef table, wherein, 3 rows of the table are displayed. Each row of the Field table is owned by one row in the TypeDef table. This is so because the TypeDef table defines a class and the fields belong to a class.

 

There is no indication as to which Type or class owns the field in the Field table. To determine these linkages, the TypeDef table is examined. The first row represents the global or pseudo class, which can be ignored for the moment. The second row represents the zzz class. The FieldList column in this row points to the first row of the Fields table.

 

Since the first row in the Fields table represents the variable i, it is logical to conclude that the variable i belongs to the zzz class.

 

The third row of the TypeDef table represents the class yyy in the namespace nnn. The value for the FieldList field points to the third row of the Field table. Thus, we can safely presume that the first two rows are owned by the class zzz, whereas, from the third row onwards, the fields belong to the class yyy. A row in the Field table can be owned by only one class from the TypeDef table.

 

This forward pointer method helps in determining the owner of the Field table. Employing this approach, the Fields in a class can be established by reading the FieldList column in the row. All rows in the Fields table belong to one type, until we reach the value given in the FieldList column of the next row. A point to be noted here is that, there can be zero or multiple rows in the Field table. The type encompasses all of them.

 

This behaviour is akin to a parent-child or one-many relationship wherein, a parent type can have multiple children fields, whereas, a child field can possess only one parent type.

 

If there exist two instance variables of fields with the same name, but located in different classes, it results in the creation of two separate rows in the Field table, each owned by a different TypeDef row. The same rule is applicable to methods also.

 

Method Table

Row 1

Name     : Main

 

Row 2

Name     : .ctor

 

Row 3

Name     : .ctor

 

We have displayed only the method names of every row in the method table, since our primary focus at present is on the Field table. Since there are two classes in the file, two constructors are visible. Hence, the method name of .ctor is also displayed twice.

 

Both the rows 2 and 3 represent a constructor. So, how do we ascertain as to which class each constructor belongs to? Bear in mind that while espying the type that lodges the constructors, you should always begin with the TypeDef table, and not with either the Field table or the Method table. This approach is at variance with the one pursued for the MethodRef table, which has a TypeRef field that reveals the class namespace data.

 

The type zzz uses a field named MethodList, which has an index value of 1. The second class i.e. yyy has the MethodList with a value of 3. Thus, the first two rows of the Method table belong to the class zzz, while the third row forms a part of the class yyy.

 

Constant Table

 

b.cs

public class zzz

{

const int i1  = 20;

const string vijay = "hi";

public static void Main()

{

}

}

 

a.cs

public void xyz()

{

int new1=tableoffset;

bool b = tablepresent(11);

int offs = tableoffset;

tableoffset = new1;

if ( b )

{

for ( int k = 1 ; k <= rows[11] ; k++)

{

byte dtype = metadata[offs];

offs += 2;

int parent = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int value = BitConverter.ToInt16 (metadata, offs);

offs += 2;

Console.WriteLine("Row {0}" , k);

Console.WriteLine("Type {0}" , GetType(dtype));

int tag = parent& 0x03;

int rid = (int) ((uint) parent >> 2);

Console.Write("Parent: ");

if ( tag == 0)

Console.Write("FieldDef");

if ( tag == 1)

Console.Write("ParamDef");

if ( tag == 2)

Console.Write("Property");

Console.WriteLine("[{0}]",rid);

int count = blob[value];

Console.WriteLine("Value Blob[{0}] Count {1}",value , count );

for ( int l = 1 ; l <= count ; l++)

{

Console.Write("{0} " , blob[value+l].ToString("X"));

}

Console.WriteLine();

}

}

}

 

Output

Row 1

Type int

Parent: FieldDef[1]

Value Blob[13] Count 4

14 0 0 0

Row 2

Type string

Parent: FieldDef[2]

Value Blob[21] Count 4

68 0 69 0

 

Field Details

Row 1

Flags: -32687

Name : i1

Signature [10]:Count=2 Type int

Row 2

Flags: -32687

Name : vijay

Signature [18]:Count=2 Type string

 

 

The Constant table has the following columns:

 

 

The constant table is at the 11th position in the valid fields, and as its name indicates, it stores the constants that are created in the module.

 

A constant is also a field; therefore, a corresponding entry gets appended to the Field table. The tables, fields and constants have been displayed, to enable you to refer to them.

 

 

The first field in the table refers to the data type of constant. It is a single byte; therefore, the next byte, which contains a value of zero, is used as a padding byte. The trusted GetType function is used to display the type as a readable string.

 

The next field parent is a HasConst coded index, where the first two bits encode a table and can either be a Field, or a Param or a Property table.

 

 

The residual six bits store the index. In this case, both the constants are an index to the Field Table.

 

 

The Field table stores the name and the signature. The signature field in the Field table provides the same information as does the type. However, the flags field displays a number and not a string. Thus, the name and the flags of the constant emanate from the Field table.

 

The last field stores the actual value assigned to the constant. The first byte in this field is the length. If it is an integer, the next four bytes are picked up; however, if it is a string, the length of the string in Unicode is utilized, and not ASCII. The Blob heap is used by the compiler to store the value of the constant.

 

It is for this very reason that the constants need to be determined at compile time, and not at run time.

 

Nested Classes

 

b.cs

public class zzz

{

class yyy

{

public int j,k;

class xxx

{

public int i;

}

}

public static void Main()

{

}

}

a.cs

public void xyz()

{

int new1=tableoffset;

bool b = tablepresent(41);

int offs = tableoffset;

tableoffset = new1;

if ( b )

{

for ( int k = 1 ; k <= rows[41] ; k++)

{

int nestedclass= BitConverter.ToInt16 (metadata, offs);

offs += 2;

int enclosingclass= BitConverter.ToInt16 (metadata, offs);

offs += 2;

Console.WriteLine("Row {0}",k);

Console.WriteLine("Nested    Class TypeDef[{0}]" , nestedclass);

Console.WriteLine("Enclosing Class TypeDef[{0}]" , enclosingclass);

}

}

}

 

Output

Row 1

Nested    Class TypeDef[3]

Enclosing Class TypeDef[2]

Row 2

Nested    Class TypeDef[4]

Enclosing Class TypeDef[3]

 

TypeDef Details

Row:1

Flags     : Class

Name      : <Module>

NameSpace :

Extends:TypeDef[0]

FieldList Field[1]

MethodList Method[1]

Row:2

Flags     : AutoLayout, AnsiClass, NotPublic, Public, BeforeFieldInit

Name      : zzz

NameSpace :

Extends:TypeRef[1]

FieldList Field[1]

MethodList Method[1]

Row:3

Flags     : AutoLayout, AnsiClass, NotPublic, NestedPrivate, BeforeFieldInit

Name      : yyy

NameSpace :

Extends:TypeRef[1]

FieldList Field[1]

MethodList Method[3]

Row:4

Flags     : AutoLayout, AnsiClass, NotPublic, NestedPrivate, BeforeFieldInit

Name      : xxx

NameSpace :

Extends:TypeRef[1]

FieldList Field[3]

MethodList Method[4]

 

 

In the b.cs file, the class zzz encloses the class yyy, which is perfectly legal in the C# world. This concept of enclosing one class within another is termed as 'nesting classes'. The class yyy in turn, contains a nested class named xxx. This too is permissible.

 

For every nested class, one row gets added to the table Nested Classes, which has an index position of 41. In terms of size, this is the smallest of the tables encountered so far. It contains only two indexes. Both these indexes point to the TypeDef table, which in turn, defines a class.

 

 

The TypeDef table contains four rows. Thus, a total of four classes dwell within the file. Apart from one pseudo class, there exist three more classes, which have obviously been created in the file b.cs. Thus, in the TypeDef table, a nested class is a class in its own right.

 

It is the flags field in the TypeDef table that identifies the class as a nested one, since the NestedPrivate bit is set ON. Further, the three classes are depicted as extending from the class System.Object.

 

Reverting to the Nested classes table, the first field in the table is the name of the nested class. Therefore, row 1 refers to the third index into the TypeDef table of class yyy and the second row points to the fourth row of class xxx.

 

The second field in the table is the Enclosing field, which identifies the main class that encloses the nested one. Since the class yyy is nested within the class zzz, the field shows a value of 2, thereby referring to the second row in the TypeDef table. The second class xxx is shown nested within the class yyy, or in the third row.

 

Thus, the nested classes table is simple to comprehend, as it only stores references to a class and its enclosing class. Both of them index the TypeDef table.

 

A nested class is defined as being lexically within the text of the enclosing class. However, when no nested class exists in the program module, the nested classes table bit is marked off, thus banishing all traces of the table and the fact that it ever existed.

 

The two fields of the nested classes must reference a valid row in the TypeDef table, or else it is treated as an error. Furthermore, the Enclosing Class field cannot reference a valid row in the TypeRef table, which shows the type references. Moreover, if the Nested Class and Enclosing class share the same values, a warning is emitted, but not an error.

 

No two rows can possibly possess the same value for the nested class field. This is the case only with the enclosing type, since multiple nested classes can be enclosed within a single class. A single type may have innumerable nested classes within it, but the inverse is not permitted.

 

Param Table

 

b.cs

public class zzz {

public static void Main()

{

}

public void abc()

{

}

public long pqr( int i , out byte  z)

{

z = 10;

return 0;

}

public bool xyz( ref byte  j , string k , long u)

{

return true;

}

}

a.cs

public void xyz()

{

int new1=tableoffset;

bool b = tablepresent(8);

int offs = tableoffset;

tableoffset= new1;

if ( b )

{

for ( int k = 1 ; k <= rows[8] ; k++)

{

short pattr = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int sequence = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int name = BitConverter.ToInt16 (metadata, offs);

offs += 2;

Console.WriteLine("Row {0}" , k);

Console.WriteLine("ParamAttributes {0} Bytes {1}" , GetParamAttributes(pattr) , pattr.ToString("X") );

Console.WriteLine("Sequence {0}" , sequence );

Console.WriteLine("Name {0}" , GetString(name));

}

}

}

public string GetParamAttributes(short a)

{

if ( a == 0x00)

return "None";

if ( (a & 0x01) == 0x01)

return "[In]";

if ( (a & 0x02) == 0x02)

return "[Out]";

if ( (a & 0x04) == 0x04)

return "[Optional]";

if ( (a & 0x1000) == 0x1000)

return "[Default]";

if ( (a & 0x2000) == 0x2000)

return "[Field Marshal]";

if ( (a & 0xcfe0) == 0xcfe0)

return "[Field Marshall]";

return "Unknown";

}

 

public void aaa()

{

int new1=tableoffset;

bool b = tablepresent(6);

int offs = tableoffset;

tableoffset=new1;

if ( b )

{

for ( int k = 1 ; k <= rows[6] ; k++)

{

int rva = BitConverter.ToInt32 (metadata, offs);

offs += 4;

MethodImplAttributes impflags = (MethodImplAttributes )BitConverter.ToInt16 (metadata, offs);

offs += 2;

int flags = (int)BitConverter.ToInt16 (metadata, offs);

offs += 2;

int name = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int signature = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int param = BitConverter.ToInt16 (metadata, offs);

offs += 2;

Console.WriteLine("Row {0}",k );

Console.WriteLine("RVA      :{0}", rva.ToString("X"));

Console.WriteLine("Name     : {0}", GetString(name));

Console.WriteLine("ImpFlags :{0}",impflags );

Console.WriteLine("Flags    :{0}",flags.ToString("X"));

Type t = typeof( System.Reflection.MethodAttributes );

FieldInfo[] f = t.GetFields(BindingFlags.Public | BindingFlags.Static);

for ( int i = 0; i < f.Length; i++ )

{

int fv = (int)f[i].GetValue(null);

if ( (fv & flags) == fv)

Console.Write( "{0} " , f[i].Name );

}

Console.WriteLine();

Console.WriteLine("Signature: #Blob[{0}]",signature);

byte count = blob[signature];

Console.Write("Blob:{0} Count:{1} Bytes ", signature , count);

for ( int l = 1 ; l <= count ; l++)

{

Console.Write("{0} " , blob[signature+l].ToString("X"));

}

Console.WriteLine();

Console.WriteLine("ParamList: Param[{0}]",param);

Console.WriteLine();

}

}

}

 

Output

Row 1

ParamAttributes None Bytes 0

Sequence 1

Name i

Row 2

ParamAttributes [Out] Bytes 2

Sequence 2

Name z

Row 3

ParamAttributes None Bytes 0

Sequence 1

Name j

Row 4

ParamAttributes None Bytes 0

Sequence 2

Name k

Row 5

ParamAttributes None Bytes 0

Sequence 3

Name u

 

Row 1

RVA      :2050

Name     : Main

ImpFlags :Managed

Flags    :96

PrivateScope FamANDAssem Family Public Static HideBySig ReuseSlot

Signature: #Blob[10]

Blob:10 Count:3 Bytes 0 0 1

ParamList: Param[1]

 

Row 2

RVA      :2060

Name     : abc

ImpFlags :Managed

Flags    :86

PrivateScope FamANDAssem Family Public HideBySig ReuseSlot

Signature: #Blob[14]

Blob:14 Count:3 Bytes 20 0 1

ParamList: Param[1]

 

Row 3

RVA      :2070

Name     : pqr

ImpFlags :Managed

Flags    :86

PrivateScope FamANDAssem Family Public HideBySig ReuseSlot

Signature: #Blob[18]

Blob:18 Count:6 Bytes 20 2 A 8 10 5

ParamList: Param[1]

 

Row 4

RVA      :2088

Name     : xyz

ImpFlags :Managed

Flags    :86

PrivateScope FamANDAssem Family Public HideBySig ReuseSlot

Signature: #Blob[25]

Blob:25 Count:7 Bytes 20 3 2 10 5 E A

ParamList: Param[3]

 

Row 5

RVA      :209C

Name     : .ctor

ImpFlags :Managed

Flags    :1886

PrivateScope FamANDAssem Family Public HideBySig ReuseSlot SpecialName RTSpecialName

Signature: #Blob[14]

Blob:14 Count:3 Bytes 20 0 1

ParamList: Param[6]

 

The file b.cs now contains three functions, viz. abc with no parameters, pqr with two parameters and xyz with three parameters. The parameters assigned to the methods get stored in the Param Table, which has an index position of 8 in the valid field. Since there are a total of 5 parameters, the Param table displays a count of 5 rows.

 

 

The Param table has the following three fields:

 

 

The first field of FlagAttributes describes the attributes assigned to the function parameters.

 

 

For this purpose, a special function named GetParamAttributes is provided, whose sole task is to return a string, depending upon the bit that is switched ON in the flag byte.

 

The second field is a sequence number, while the third field is the name of the parameter. 

 

Let us first take a closer peek at the method table. The Rows 2 and 3 stand for the methods abc and pqr, respectively. They both point to Row 1 of the ParamList. It appears deceptive at this stage, since the method abc does not take any parameters, while the method pqr takes two parameters. The constructor in Row 1 also takes no parameters, and yet, it points to the first row in the parameter list.

 

However, as we have learnt in the previous chapter, the second byte of the Blob heap must be examined to determine the actual number of parameters that the function is being passed. Neither the constructor nor the abc function takes any parameters, since the value specified in the second byte is 0.

 

 

The pqr method takes two parameters. Thus, in the Param Table, the parameters for the function are present from the first row onwards. The sequence number identifies the ordering of the parameters, which is why the first parameter i has a value of 1, the second parameter z has a value of 2, and so on.

 

The fourth row of the method table represents the method xyz. The number of parameters in the Blob heap is shown as 3, thereby referring to the third row in the Param table. The third row in the Param table has the parameter named j with the sequence number as 1. The next row for parameter k has a sequence number of 2.

 

The sequence number thus provides the order or the sequence of the parameters. It starts with 1, and thereafter, gets reset to 1 for every new method. The fitting approach is that the method table's Blob field is read first, in order to determine the number of parameters and its type. Then, depending upon the value in the Params field, the appropriate row in the Params table is accessed. The Params table provides the name of the parameter, its attribute and the order in the param list. Thereafter, contingent on the number of parameters in the Blob field, the next set of rows is picked up from the Params table.

 

The number of parameters can be re-ascertained by examining the value in the sequence number. For reasons unknown, the attribute bits are not set accurately.

 

The IN parameter is the default in C#. The OUT parameter is employed when the calling function is authorized to modify the value of the parameters. Ref is a variation in C#. It is considered to be a variant of IN. Therefore, it does not display the attribute of OUT. However, as per our interpretation, Ref is a variant of OUT. You may blame this misconception on our lack of insight. Another possibility could be that the C# compiler may be taking a break.

 

Conceptually, every row in the method table owns one row in the param table, with the singular exception of Row 1. A row cannot have two owners in the method table. Thus, if there are two functions abc and pqr with one parameter, i.e. an int i, there will be two identical rows in the param table. This is not considered erroneous at all, since duplicate rows are acceptable.

 

The sequence number can be zero, signifying the owner's methods return type. The sequence numbers are arranged as per increasing sequence values. Resultantly, there will be omissions in the sequence numbers, which is perfectly valid.

The parameters in the .Net world cannot have default values. So, the HasDefault flag will always be zero.

 

Properties Table

 

b.cs

public class zzz

{

public int aa

{

set

{

}

get

{

return 10;

}

}

public string bb

{

set

{

}

get

{

return "hi";

}

}

public static void Main()

{

}

}

public class yyy

{

public byte cc

{

set

{

}

get

{

return 10;

}

}

}

 

a.cs

public void xyz()

{

int new1=tableoffset;

int old = tableoffset;

bool b = tablepresent(21);

int offs = tableoffset;

Console.WriteLine("Properties Map Table ");

if ( b )

{

for ( int k = 1 ; k <= rows[21] ; k++)

{

short parent = BitConverter.ToInt16 (metadata, offs);

offs += 2;

short propertylist = BitConverter.ToInt16 (metadata, offs);

offs += 2;

Console.WriteLine("Row {0}" , k);

Console.WriteLine("Parent TypeDef[{0}]" , parent);

Console.WriteLine("PropertyList Property[{0}]" , propertylist );

}

}

Console.WriteLine("Properties Table");

tableoffset = old;

b = tablepresent(23);

offs = tableoffset;

if ( b )

{

for ( int k = 1 ; k <= rows[23] ; k++)

{

PropertyAttributes flags = (PropertyAttributes )BitConverter.ToInt16 (metadata, offs);

offs += 2;

int name= BitConverter.ToInt16 (metadata, offs);

offs += 2;

int type = BitConverter.ToInt16 (metadata, offs);

offs += 2;

Console.WriteLine("Row {0}" , k);

Console.WriteLine("Flags [{0}]" , flags);

Console.WriteLine("Name {0}" , GetString(name));

int count = blob[type];

Console.Write("Type BLOB[{0}] Count={1} " , type , count);

for ( int l = 1 ; l <= count ; l++)

{

Console.Write("{0} " , blob[type+l].ToString("X"));

}

Console.WriteLine();

}

}

Console.WriteLine("MethodSematics Table");

tableoffset = old;

b = tablepresent(24);

offs = tableoffset;

if ( b )

{

for ( int k = 1 ; k <= rows[24] ; k++)

{

short methodsemanticsattributes  = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int methodindex = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int association = BitConverter.ToInt16 (metadata, offs);

offs += 2;

Console.WriteLine("Row {0}" , k);

Console.WriteLine("Semantics {0} ", GetMethodSemantics(methodsemanticsattributes));

Console.WriteLine("Method Method[{0}]", methodindex );

int tag = association & 0x01;

Console.Write("Association ");

if ( tag == 0 )

Console.Write("Events");

if ( tag == 1 )

Console.Write("Properties");

int riid = association >> 1;

Console.WriteLine("[{0}]",riid);

}

}

tableoffset=new1;

}

public string GetMethodSemantics(short a)

{

string s = "";

if ( (a & 0x01) == 0x01)

s = s + "Setter";

if ( (a & 0x02) == 0x02)

s = s + "Getter";

if ( (a & 0x04) == 0x04)

s = s + "Other";

if ( (a & 0x08) == 0x08)

s = s + "Event Addon";

if ( (a & 0x10) == 0x10)

s = s + "Event Remove";

if ( (a & 0x20) == 0x20)

s = s + "Event Fire";

return s;

}

 

Output

Properties Map Table

Row 1

Parent TypeDef[2]

PropertyList Property[1]

Row 2

Parent TypeDef[3]

PropertyList Property[3]

 

Properties Table

Row 1

Flags [None]

Name aa

Type BLOB[36] Count=3 28 0 8

Row 2

Flags [None]

Name bb

Type BLOB[40] Count=3 28 0 E

Row 3

Flags [None]

Name cc

Type BLOB[53] Count=3 28 0 5

 

MethodSematics Table

Row 1

Semantics Getter

Method Method[2]

Association Properties[1]

Row 2

Semantics Setter

Method Method[1]

Association Properties[1]

Row 3

Semantics Getter

Method Method[4]

Association Properties[2]

Row 4

Semantics Setter

Method Method[3]

Association Properties[2]

Row 5

Semantics Getter

Method Method[8]

Association Properties[3]

Row 6

Semantics Setter

Method Method[7]

Association Properties[3]

 

As always, let us commence with the b.cs file. In the class zzz, there exist two properties, viz. aa and bb; while in the class yyy, there exists a single property named cc.

 

In a.cs, prior to exhibiting the values in the Property table, we initially display the details of the table Properties Map. This table is at the 21st position in the valid table.

 

The value of the tableoffset variable is also stored in the variable old, apart from new1.

 

The PropertyMap table possesses the following two columns:

 

 

The first field is called the parent, which is an index into the TypeDef table. Since there are two classes that contain properties within them, two rows come into view. The first row points to the second row in the TypeDef table zzz. The parent field in the second row points to the third row in the TypeDef table yyy.

 

The second field of the Properties Map table indexes into the Properties table. The table is displayed below.

 

The Property ( 0x17 ) table has the following columns:

 

 

The Properties Table is the 23rd bit in the valid field. The first field in this table is an Enum of PropertyAttributes. The second is the name of the property. The third field is a series of bytes in the Blob.

 

Let us revert to the Properties Map table. The first row for the class zzz has an index value of 1 in the Properties table, whereas, the second row has the index value of 3. Since the value is not 2, it means that the first two rows of the properties table are owned by TypeDef[2], or the class zzz.

 

The properties table includes one row for each property. Thus, one link is TypeDef-PropertyMap-Property. Now, let us examine the rows in the Method Table.

 

Method Table

Row 1

Name     : set_aa

Signature: #Blob[10]

Blob:10 Count:4 Bytes 20 1 1 8

ParamList: Param[1]

 

Row 2

Name     : get_aa

Signature: #Blob[15]

Blob:15 Count:3 Bytes 20 0 8

ParamList: Param[2]

 

Row 3

Name     : set_bb

Signature: #Blob[19]

Blob:19 Count:4 Bytes 20 1 1 E

ParamList: Param[2]

 

Row 4

Name     : get_bb

Signature: #Blob[24]

Blob:24 Count:3 Bytes 20 0 E

ParamList: Param[3]

 

Row 5

Name     : Main

 

Row 6

Name     : .ctor

 

Row 7

Name     : set_cc

Signature: #Blob[44]

Blob:44 Count:4 Bytes 20 1 1 5

ParamList: Param[3]

 

Row 8

Name     : get_cc

Signature: #Blob[49]

Blob:49 Count:3 Bytes 20 0 5

ParamList: Param[4]

 

Row 9

Name     : .ctor

Signature: #Blob[32]

Blob:32 Count:3 Bytes 20 0 1

ParamList: Param[4]

 

Param Table

Row 1

Name value

Row 2

Name value

Row 3

Name value

 

This table is adorned with nine functions. Hey, wait a minute! We expected only three functions, viz. a Main and a constructor each for the classes zzz and yyy. Now, the moment of truth has dawned upon us. For each occurrence of a property called aa, two methods get created: one called set_aa for the set accessor, and the other known as get_aa, for the get accessor.

 

Since three properties prevail within the file, a total of 6 functions get created; and in the wake of it, 6 rows get added to the method table.

 

Although the C# programming language is capable of comprehending properties, they subsist in the form of functions in the IL world. Thus, all properties get transformed into simple function calls. The set function or the set accessor is passed one parameter, as the signature represents this introduction. This parameter named 'value' is the row 1 in the Param table.

 

The people who designed the C# compiler chose to call the parameter by no other name but 'value'.

 

The set_aa function is passed a parameter called 'value', with the type being set to the type of the property, as indicated by the signature. The get accepts no parameters, and even though the ParamList field makes a mention of an index in the param table, it can safely be ignored. The signature of the method is to be read first. The Param table owns only a single parameter called value, despite the existence of three rows, one for each property.  

 

So far so good, but the link between the Properties table and the method table is conspicuous by its absence. This relation is perceptible in a table called the MethodSematics, which has a bit position of 24 in the valid field.

 

The MethodSemantics table has the following columns:

 

 

This table commences with a two-byte attributes mask. We have created a function called GetMethodSemantics, which checks if the bits are ON or not. The whole idea behind creating functions is that they may be of utility in the future too.

 

In this function, the coding is done using a slightly unusual technique. On most occasions, a combination of bits may be ON. Till now, an inspection was being carried out in order to verify if a specific bit was ON or not, and accordingly, a value was returned. However, this approach proves ineffective if we aspire to establish whether multiples bits are ON or not.

 

 

Innovation is the order of the day! Accordingly, in the function, we keep adding or concatenating to the string 's' in case the bit is ON. The final outcome is that the attribute flag furnishes information as to whether it is a 'getter' or a 'setter' function. The possible values for this mask are stipulated below.

 

 

The second field in the semantics table points to a row in the method table. Thus, the first row in the MethodSematics table is a 'getter'. It refers to Row 2 in the method table, which represents the function get_aa.

 

The last field called 'association' is a link to the properties table. It wields a coded index of 1 bit, thereby resulting in a value of 1. Since the first row of the properties table is the property aa, it associates the getter flag with this property.

 

 

Thus, the deficient link between the method and the properties is established with the help of the MethodSematics table. The field association is considerably more complex and it deals with events as well. We shall inquire into it in a little while from now.

 

 

To sum up, the PropertiesMap table talks about the classes in the typedef table, which own properties in the Properties Table.

 

Thereafter, the Methods table merely lists the methods that are created as an outcome of the properties. It is the MethodSematics table that links up the Properties and the Methods table, by pointing each of them to the function and the property.

 

FieldLayout Table

 

b.cs

using System.Runtime.InteropServices;

[StructLayoutAttribute(LayoutKind.Explicit)]

public class zzz

{

[FieldOffset(2)] int i;

[FieldOffset(20)] long j;

public static void Main()

{

}

}

 

a.cs

public void xyz()

{

bool b = tablepresent(16);

int offs = tableoffset;

if ( b )

{

for ( int k = 1 ; k <= rows[16] ; k++)

{

int offset = BitConverter.ToInt32 (metadata, offs);

offs += 4;

int fieldindex = BitConverter.ToInt16 (metadata, offs);

offs += 2;

Console.WriteLine("Row {0}" , k);

Console.WriteLine("Offset:{0}" , offset);

Console.WriteLine("Field :Field[{0}]" , fieldindex );

}

}

}

 

Output

Row 1

Offset:2

Field :Field[1]

Row 2

Offset:20

Field :Field[2]

 

Field Table

Row 1

Name : i

Signature [10]:Count=2 Type int

Row 2

Name : j

Signature [13]:Count=2 Type long

 

In the normal course, by default, memory locations are allocated to the fields present in a class or structure by the runtime. Under specific circumstances, we are required to determine these memory locations manually. To accomplish this, an attribute named StructLayoutAttribute in the program, has to be prefixed to the class name, which acquires the value of Explicit from an enum named LayoutKind. 

 

It is the attribute FieldOffset that contains the offset and the field, which is the ultimate authority that determines the layout. In b.cs, we have specified the first field i to be laid out at a starting position of 2, in place of 0. Further, we have also stipulated the fact that the second field, which normally starts at the end of the first field, should start at offset 20 instead. The FieldOffset attribute must be placed on each of the instance members.

 

On each occasion that the fields are laid out manually, rows get supplemented to the FieldLayout table. They have an index position of 16 in the valid table. The FieldLayout table has the following columns:

 

 

The first field is an int, which stores the offset. The second field is an index into the Field table. Thus, the first row points to the first field i in the Field table, while the second row in the FieldLayout refers to the field j.

 

 

It is as straightforward as this!

 

Events and Delegates

 

b.cs

public class zzz

{

public static void Main()

{

}

}

public delegate void pqr(int p);

 

The above example defines a delegate called pqr at the namespace level. A delegate is brought into play to call methods unconventionally, in a type-safe manner. They go hand in glove with Events. Let us scrutinize the assorted tables that are created.

 

TypeRef Table

Row[1]

Name      :Object,0x20

Namespace :System,0x19

Row[2]

Name      :MulticastDelegate,0x2B

Namespace :System,0x19

Row[3]

Name      : IAsyncResult,0x53

Namespace :System,0x19

Row[4]

Name      : AsyncCallback,0x60

Namespace :System,0x19

Row[5]

Name      :DebuggableAttribute,0x97

Namespace :System.Diagnostics,0x84

 

The TypeRef table reveals the fact that 5 types are being referred to in the assembly. The first and the last types are invariably present. However, with the creation of a delegate, all the three types, viz. MulticastDelegate, IAsyncResult and AsyncCallback, which belong to the System namespace, get augmented. These references have come about as a result of the code being introduced by the delegate class.

 

TypeDef Table

Row:1

Flags     : Class

Name      : <Module>

Row:2

Flags     : AutoLayout, AnsiClass, NotPublic, Public, BeforeFieldInit

Name      : zzz

Row:3

Flags     : AutoLayout, AnsiClass, NotPublic, Public, Sealed

Name      : pqr

NameSpace :

Extends:TypeRef[2]

FieldList Field[1]

MethodList Method[3]

 

In the previous chapter, we examined the rows in the TypeDef table. The pseudo and zzz types are created, just as before. With the ushering in of the delegate, a new row with the type name of pqr, gets supplemented to the table. This becomes a first class type, to which the flag Sealed gets added, thereby preventing access to all other classes to derive from it.

 

The Extends field reveals the type that pqr is derived from. It points to the second row in the TypeRef table, i.e. the class MulticastDelegate. Thus, it can be competently established that a delegate class derives from the MulticastDelegate class.

 

Now, we take a look at the Method table from the third row onwards, to unfurl the methods that a delegate class introduces.

 

Methods Table

Row 1

Name     : Main

Row 2

Name     : .ctor

Row 3

Name     : .ctor

ImpFlags :Runtime

PrivateScope FamANDAssem Family Public HideBySig ReuseSlot SpecialName RTSpecialName

Signature: #Blob[18]

Blob:18 Count:5 Bytes 20 2 1 1C 18

ParamList: Param[1]

Row 4

Name     : Invoke

ImpFlags :Runtime

PrivateScope FamANDAssem Family Public Virtual HideBySig ReuseSlot

Signature: #Blob[24]

Blob:24 Count:4 Bytes 20 1 1 8

ParamList: Param[3]

Row 5

Name     : BeginInvoke

ImpFlags :Runtime

PrivateScope FamANDAssem Family Public Virtual HideBySig VtableLayoutMask ReuseSlot NewSlot

Signature: #Blob[29]

Blob:29 Count:8 Bytes 20 3 12 D 8 12 11 1C

ParamList: Param[4]

Row 6

Name     : EndInvoke

ImpFlags :Runtime

PrivateScope FamANDAssem Family Public Virtual HideBySig VtableLayoutMask ReuseSlot NewSlot

Signature: #Blob[38]

Blob:38 Count:5 Bytes 20 1 1 12 D

ParamList: Param[7]

 

Param Table

Row 1

Name object

Row 2

Name method

Row 3

Name p

Row 4

Name p

Row 5

Name callback

Row 6

Name object

Row 7

Name result

 

The third row in the method table is a constructor. Since none of these functions have been entered manually, the ImpFlags for all the four functions are Runtime. This is the first occasion on which we have encountered this flag. We shall explicate the other flags of Virtual and NewSlot a little later.

 

The signature of the constructor reveals two parameters. On inspecting the params table, we discover that the row 1 is a parameter called object, and the row 2 is a parameter named  method. In the same vein, the second function of Invoke has one parameter called 'p'. The third function in the delegate is BeginInvoke, which accepts three parameters i.e. 'p', callback and object. Finally, we come upon the EndInvoke function that takes one parameter named result.

 

 

By now, it would have become amply evident to you that reading metadata tables is becoming progressively easier.

 

b.cs

using System;

public class zzz

{

public event EventHandler a;

public static void Main()

{

}

}

 

In the file b.cs, the field 'a' is declared to be of type event. The EventHandler delegate is present in the System namespace. Let us take a look at the rows inserted in the metadata tables.

 

TypeRef Table

Row[1]

Name          :Object,0x20

Row[2]

Name         :EventHandler,0x2B

Namespace :System,0x19

Row[3]

Name         :DebuggableAttribute,0x67

Row[4]

Name          :Delegate,0x83

Namespace :System,0x19

There are two extra type refs that get introduced at rows 2 and 4, viz. the EventHandler delegate that the event uses, and the Delegate type. We shall revert to them in no time.

 

The TypeDef table contains the rows of the pseudo class and the zzz class. Hence, they have not been displayed. An event, unlike a delegate, is basically treated as a field. Therefore, a row gets added to the Field table.

 

Field Table

Row 1

Flags: Private

Name : a

Signature [10]:Count=3 Type unknown

 

However, the signature assigned to the field is an obscure one. This is because we have not implemented all the types, objects in particular.

 

Method Table

Row 1

Name     : add_a

ImpFlags :Synchronized

Flags    :886

PrivateScope FamANDAssem Family Public HideBySig ReuseSlot SpecialName

Signature: #Blob[14]

Blob:14 Count:5 Bytes 20 1 1 12 9

ParamList: Param[1]

Row 2

Name     : remove_a

ImpFlags :Synchronized

Flags    :886

PrivateScope FamANDAssem Family Public HideBySig ReuseSlot SpecialName

Signature: #Blob[14]

Blob:14 Count:5 Bytes 20 1 1 12 9

ParamList: Param[2]

Row 3

Name     : Main

Row 4

Name     : .ctor

 

With the introduction of events in the program, two rows get added to the method table, viz. add_a and remove_a. We will handle the residual Flag bits in a single stroke, a little later.

 

The add_a function takes one parameter whose name is 'value', as verified by the params table. The second method named remove_a also takes a parameter called 'value'.

 

Thus, an event eventually disintegrates into two methods, i.e. add_eventname and remove_eventname.

 

MemberRef

Row 1

Name:.ctor

Row 2

Class:TypeRef[4]

Name:Combine

Signature #BLOB[34] Count 8 0 2 12 11 12 11 12 11

Row 3

Class:TypeRef[4]

Name:Remove

Signature #BLOB[34] Count 8 0 2 12 11 12 11 12 11

Row 4

Name:.ctor

 

The MemberRef table unveils the fact that the event refers to the two methods named Combine and Remove. These methods index into row 4 of the TypeRef table, which represents the System.Delegate class. The signature shall be explained in the subsequent chapters, since it is too convoluted to be handled at this point in time.

 

a.cs

public void xyz()

{

int old = tableoffset;

bool b = tablepresent(20);

int offs = tableoffset;

if ( b )

{

Console.WriteLine("Event");

for ( int k = 1 ; k <= rows[20] ; k++)

{

short attr = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int name = BitConverter.ToInt16 (metadata, offs);

offs += 2;

int coded = BitConverter.ToInt16 (metadata, offs);

offs += 2;

Console.WriteLine("Row {0}" , k);

Console.WriteLine("Event Flags: {0}" , GetEventsAttributes(attr));

Console.WriteLine("Name: {0}" , GetString(name));

Console.Write("Event Type: ");

int tag = coded & 0x03;

if ( tag == 0 )

Console.Write("TypeDef");

if ( tag == 1 )

Console.Write("TypeRef");

if ( tag == 2 )

Console.Write("TypeSpec");

int riid = coded >> 2;

Console.WriteLine("[{0}]" , riid);

}

}

tableoffset = old;

b = tablepresent(18);

offs = tableoffset;

if ( b )

{

Console.WriteLine("EventMap Table");

for ( int k = 1 ; k <= rows[18] ; k++)

{

short index = BitConverter.ToInt16 (metadata, offs);

offs += 2;

short eindex = BitConverter.ToInt16 (metadata, offs);

offs += 2;

Console.WriteLine("Row {0}" , k);

Console.WriteLine("Parent TypeDef[{0}]" , index);

Console.WriteLine("EventList Event[{0}]" , eindex);

}

}

}

public string GetEventsAttributes(short a)

{

string s = "";

if ( (a & 0x0200) == 0x0200)

s = s + "Special Name";

if ( (a & 0x0400) == 0x0400)

s = s + "RTSpecialName";

if (s.Length == 0)

return "None";

else

return s;

}

 

Output

Event

Row 1

Event Flags: None

Name: a

Event Type: TypeRef[2]

EventMap Table

Row 1

Parent TypeDef[2]

EventList Event[1]

 

MethodSematics

Row 1

Semantics Event Addon

Method Method[1]

Association Events[1]

Row 2

Semantics Event Remove

Method Method[2]

Association Events[1]

 

 

TypeDef Table

Row:1

Name      : <Module>

Row:2

Name      : zzz