![]()
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