Program30.csc.txt

public string GetElementType ( int index , byte [] blobarray , out int howmanybytes)

{

howmanybytes = 0;

string returnstring = "";

byte type = blobarray[index];

if ( type == 0x0f)

returnstring = GetPointerToken(index, blobarray , out howmanybytes);

return returnstring;

}

 

public string GetPointerToken(int index , byte [] blobarray , out int howmanybytes)

{

string returnstring = "";

int howmanybytes2;

returnstring = GetElementType(index+1 , blobarray , out howmanybytes2) + "*";

howmanybytes = howmanybytes2 + 1;

return returnstring;

}

 

e.il

.class zzz

{

.method  int32 *** a1()

{

}

.method  class zzz ****** a2()

{

}

.method   int32  [12][3,5] *  a3()

{

}

}

 

Output

  .method /*06000001*/   int32***

  .method /*06000002*/   class zzz/* 02000002 */******

  .method /*06000003*/   int32[12][3,5]*

 

In the above il file we have added a unmanaged pointer * like we added a managed pointer & in the earlier example. There is not much change in from the earlier program.

 

The only difference is that the unmanaged pointer has a type of 0x0f and in the il file we have shown how recursion really works by adding as many stars as we could and yet demonstrate that the program works. Thus the short explanation.

 

Program31.csc

public string DecodeToken (int token , int type)

{

byte tabletype = (byte)(token & 0x03);

int tableindex = token >> 2;

string returnstring = "";

if ( tabletype == 0)

returnstring = typedefnames[tableindex];

if ( tabletype == 1 )

returnstring = typerefnames[tableindex] ;

return returnstring;

}

public string GetTableRefNameForFillArray( int tablerow , int typerow , string tablename)

{

string nameandnamespace = "";

if ( tablename == "AssemblyRef")

nameandnamespace = NameReserved(GetString(AssemblyRefStruct[tablerow].name)) ;

if ( tablename == "ModuleRef")

nameandnamespace = ".module " + NameReserved(GetString(ModuleRefStruct[tablerow].name)) ;

string stringnamespace = NameReserved(GetString(TypeRefStruct[typerow].nspace)) ;

string stringnested  = "";

if ( stringnamespace != "")

stringnamespace  = stringnamespace + ".";

if ( tablename == "AssemblyRef")

stringnested  = "[" + nameandnamespace  + "/* 23" + tablerow.ToString("X6") + " */]" + stringnamespace + NameReserved(GetString(TypeRefStruct[typerow].name));

if ( tablename == "ModuleRef")

stringnested  = "[" + nameandnamespace + "/* 1A" + tablerow.ToString("X6") + " */]" + stringnamespace + NameReserved(GetString(TypeRefStruct[typerow].name));

if ( tablename == "Module")

stringnested  = stringnested  + stringnamespace + NameReserved(GetString(TypeRefStruct[typerow].name));

stringnested  = stringnested  + "/* 01" + typerow.ToString("X6") + " *//" ;

return stringnested;

}

public string GetTypeRefForFillarray(int k)

{

string stringnamespace = NameReserved(GetString(TypeRefStruct[k].nspace)) ;

if ( stringnamespace != "")

stringnamespace= stringnamespace + ".";

string stringnested  = stringnamespace + NameReserved(GetString(TypeRefStruct[k].name)) ;

stringnested  = stringnested  + "/* 01" + k.ToString("X6") + " */" ;

return stringnested;

}

string [] typerefnames;

string [] typedefnames;

public void FillArray ()

{

int old = tableoffset;

bool tablehasrows  = tablepresent(1);

int offs = tableoffset;

tableoffset = old;

if ( tablehasrows )

{

typerefnames = new string[rows[1]+1];

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

{

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

offs = offs + 2;

int name = ReadStringIndex(metadata , offs);

offs = offs + offsetstring;

int nspace = ReadStringIndex(metadata , offs);

offs = offs + offsetstring;

string stringname = NameReserved(GetString(name));

string stringtypefminusnested = GetString(nspace) ;

if ( stringtypefminusnested .Length != 0)

stringtypefminusnested = stringtypefminusnested + ".";

stringtypefminusnested = stringtypefminusnested + stringname;

string stringnested = "";

string resolutioncodedtable = GetResolutionScopeTable(resolutionscope );

int resolutionrow = GetResolutionScopeValue(resolutionscope );

if ( resolutioncodedtable == "Module")

{

stringtypefminusnested = stringtypefminusnested + "/* 01" + k.ToString("X6") + " */";

}

if ( resolutioncodedtable == "AssemblyRef")

{

stringnested  = "[" + NameReserved(GetString(AssemblyRefStruct[resolutionrow].name)) + "/* 23" + resolutionrow.ToString("X6") + " */]";

stringtypefminusnested = stringtypefminusnested + "/* 01" + k.ToString("X6") + " */";

}

if ( resolutioncodedtable == "ModuleRef")

{

stringnested  = "[.module " + NameReserved(GetString(ModuleRefStruct[resolutionrow].name)) + "/* 1A" + resolutionrow.ToString("x6") + " */]";

stringtypefminusnested = stringtypefminusnested + "/* 01" + k.ToString("X6") + " */";

}

if ( resolutioncodedtable == "TypeRef" )

{

string resolutioncodedtable1 = GetResolutionScopeTable(TypeRefStruct[resolutionrow].resolutionscope );

int resolutionrow1 = GetResolutionScopeValue(TypeRefStruct[resolutionrow].resolutionscope );

if ( resolutioncodedtable1 == "AssemblyRef" || resolutioncodedtable1 == "ModuleRef" || resolutioncodedtable1 == "Module")

{

stringnested = GetTableRefNameForFillArray(resolutionrow1 , resolutionrow , resolutioncodedtable1);

stringnested = stringnested + GetTypeRefForFillarray(k);

stringtypefminusnested = "";

}

else

{

string tablename = GetResolutionScopeTable(TypeRefStruct[k].resolutionscope );

int row  = GetResolutionScopeValue(TypeRefStruct[k].resolutionscope );

int cnt = 0;

while ( tablename == "TypeRef")

{

row  = GetResolutionScopeValue(TypeRefStruct[row].resolutionscope );

tablename = GetResolutionScopeTable(TypeRefStruct[row].resolutionscope );

cnt++;

}

int i = 0;

int [] typerows = new int[cnt];

string tablename1 = GetResolutionScopeTable(TypeRefStruct[k].resolutionscope );

int row1  = GetResolutionScopeValue(TypeRefStruct[k].resolutionscope );

while ( i < cnt )

{

row1  = GetResolutionScopeValue(TypeRefStruct[row1].resolutionscope );

tablename1 = GetResolutionScopeTable(TypeRefStruct[row1].resolutionscope );

typerows[i] = row1;

i++;

}

 

int assemblyrow = GetResolutionScopeValue(TypeRefStruct[row].resolutionscope );

int typerow = typerows[typerows.Length-1];

stringnested = GetTableRefNameForFillArray( assemblyrow , typerow , tablename);

 

i = 0;

string dummy = "";

while ( i < cnt-1 )

{

dummy  =  GetTypeRefForFillarray(typerows[i]) + "/" + dummy;

i++;

}

stringnested = stringnested + dummy;

stringnested = stringnested + GetTypeRefForFillarray(resolutionrow);

stringnested = stringnested + "/" + GetTypeRefForFillarray(k);

stringtypefminusnested = "";

}

}

typerefnames[k] = stringnested  + stringtypefminusnested ;

//Console.WriteLine(".......{0} {1} {2}" , resolutioncodedtable , typerefnames[k] , k);

}

}

old = tableoffset;

tablehasrows  = tablepresent(2);

offs = tableoffset;

tableoffset = old;

if ( tablehasrows )

{

typedefnames = new string[rows[2]+1];

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

{

int name = TypeDefStruct[k].name;

offs += offsetstring;

int nspace = TypeDefStruct[k].nspace;

offs += offsetstring;

string nestedtypestring  = "";

nestedtypestring  = GetNestedTypeAsString(k);

string namestring  = GetString(name);

string namespacestring = NameReserved(GetString(nspace));

if ( namespacestring.Length != 0)

namespacestring = namespacestring + ".";

namestring  = NameReserved(namestring );

typedefnames[k] = nestedtypestring + namespacestring + namestring  + "/* 02" + k.ToString("X6") + " */";

}

}

}

 

e.il

.assembly extern vijay

{

}

.module extern bbb

.module ccc

.class zzz

{

.method  class [vijay]z1 a1()

{

}

.method  class [.module bbb]z2 a2()

{

}

.method  class [.module ccc]z3 a3()

{

}

.method  class [vijay]z4/z5 a41()

{

}

.method  class [vijay]z4/z5/z6 a5()

{

}

.method  class [vijay]z4/z5/z6/z7 a6()

{

}

.method  class [vijay]z4/z5/z6/z7/z8/z9/z10/z11 a71()

{

}

.method  class [.module bbb]z4/z5/z6/z7/z8/z9/z10/z11 a72()

{

}

.method  class [.module ccc]z4/z5/z6/z7/z8/z9/z10/z11 a73()

{

}

}

 

Output

  .method /*06000001*/   class [vijay/* 23000001 */]z1/* 01000002 */

  .method /*06000002*/   class [.module bbb/* 1A000001 */]z2/* 01000003 */

  .method /*06000003*/   class z3/* 01000004 */

  .method /*06000004*/   class [vijay/* 23000001 */]z4/* 01000005 *//z5/* 01000006 */

  .method /*06000005*/   class [vijay/* 23000001 */]z4/* 01000005 *//z5/* 01000006 *//z6/* 01000007 */

  .method /*06000006*/   class [vijay/* 23000001 */]z4/* 01000005 *//z5/* 01000006 *//z6/* 01000007 *//z7/* 01000008 */

  .method /*06000007*/   class [vijay/* 23000001 */]z4/* 01000005 *//z5/* 01000006 *//z6/* 01000007 *//z7/* 01000008 *//z8/* 01000009 *//z9/* 0100000A *//z10/* 0100000B *//z11/* 0100000C */

  .method /*06000008*/   class [.module bbb/* 1A000001 */]z4/* 0100000D *//z5/* 0100000E *//z6/* 0100000F *//z7/* 01000010 *//z8/* 01000011 *//z9/* 01000012 *//z10/* 01000013 *//z11/* 01000014 */

  .method /*06000009*/   class z4/* 01000015 *//z5/* 01000016 *//z6/* 01000017 *//z7/* 01000018 *//z8/* 01000019 *//z9/* 0100001A *//z10/* 0100001B *//z11/* 0100001C */

 

 

 

So far we have looked at data types that have been created within our program and now we need to look at those defined somewhere else. Thus the method a1 has a return type of class z1. The only problem is that this class z1 is defined in vijay which is placed in square brackets.

 

This name vijay has to been a assembly ref as we specified earlier. The only problem is there is no check to see whether this assembly vijay actually exits and within it there exists a type called z1. Every type we reference in our program is stored in the type ref table.

 

The name and namespace field store for us the name and namespace name respectively. The field resolution we will deal with in  a short while. In the type signature, the class type remains at 12 and the token for the method a1 is 09. As the first bit is 1, this token refers to the type ref table and not the typedef table.

 

Thus in the DecodeToken table the if statement checks if the variable tabletype is 1, it picks up the row from the typeref and not typedef table. Thus in the FillArray method we now populate the typerefnames array. The variable stringtypefminusnested simply gives us the name and namespace combo of the type used with or without the dot.

 

Then we move on to the core of what this program is all about. We want to know where this type is really stored. The field resolutionscope is just what the doctor ordered. This method used earlier gives us one of the four tables, AssemblyRef, Module, ModuleRef and TypeRef. These values tell us where to find the type.

 

For the method a1, the type is to be found in a Assembly and hence the coded index is a AssembleyRef. The coded index row variable now points to the AssemblyRef table.

 

Thus we start with a open [, then we pick up the name of the assembly from the AssemblyRef table using the resolutionrow variable, followed by the table number of the AssemblyRef table 0x23 and the row number in this table using the resolutionrow variable. Finally the for loop index variable k tells us the row number in the type ref table that has a number 1.

 

This is at the end as a comment for all the typeref rows. We add the two strings stringtypefminusnested and string stringnested at the end and place this value into the typerefnames array. In this case we need not have two separate variables as one would have done the job. Read on.

 

For method a2, the return type is z2 and this is now found in a module bbb. There is a module extern directive that specifies the existence of a module with this name. Thus the coded index table is now ModuleRef as the type is in the same assembly but a different module.

 

For the module ref we add the .module directive and now the variable resolution row is an offset into the ModuleRef table whose number is 0x1a. The rest of the code remains like that of the AssemblyRef.

 

The third method a3 has its return data type z3 stored in a module called ccc and this is also the name of the current module using the module directive. Thus the code index table will return Module and in the output we do not display the words module and the name of the module, but only the type name.

 

The problems starts with the method a4 that has a type in the AssemblyRef vijay but its type is nested. The main type is z4 and within this type we have a nested type z5. The Nested Types table does not get an entry added as that table deals with nested types created in this assembly and not somewhere else.

 

Thus two records will be added to the TypeRef table, one for the type z4 and the second for the type z5. To make matters simpler for us to understand we will comment the entire il file and only focus on one method at a time from now on. Our current focus is method a4.

 

The TypeRef table has three records, the first for the type Object as all classes are derived from class Object at the end of the day. Thus we will not show you the first record of the type ref table that refers to the class Object.

 

The second row refers to the class z4 and the resolution scope refers to the assembly ref table record 1 that is vijay. This is obvious as the class or type must reside someplace. The third record refers to the class z5. This class is nested within the class z4.

 

This relationship is mapped by specifying the resolution scope of TypeRef and the row number is the row number of the class within which this class is nested. Thus whenever we see a TypeRef resolution scope it means that the current class  is nested within the class specified by the resolution index.

 

This class if it has a resolution scope of TypeRef means one more level of nesting. This relationship ends with a type whose resolution scope is Module, ModuleRef or AssemblyRef as we shall soon see.

 

Row #2

ResolutionScope   : AssemblyRef[1]

Name              : "z4"

Row #3

ResolutionScope   : TypeRef[2]

Name              : "z5"

 

The if statement for the value of the resolution scope index being TypeRef is true for the nested type z5 and not type z4. The value of the variable resolutionrow is now 2 which simply means that this TypeRef row may be the parent type.

 

We use this value to move to row 2, we are at row three now and find out the new values of this coded index. If it is any of the terminating table types, we call the function GetTableRefNameForFillArray that gives us the name of the Assembly or Module or ModuleRef that the type is in.

 

The last parameter is the type of container that this type resides in. The first parameter is the tablerow in the table type specified in the last parameter and the second parameter is the type row number that has a coded index other than  TypeRef. Lets look at this function GetTableRefNameForFillArray first.

 

We first ask which table the tablerow parameter is an offset to. Depending upon one of two and not three possible tables, we use the relevant array to gives us the name of the assembly or module. If the type resides in the same module, the module directive is removed.

 

For the AssemblyRef table, it is simply the table name and for the ModuleRef table it is the module directive plus the extern module name. We now need to add the type name and namespace name which is stored in the TypeRef table. This type is the parent type z4 and not nested type z5 in this case.

 

We then add the relevant name of the type and the row number in comments. As this is a nested type we end with a single slash specifying a nested type. Now that we have the parent type, we need to add the last nested type z5 which is the value of the loop index k.

 

Thus we have a method GetTypeRefForFillarray that simply displays a type with a certain index. This function is very elementary and all that we do is add the name and namespace and then the row number of the type ref table in comments. This is how we take care of a single level of a nested type.

 

This does not work with a type that has more than one level of nesting. We need more complex code. This if statement was unnecessary but as an aid to understanding. The else takes place whenever we have more than one level of nesting. Lets take a certain case of method a6 which is complex enough.

 

Its data type of the return value is z4/z5/z6/z7 where we have four levels of nesting. How do we handle complex cases like this. Simple we start at the inner else statement.

 

Row #2

ResolutionScope   : AssemblyRef[1]

Name              : "z4"

Row #3

ResolutionScope   : TypeRef[2]

Name              : "z5"

Row #4

ResolutionScope   : TypeRef[3]

Name              : "z6"

Row #5

ResolutionScope   : TypeRef[4]

Name              : "z7"

 

Lets start with the rows of the TypeRef table. In the DecodeToken method, the value of the tableindex will b3 5, the record number for the z7 type. If we look at the rows, the resolution coded table is typeref that tells us that the parent type of z7 is z6.

 

But this row also has a coded index of TypeRef and thus class z5 is the parent of class z6. However class z5 or row 3 has also a TypeRef coded index and thus row 2 or class z4 is the parent for class z5. The good news is that class z4 has a coded index of AssemblyRef. Lets take a hands on look at our code.

 

The string tablename is TypeRef and the type row is 5 or class z7. The row variable is 4 specifying that this row will be the parent of class z7. We would now need to find out how many levels of nesting are there. Thus we need to keep looping until we find a type row whose coded index table is not TypeRef.

 

The problem is that we cannot assume that each nested types parent is stored one about it. So we in a while loop, keep looping until we reach a table name that is not TypeRef. Now that we have entered the while loop, we are on a nested type and hence we calculate the row number of the parent type.

 

This is stored in row. We then use this row number to give us the coded index table of the parent type. If this is ever not TypeRef we exit the while loop. Thus in our specific case the cnt variable will have a value of 2 as we have entered the while loop twice and the row number that had a coded index other than TypeRef is the second row.

 

Thus the value of cnt is actually one less than the number of types. It gives us types z5 and z6. Z7 is the type denoted by the k index and z4 by the AssembleyRef. We would now like to create an array of 2 int’s t store the row numbers of types z5 and z6.

 

We once again enter the while loop twice and simply recalculate the coded index row variable and store it in the typerows array. The values in this array are rows 3 and  2 as we are moving up the type row table. The tablename1 and row1 variables are set to a value of the type z7 or row 5 in the type ref table.

 

We are now calling the method GetTableRefNameForFillArray with the first parameter assemblyrow as 1 as this is the record number of the AssemblyStruct table and name vijay. The typerow variable is the 2 as this is the first parent type z4. This method will return a string as [vijay/* 23000001 */]z4/* 01000002 *//.

 

We now enter the while loop only once as the value of cnt is only two. Here we display the next series of nested types. The typerow array has a value of 3 and the string dummy contains z5/* 01000003 *//. Now we need to display the second last type that is denoted by the resolutionrow variable or row 4 or type z6.

 

Finally the current type denoted by k or row 5 or type z7 is displayed at the end without the nested slash. We could not have placed the second last GetTypeRefForFillarray within the while as the type z4 would come twice. If you are not clear please reread the entire explanation a couple of times with some aspirin.

 

Program32.csc

public string GetElementType ( int index , byte [] blobarray , out int howmanybytes)

{

howmanybytes = 0;

string returnstring = "";

string modoptstring = "";

int bytestaken;

modoptstring = GetModOptOrModReq( index , blobarray , out bytestaken);

index = index + bytestaken;

byte type = blobarray[index];

if ( type >= 0x01 && type <= 0x0e )

{

returnstring = GetType(type);

howmanybytes = 1;

}

if ( type == 0x45 )

{

int howmanybytes2 ;

returnstring = GetElementType( index + 1 , blobarray , out howmanybytes2) + " pinned";

howmanybytes = howmanybytes2 + 1;

}

howmanybytes = howmanybytes + bytestaken;

if ( modoptstring != "")

returnstring = returnstring + " " + modoptstring;

return returnstring ;

}

public string GetTokenType ( byte [] blobarray , int index , out int howmanybytes)

{

string returnstring = "";

int uncompressedbyte;

int howmanybytes1 = 0;

howmanybytes1 = howmanybytes1  + CorSigUncompressData(blobarray , index + 1 , out uncompressedbyte);

string dummy1  = DecodeToken(uncompressedbyte , blobarray[index]);

if ( blobarray[index] == 0x12)

returnstring = "class " + dummy1; 

else if ( blobarray[index] == 0x11)

returnstring = "valuetype " + dummy1;

else

returnstring = dummy1;

howmanybytes = howmanybytes1;

return returnstring;

}

 

public string GetModOptOrModReq ( int index , byte [] blobarray , out int bytestaken)

{

string returnstring = "";

bytestaken = 0;

int whileindex = index;

if ( blobarray[index]  == 0x20 || blobarray[index]  == 0x1f)

{

while ( true )

{

int noofbytes;

string tokens = GetTokenType (blobarray , whileindex , out noofbytes) ;

if ( blobarray[whileindex]  == 0x20)

tokens = "modopt(" + tokens + ")";

if ( blobarray[whileindex]  == 0x1f)

tokens = "modreq(" + tokens + ")";

bytestaken = bytestaken + noofbytes + 1;

returnstring = tokens + " " + returnstring ;

whileindex = whileindex + noofbytes + 1;

if ( !(blobarray[whileindex]  == 0x20 || blobarray[whileindex]  == 0x1f)  )

break;

}

}

if ( returnstring != "")

returnstring = returnstring.Remove(returnstring.Length-1,1);

return returnstring;

}

 

e.il

.assembly extern vijay

{

}

.class zzz

{

.method  void modopt([vijay]aa  ) a1()

{

}

.method  void modreq([vijay]aa ) a2()

{

}

.method  void * modopt([vijay]aa ) modreq([vijay]bb ) modreq ([vijay]cc )  modreq([vijay]dd )  a3()

{

}

}

 

Output

  .method /*06000001*/   void modopt([vijay/* 23000001 */]aa/* 01000002 */)

  .method /*06000002*/   void modreq([vijay/* 23000001 */]aa/* 01000002 */)

  .method /*06000003*/   void* modopt([vijay/* 23000001 */]aa/* 01000002 */) modreq([vijay/* 23000001 */]bb/* 01000003 */) modreq([vijay/* 23000001 */]cc/* 01000004 */) modreq([vijay/* 23000001 */]dd/* 01000005 */)

 

We are now going to delve into two modifiers modreq and modopt that we have no parallel when it comes to the C# programming language. If you were a C++ programmer, thee custom modifiers may have made some sense. These modifiers are similar to custom attributes but they are part of the method signature unlike attributes that are not.

 

The modreq modifer means that the caller must understand the modifer whereas modopt means that the modifer may be ignored. These modifiers can be attached to a parameter or a return value, in other words they associate only with a data type. This allows us to associate a type reference with a item.

 

The CLI however does not care about these modifiers other than changing the signature of the method. These modifiers are thus used by tools that interact with the metadata. These tools are compilers, program analysers etc. These modifiers tell us that a modopt may be ignored but a modreq has some special meaning.

 

If this was a C++ book, we would have explained how the concept of a const can be modelled using custom modifiers. Lets look at the first method a1 which has a optional modifer with a type reference to type aa in assembly vijay. The modifiers need a any type reference. This could be a type created somewhere else or in the same file.

 

A int32 type will not suffice. For consistency we have used a type aa for all. The signature for this method reads as 20 00 20 09 01. You will realize that the third byte is not a 1 which is the data type of a void but 0x20. This is the type number for a modopt modifier.

 

These modifiers are followed by a token type that pinpoints the type reference. This is the reason in built types like int32 are not permitted. After the type token follows the data type of the return value. Lets look at method a2 that uses a modreq instead.

 

Its signature is 20 00 1F 09 01 which tells us that a modreq has a type of 0x1f. Finally method a3 is more complex, it starts with one modopt and then we have three modreq modifiers. Its signature is 20 00 1F 15 1F 11 1F 0D 20 09 0F 01. The last two bytes are for the pointer to void.

 

Even though we start with a modopt, then the modreqs, the order is the signature is reversed. This we see the three modreqs first and then the modopt. When we display these custom modifiers we will have to reverse them. Lets start with the method GetElementType first.

 

At the very beginning we would like to find out if there are any custom modifiers. This is most important as custom modifiers come first and then the data type. This is true for a return value as well as for parameters.

 

We pass the function GetModOptOrModReq three parameters, the index of the type in the blob array, the blobarray and we expect a string that has all the modopt and modreq modifiers. Also we expect the out parameter to give us the number of bytes taken up by the custom modifiers.

 

We then increase the index variable by the number of bytes taken up by the custom modifiers so we now point to the actual data type. At the end of the GetElementType method we add the howmanybytes variable by the bytestaken or the number of bytes taken up by the custom modifiers.

 

This variable is the out parameter for the GetElementType method. Finally we add the modoptstring to the end of the returnstring and not at the beginning with a space. Lets move on the GetModOptOrModReq that actually does all the work. Most of the time, custom modifiers are rarely used and this method will do nothing.

 

Thus we initialize the out parameter bytestaken to b zero and the returnstring to a empty string. If and only if the byte denoted by the index parameter is a modreq 0x1f or a modopt  0x20, do we need to do something. As we may have scores of custom modifiers we enter a infinite while loop.

 

The fact that we are here now means that we have at least one custom modifer. We then call the  method GetTokenType to figure out the token that follows a custom modifer. This method remains the same like before but with a small else added.

 

Earlier we called this function for a class or valuetype token and within if statements added a class or valuetype to the value returned by the DecodeToken method. Now as we are calling it from a third method, we need to add no such embellishments to the token string and thus the final else simply initializes the return value.

 

Thus variable tokens now contains the type reference token as a string but we need to place this token in a modopt or modreq keyword. Thus we check whether the byte type is that of modopt 0x20 or modreq 0x1f. We use the variable whileindex for this offset.

 

We now set bytestaken to increase by the number of bytes the token has taken that is stored in noofbytes and again by 1 as one byte gets taken by the custom modifier. We then set the return string returnstring to this value of the token but as the world is reverse, we add the tokens at the beginning and not at the end.

 

We increase the whileindex by the same amount as bytestaken and now check if the next byte after the custom modifier is also another custom modifer. If it is we go back to the loop, if not we end the loop. Finally these is an extra space left in this string returnstring which we remove using the Remove method.

 

This is how we handle custom modifiers.

 

Program33.csc

public void CreateMethodDefSignature (byte [] blobarray , int row)

{

string returntypestring = "";

returntypestring = GetElementType(index  , blobarray , out howmanybytes ,row , "Method") + " ";

methoddefreturnarray[row] = returntypestring;

}

public void DisplayAllMethods (int typerow)

{

methodstring = methodstring  + ".method ";

methodstring = methodstring + "/*06" + methodindex.ToString("X6") + "*/ " ;

string s = methodstring + "  " + methoddefreturnarray[methodindex] + " " + GetString(MethodStruct[methodindex].name);

Console.WriteLine(s);

}

public string GetElementType ( int index , byte [] blobarray , out int howmanybytes , int row , string name)

{

howmanybytes = 0;

string returnstring = "";

string modoptstring = "";

int bytestaken;

modoptstring = GetModOptOrModReq ( index , blobarray , out bytestaken);

index = index + bytestaken;

byte type = blobarray[index];

else if ( type == 0x1b)

{

int howmanybytes2 ;

returnstring = GetPointerToFunctionSignature ( index , blobarray , out howmanybytes2 , ref modoptstring, row , name) ;

modoptstring = "";

howmanybytes = howmanybytes2 ;

}

howmanybytes = howmanybytes + bytestaken;

if ( modoptstring != "")

returnstring = returnstring + " " + modoptstring;

return returnstring ;

}

public string GetPointerToFunctionSignature ( int index , byte [] blobarray , out int howmanybytes , ref string modoptstring, int row , string name)

{

int uncompressedbyte;

index = index + 1;

int nobytes ;

int totalbytes = 1;

string returnstring = "";

nobytes = CorSigUncompressData(blobarray , index , out uncompressedbyte);

if ( row != 0 && name == "Method")

{

methoddeftypearray [row] = DecodeFirstByteofMethodSignature (uncompressedbyte , row);

methoddeftypearray[row] = "method " + methoddeftypearray [row];

}

else

returnstring =  "method " + DecodeFirstByteofMethodSignature(uncompressedbyte , row);

index = index + nobytes;

totalbytes = totalbytes + nobytes;

nobytes = CorSigUncompressData(blobarray , index , out uncompressedbyte);

index = index + nobytes;

totalbytes = totalbytes + nobytes;

int nooftypes = uncompressedbyte;

int bytestaken1;

string modoptstring1 = GetModOptOrModReq( index , blobarray , out bytestaken1);

index = index + bytestaken1;

totalbytes = totalbytes + bytestaken1;

returnstring = returnstring + GetElementType(index  , blobarray , out nobytes , 0 , "") ;

index = index + nobytes;

totalbytes = totalbytes + nobytes;

if (modoptstring1!= "")

returnstring = returnstring + " " + modoptstring1 ;

returnstring = returnstring + " *(";

if ( nooftypes == 0)

returnstring = returnstring + ")";

else

for ( int i = 1; i <= nooftypes ; i++)

{

returnstring = returnstring + GetElementType(index  , blobarray , out nobytes ,0 , "");

if ( i != nooftypes)

returnstring = returnstring + ",";

else

returnstring = returnstring + ")";

index = index + nobytes;

totalbytes = totalbytes + nobytes;

}

if ( modoptstring != "")

returnstring = returnstring + " " + modoptstring;

howmanybytes = totalbytes;

return returnstring;

}

 

e.il

.assembly extern vijay

{

}

.class zzz

{

.method public static method unmanaged stdcall int32*(int8,int16,string ) a1(unsigned int8  a)

{

}

.method public static method unmanaged fastcall int32  modopt(zzz) modopt ([vijay] yyy) modopt ([vijay] xxx)  *(int8,int16,string ) modreq([vijay]a1) modreq([vijay]a2) a2(int8  a, int16 b)

{

}

.method public static method unmanaged thiscall int32  *(int8,int16,string ) modreq([vijay]a1) modreq([vijay]a2) a3(int8  a, int16 b)

{

}

.method public static method unmanaged cdecl int32  modopt(zzz) modopt ([vijay] yyy) modopt ([vijay] xxx)  *(int8,int16,string ) a4(int8  a, int16 b)

{

}

}

 

Output

 

  .method /*06000001*/   int32 *(int8,int16,string)  a1

  .method /*06000002*/   int32 modopt(zzz/* 02000002 */) modopt([vijay/* 23000001 */]yyy/* 01000002 */) modopt([vijay/* 23000001 */]xxx/* 01000003 */) *(int8,int16,string) modreq([vijay/* 23000001 */]a1/* 01000004 */) modreq([vijay/* 23000001 */]a2/* 01000005 */)  a2

  .method /*06000003*/   int32 *(int8,int16,string) modreq([vijay/* 23000001 */]a1/* 01000004 */) modreq([vijay/* 23000001 */]a2/* 01000005 */)  a3

  .method /*06000004*/   int32 modopt(zzz/* 02000002 */) modopt([vijay/* 23000001 */]yyy/* 01000002 */) modopt([vijay/* 23000001 */]xxx/* 01000003 */) *(int8,int16,string)  a4

 

In the above example the DisplayAllMethods function has only one change and that is the name of the method stored in the field name is also displayed. The major changes takes place in the GetElementType method. We have added two more parameters to this method. This is how code gets written in real life.

 

As we did no prior planning earlier, we did not account for the fact that the existing parameters for the GetElementType method we not enough. Thus each and every place we have called the GetElementType method in the past, we now have to add these two extra parameters.

 

As these earlier calls did not use these parameters, we use 0 and the empty string “”. The only place we use it is in the CreateMethodDefSignature method where we pass the row number in the method table for the fourth  parameter and the fifth is where we have called the GetElementType method from, the Method table obviously.

 

There is one more addition to the GetElementType method and that is the type 0x1b that stands for a full method signature. Lets look at the first method a1 in the il file. The first change is the addition of the word method to the calling convention. This is followed by the data type int32 and then a star.

 

This complicates life as we now have a pointer to a function. The open and close brackets now encompass the parameters of this function. Whenever we have a pointer to a function, we shall see a 0x1b in our signature byte. These bytes now look as 00 01 1B 02 03 08 04 06 0E 05.

 

The first is the calling convention byte that is zero, followed by the number of parameters of the method a1 01. Then we get a 0x1b which is the type for a full method signature following. This byte now has the calling convention byte first and 2 is stdcall. As this is a pointer to a function, we need to add the word method to the calling convention.

 

The next byte is the stdcall calling convention followed by not the count bytes of number of parameters to function a1 but to the function that the return value points to 3. Each and every function call returns a value and in our case our function returns a int32 and its type byte 08 is next. This is followed by the three parameter types to the method a int8 or 04, then a int16 a 06 and a string 0e.

 

This completes the pointer to method and then we have the data type of the first parameter. Method a2 complicates life a little bit for us. The modopt and modreq appear after any data type. We have two data types in a pointer to function situation, the first is the int32 or return type of the pointer method.

 

In function a2 we have a series of modopt which if you forgotten is 0x20 and then we have a series of modreqs or 0x1f after the pointer function parameters and before the name of the function. Thus the signature bytes read as 00 02 1F 15 1F 11 1B 04 03 20 0D 20 09 20 08 08 04 06 0E 04 06.

 

This make look complex but if you look at them closely, you will see that byte 3 onwards are the modreqs 0x1f. Thus what we write at the end come first in the signature. This is then followed by the signature of the pointer to function 0x1b and the next two bytes.

 

Then come the modopt’s and return value of the pointer, the three function pointer data types and the data types of the three parameters. Methods a3 and a4 have the required modifer at the beginning or at the end. First lets look at the code of the GetElementType method All that we do is what we have always done.

 

We call a method GetPointerToFunctionSignature which takes the same five parameters passed to the GetElementType method. The fourth is a valid row number of the method in the method table and the last is the string Method. In the same vein the howmanybytes variable is set to the third parameter of the GetPointerToFunctionSignature method.

 

The major deviation is that we set the variable modoptstring to null which means that we undo the effect of the first GetModOptOrModReq method called. Also we pass this string modoptstring  as the fifth parameter. Coming to the GetPointerToFunctionSignature method we start with using the index variable as an offset into the signature.

 

We are now at the 0x1b and need to pick up the calling convention byte. We increase the index variable by 1 and also use the totalbytes to tell us how many bytes the pointer to function signature consumes. As the 0x1b takes up one byte, we start variable totalbytes with 1 and not zero.

 

Now we need to decode the calling convention byte using the method DecodeFirstByteofMethodSignature that we have done before. We also need to add the words method before it. The only problem is that for a method we have stored the calling convention in the array methoddeftypearray.

 

For others like a local var signature, method ref signature etc we have created no such array. Thus we check if the row is non zero and the name parameter is Method. If yes we simply place the calling convention string in the array  methoddeftypearray.

 

If another signature, we add the calling convention to the returnstring variable that will contain the entire signature to be returned. The index and totalbytes variable will move in lock step always. We then take care of the number of parameters to the function by storing the result in nooftypes that we use later to simply print out the data types to the method.

 

Now we may or may not have custom modifiers modopt or modreq. Here the modifiers are the one before and the first set of modifiers are stored in the string modoptstring. We then call the GetElementType method to gives the data type of the pointer to function which is a int32 in our case.

 

We once increase the index and totalbytes variables. We now check whether the modoptstring1is blank or not as we need to add the modopt string with a space. These custom modifiers are the custom modifiers that we have just got. We then place the * and the brackets and if the function has no data types, i.e. nooftypes is zero we add the closing brackets.

 

We then in a for loop use GetElementType to give us the data type of each parameter. We also know that of the last parameter we do not put a comma but a close bracket. Finally we add the last modoptstring that actually comes first in the signature byte and we captured at the beginning of the GetElementType method.

 

We also initialize the out parameter howmanybytes to totalbytes. This completes what we believe is the most difficult method signature.

 

Finally we have used the same pointer to function as a parameter. .method public static int32 a2(method unmanaged stdcall int8 modopt([vijay]aa) modopt([vijay]bb) * (int16,int32) modreq([vijay]cc) a )

{

}

 

Its signatures bytes are 00 01 08 1F 11 1B 02 02 20 0D 20 09 04 06 08. Thus life remains the same whether we deal with parameters or return values.

 

We finally take a real complex method signature. First lets take a look at the il file.

 

e.il

.assembly extern vijay

{

}

.class zzz

{

.method public static method unmanaged stdcall bool modopt([vijay]bb) * ( method unmanaged thiscall  int8 modreq([vijay]cc) modreq([vijay]dd) *(int16,method unmanaged fastcall int32 modreq([vijay]z1) modopt([vijay]z2) * ( bool , unsigned int32) ) , int64 , unsigned int64) modreq([vijay]ee) a2()

{

}

}

 

Looks very intimidating but lets take it one byte at a time by looking at the signature generated. This looks like 00 00 1F 1D 1B 02 03 20 09 02 1B 03 02 1F 11 1F 0D 04 06 1B 04 02 20 19 1F 15 08 02 09 0A 0B.Lets as always start from the beginning taking.

 

The first 00 is the calling convention and the second 00 the number of parameters the function a1 takes that are none. Then we come across the modreq modifer 1f that is the last modreq modifer modreq([vijay]ee). Remember these modifiers are stored backwards, the last is seen first.

 

Then we have a 0x1b or the pointer to function signature. The 02 is the standard calling type stdcall of the main pointer to function that is followed by the number of parameters 03 that this pointer has. The last two are a int64 and unsigned int64. This is followed by the two modopt’ s modopt([vijay]aa) modopt([vijay]bb)  that we have before the *.

 

The 02 is the data type for a bool which is the return value data type for the pointer to function. Then we have the data type of the first parameter out of three which unfortunately starts with a 0x1b or a pointer to function. The 03 is the data type fir a thiscall and the 02 is the number of parameters of this pointer to function.

 

The two 0x1f’s are the modreq’s modreq([vijay]cc) modreq([vijay]dd) and the 04 is the data type int8 the return type of this pointer to function. As we have two parameters to this function a int16 whose data type is 06. The second parameter is a another pointer to function and hence we see a 0x1b.

 

Its calling convention is 04 or fastcall. The next 02 tells us that we have two parameters. We have a modopt and a modreq following modreq([vijay]z1) modopt([vijay]z2). The int32 data  is the return type of the function and this accounts for the 08. The 02 and 09 and the bool and unsigned int32 data types of the innermost pointer to function.

 

Finally the in64 and unsigned int64 have a data type 0f 0x0a and 0x0b that take up the last two bytes of the signature.

 

The following il file hangs ilasm.

 

.method public static method unmanaged stdcall bool modopt([vijay]bb) * ( method unmanaged thiscall  int8 modreq([vijay]cc) modreq([vijay]dd) *(int16,method unmanaged fastcall int32 modreq([vijay]z1) modopt([vijay]z2) * ( bool , unsigned int32) ) ) modreq([vijay]ee) a2(int8 aa, int16 bb)

{

}

 

Program34.csc.txt

public void DisplayAllMethods (int typerow)

{

if ( TypeDefStruct == null)

return;

if ( MethodStruct == null)

return;

int start , startofnext=0;

start =  TypeDefStruct[typerow].mindex ;

if ( typerow == (TypeDefStruct.Length -1) )

{

startofnext= MethodStruct.Length;

}

else

startofnext = TypeDefStruct[typerow+1].mindex ;

for ( int methodindex = start ; methodindex < startofnext ; methodindex++)

{

string methodstring = CreateSpaces(spacesforrest);

if ( IsTypeNested(typerow))

methodstring = methodstring + CreateSpaces(spacesfornested);

methodstring = methodstring  + ".method ";

methodstring = methodstring + "/*06" + methodindex.ToString("X6") + "*/ " ;

string s = methodstring + methoddefreturnarray[methodindex] + GetString(MethodStruct[methodindex].name) + "(" + methoddefparamarray [methodindex] + ")" ;

Console.WriteLine(s);

}

}

string [] paramnames;

public void CreateMethodDefSignature (byte [] blobarray , int row)

{

//Console.WriteLine("CreateMethodDefSignature Array Length={0} method row={1} name={2}" , blobarray.Length , row , GetString(MethodStruct[row].name));

int howmanybytes,uncompressedbyte , count , index;

index = 0;

howmanybytes = CorSigUncompressData(blobarray , index , out uncompressedbyte);

methoddeftypearray[row] = DecodeFirstByteofMethodSignature(uncompressedbyte , row);

index = index + howmanybytes;

howmanybytes = CorSigUncompressData(blobarray , index , out uncompressedbyte);

count = uncompressedbyte;

methoddefparamcount[row] = count;

index = index + howmanybytes;

string returntypestring = "";

returntypestring = GetElementType(index  , blobarray , out howmanybytes ,row , "Method") + " ";

index = index + howmanybytes;

methoddefreturnarray[row] = returntypestring;

string returnstring1 = "" ;

string returnstring2 = "";

string typestring = "";

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

{

typestring = GetElementType ( index , blobarray , out howmanybytes , 0 , "") ;

index = index + howmanybytes;

returnstring2= returnstring2 + typestring ;

if ( l != count)

returnstring2 = returnstring2 + ",";

int ind = GetParamRowNumber (row, l );

Now every parameter like a return value can have attributes like in, out, opt as well as marshalling information. The GetParamAttrforParamCalling function takes the param row number and returns the attributes.

 

string paramcalling = GetParamAttrforParamCalling (ind);

string parammarshal = GetParamAttrforParamMarshal(ind , 1 ) ;

returnstring1 = returnstring1 + paramcalling ;

returnstring1 = returnstring1 + typestring + " " ;

returnstring1 = returnstring1 + parammarshal ;

returnstring1 = returnstring1 + GetParamNameForMethod( row , count , l);

if ( l != count)

returnstring1 = returnstring1 + "," ;

methoddefparamarray[row] = returnstring1;

methoddefparamarray1[row] = returnstring2;

}

}

public string GetParamNameForMethod (int row , int count , int l)

{

string returnstring = "";

string mattributes = GetMethodAttribute(MethodStruct[row].flags , row);

if ( ParamStruct == null)

{

//isnan.exe

if ( mattributes.IndexOf("static") != -1)

l--;

return  "A_" + l.ToString();

}

int start , startofnext=0 , paramcount;

start =  MethodStruct[row].param ;

if ( row  == (MethodStruct.Length -1) )

startofnext= ParamStruct.Length;

else

startofnext = MethodStruct[row+1].param;

paramcount =   startofnext - start;

int ind;

bool paramfound = false;

for ( ind = start ; ind < startofnext ; ind++)

{

if ( ParamStruct[ind].sequence == l)

{

paramfound = true;

break;

}

}

if ( paramfound && paramnames[ind] != "" )

returnstring = NameReserved(paramnames[ind]);

else

{

//Interop.orders.dll

if ( mattributes.IndexOf("static") != -1)

l--;

returnstring = "A_" + l.ToString();

}

return returnstring;

}

int GetParamRowNumber( int row , int l )

{

if ( MethodStruct == null ||  ParamStruct == null)

return 0;

int start =  MethodStruct[row].param ;

int end = 0;

if ( row  == (MethodStruct.Length -1) )

end = ParamStruct.Length;

else

end = MethodStruct[row+1].param;

end--;

int i;

for ( i = start ; i <= end ; i++)

{

if ( ParamStruct[i].sequence == l)

return i;

}

return 0;

}

public string GetParamAttrforParamMarshal (int paramindex , int tabletype)

{

string returnstring = "";

if (ParamStruct == null  )

return "";

if (paramindex >= ParamStruct.Length )

return "";

int pattr = ParamStruct[paramindex].pattr;

returnstring = DecodeParamAttributes( pattr , tabletype , paramindex , 0x2000);

return returnstring;

}

public string GetParamAttrforParamCalling (int paramindex)

{

string returnstring = "";

if (ParamStruct == null  )

return "";

if (paramindex >= ParamStruct.Length )

return "";

if (ParamStruct[paramindex].sequence == 0)

return returnstring;

int pattr = ParamStruct[paramindex].pattr;

if ( (pattr & 0x01) == 0x01)

returnstring = returnstring + "[in]" ;

if ( (pattr & 0x02) == 0x02)

returnstring = returnstring + "[out]" ;

if ( (pattr & 0x10) == 0x10)

returnstring = returnstring + "[opt]" ;

if ( returnstring != "")

returnstring = returnstring + " ";

return returnstring ;

}

public void FillArray ()

{

int old = tableoffset;

bool tablehasrows = tablepresent(8);

int offs = tableoffset;

tableoffset = old;

if ( tablehasrows )

{

paramnames = new string[rows[8] + 1];

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 = ReadStringIndex(metadata, offs);

offs += offsetstring;

string dummy = GetString(name);

paramnames [k] = NameReserved(dummy);

}

}

}

 

 

e.il

.assembly extern vijay

{

}

.class zzz

{

.method void a1(int8 i1 , int16 i2)

{

}

.method void a2(int8  , int16 i3)

{

}

.method static void a21(int8  , int16 i3)

{

}

.method static void a3(int8  , int16 )

{

}

.method [in] void a4( int8 i4 , int16 i5)

{

}

.method  void marshal (int32) a5 ([out] int8 i4 , int16 i5)

{

}

.method void  modopt([vijay]aa) modreq([vijay]bb) a6([out] int8 i4 , int16 i5)

{

}

.method public static method unmanaged stdcall int32*(int8,int16 aa ,string bb) a7(unsigned int8  a)

{

}

//.method public static method unmanaged stdcall bool *( method unmanaged thiscall  int8 *(int16,method unmanaged fastcall int32 * ( bool pp, unsigned int32 kk) ) , int64 gg, unsigned int64 dd) a82(){}

}

 

Method name=a1 ParmNo=1

Method name=a2 ParmNo=3

Method name=a21 ParmNo=5

Method name=a3 ParmNo=7

Method name=a4 ParmNo=9

Method name=a5 ParmNo=12

Method name=a6 ParmNo=15

Method name=a7 ParmNo=17

Param 1 name=i1 seq=1 attr=0

Param 2 name=i2 seq=2 attr=0

Param 3 name=A_0 seq=1 attr=0

Param 4 name=i3 seq=2 attr=0

Param 5 name=A_0 seq=1 attr=0

Param 6 name=i3 seq=2 attr=0

Param 7 name=A_0 seq=1 attr=0

Param 8 name=A_1 seq=2 attr=0

Param 9 name= seq=0 attr=1

Param 10 name=i4 seq=1 attr=0

Param 11 name=i5 seq=2 attr=0

Param 12 name= seq=0 attr=8192

Param 13 name=i4 seq=1 attr=2

Param 14 name=i5 seq=2 attr=0

Param 15 name=i4 seq=1 attr=2

Param 16 name=i5 seq=2 attr=0

Param 17 name=a seq=1 attr=0

 

Output

  .method /*06000001*/ void a1(int8 i1,int16 i2)

  .method /*06000002*/ void a2(int8 A_0,int16 i3)

  .method /*06000003*/ void a21(int8 A_0,int16 i3)

  .method /*06000004*/ void a3(int8 A_0,int16 A_1)

  .method /*06000005*/ void a4(int8 i4,int16 i5)

  .method /*06000006*/ void a5([out] int8 i4,int16 i5)

  .method /*06000007*/ void modopt([vijay/* 23000001 */]aa/* 01000002 */) modreq([vijay/* 23000001 */]bb/* 01000003 */) a6([out] int8 i4,int16 i5)

  .method /*06000008*/ int32 *(int8,int16,string) a7(unsigned int8 a)

 

In the DisplayAllMethods method we now print out the contents of the array methoddefparamarray. In this example we will display the method along with all the parameter data types and names. Before we move onto the code, lets look at the il file first and at the same time we have displayed the rows from the method table as well as the Param table.

 

Lets look at method a1 in the il file, it has two parameters i1 and i2. The method table has a field called Param that behaves like the method field in the TypeDef table. The current row tells us the first row number in the Param table and the next record tells us the first row of the next method in the Param table.

 

Thus for row 1 in the method table the param field has a value of 1 and for the next row the same field has a value of 3 as the first method has two params. Moving on to the Params table, the name field is the name of the param that is i1 and i2. In the second method a2, the first param has no name and the second param a name i1.

 

In il we are allowed to specify a parameter but give it no name. In spite of this, ilasm adds two records to the param table and lets the second param have its name i3, but gives the first param a name A_0. In method a21, the keyword static has been added and we yet have the unknown name parameter as A_0.

 

Thus adding static does not change the name that ilasm automatically chooses. In method a3, both the parameters have no names and the system decides the names A_0 and A_1 respectively. This creates one more parameter that has no name and a sequence number of zero.

 

In method a5 we have added the marshal keyword which also add a new parameter with no name but a sequence number of zero. In method a6 we have the modifiers modopt and modreq but this does not effect the number of rows in the param table unlike the earlier two cases.

 

Method a7 has a return value of a pointer to function but this does not matter to ilasm as the function called may have parameters but we do not specify any names. If we do, they get ignored. Now lets take a look at the CreateMethodDefSignature method that fills up the array methoddefparamarray with all the parameter data types and names.

 

The count variable tells us the number of parameters that we have and we iterate all the parameters using a for loop. We store in typestring the parameter data type that we get using the GetElementType method. We do not specify the method row number and a method name as we do not want to add the words method to the calling convention.

 

The returnstring2 variable is set to the value of typestring and then we decide to add a comma or not. The comma should be added to separate all parameters but the last. We now that we are on the last parameter when variable count is equal to the variable l.

 

Hey where are the parameter names. We need two types of signatures, one with parameter names when we display a declaration of a function. When we call a function we also need the signature but without the parameter names and only the data types. Thus we have two separate strings returnstring2 and returnstring1.

 

The function GetParamRowNumber is passed two parameters, the method row number and the parameter number l. Given these two values, it must return the corresponding row number owned by this parameter. If for some reason, a parameter does not own any row, this function returns zero.

 

Lets figure out how this function works. We first store in the start and end variable the first and last parameter row owned by this method. We reduce the end variable by 1 as we do not, it will give us the starting row of the next method. Then we iterate in a for loop starting from start and ending with end.

 

This means that if our method owns 5 rows in the param table, this loop will iterate five times. All that we check is for a record with the same sequence number that the parameter has. If we meet a match we return the param row number stored in the loop variable i.

 

If we get out of the loop, it means that this param have no row in the param table and hence we return 0. Earlier we showed you that in spite of having parameters with no names and attributes, the system yet allocated a row in the param table and gave the parameter a name. This is what the current verison of ilasm does.

 

Thus all that we need to do is figure out the starting param row number and then add l to it. The only precaution we need to take is ask whether we have a param number with a sequence of zero. The point is that today every parameter owns one record in the param table.

 

Earlier versions of ilasm made our lives more difficult and for reasons of efficiency did not allocate one row to the param table. Thus in method a2, the first param that has no name would not be allocated a row in the param table. Thus method a2 would have only one row in the param table with a sequence number of 2. Method a3 would be worse.

 

It would be allocated no row in the param table as both params have no names or attributes. Thus we cannot assume that every param has a row in the param table. But if it has one, the sequence number field will have a value corresponding to the variable l. We had a method called GetParamAttrforMethodCalling which returned the attributes.

 

There we read from the MethodStruct  table and here we are reading from the ParamStruct table. As the code for the attributes is not very large we decided to simply copy, cut paste from the earlier method. A better way would be to create a separate method for the common code which both methods could then call.

 

Hey but we do not want to win a prize for writing politically correct code. We did not make the same mistake for the method GetParamAttrforParamMarshal where we now call the DecodeParamAttributes with the attribute byte that we read from the Param table.

 

The tabletype is 1 and the flags is 0x2000 to specify a param attribute and not a return value attribute. Now lets move on to the most important method GetParamNameForMethod that does the real work. We pass it the Method row number, the count of parameters as well as the parameter number.

 

The first problem is that we may have a situation where we have 10 methods, all of them with parameters but none of them with a name. This would give us as per the earlier compiler a empty param or no param table. Thus we first check whether our param table is empty. If yes, then we need to name the parameter ourselves.

 

The naming convention is that the parameter names start with 1 for instance methods and one less for static methods. Thus if the method has a static attribute, we reduce the parameter number stored in l by 1 and then create a name A_ along with the parameter number.

 

We then figure out as before the stating and end param table row number but as we do not reduce the startofnext by one, in the for loop we remove the equal to sign. Also the paramcount variable tells us the number of parameters as per the Method table.

 

The count variable is as per the method signature. Today both are the same, Yesterday this need not be true. As before we loop in the for statement and leave when we find a sequence number that matches our parameter count number stored in variable l. Along the way we set the paramfound parameter to true.

 

We first move to our method FillArray where we have filled up our paramnames array with the names of the parameters. This is really not necessary as all that we do is fill up the paramnames array with the name of the param stored in the name field. We then ask if the row has a param, then the name would be stored in the paramnames array.

 

At times however, the parameter has a row in the param array but the name is a null. For these cases the else must be called. The else must also be called when the param has no row un the param table. Once again the earlier rules apply, reduce by 1 if it is static method.

 

The following gives us the wrong answer for the parameter names. The sequence numbers go haywire.

 

.method public static method unmanaged stdcall bool modopt([vijay]bb) * ( method unmanaged thiscall  int8 modreq([vijay]cc) modreq([vijay]dd) *(int16,method unmanaged fastcall int32 modreq([vijay]z1) modopt([vijay]z2) * ( bool , unsigned int32) ) , int64 , unsigned int64) modreq([vijay]ee) a2(int8 aa, int16 bb)

{

}

 

Method a2 1

Param seq=4 name=aa attr=0

Param seq=2 name=bb attr=0

 

We have not finished methods yet and we have miles to go.

 

Program35.csc.txt

public void DisplayAllMethods (int typeindex)

{

if ( TypeDefStruct == null)

return;

if ( MethodStruct == null)

return;

int start , startofnext=0;

start =  TypeDefStruct[typeindex].mindex ;

if ( typeindex == (TypeDefStruct.Length -1) )

{

startofnext= MethodStruct.Length;

}

else

startofnext = TypeDefStruct[typeindex+1].mindex ;

bool placedrvazero ;

for ( int methodindex = start ; methodindex < startofnext ; methodindex++)

{

placedrvazero = false;

string methodstring = CreateSpaces(spacesforrest);

if ( IsTypeNested(typeindex))

methodstring = methodstring + CreateSpaces(spacesfornested);

methodstring = methodstring  + ".method ";

methodstring = methodstring + "/*06" + methodindex.ToString("X6") + "*/ " ;

string methodattribute = GetMethodAttribute(MethodStruct[methodindex].flags , methodindex);

int pos1 = methodattribute.IndexOf("instance");

if ( pos1 != -1)

methodattribute = methodattribute.Remove(pos1, 9);

string enterformethodattrbute = "";

string enterforreturnvalue = "";

string enterformarshal = "";

string paramattrstring = "";

string parammarshalstring = "";

paramattrstring = GetParamAttrforMethodCalling(methodindex);

parammarshalstring = GetParamAttrforMethodMarshal(methodindex , 0 );

//Console.WriteLine("..methodattribute={0} return={1} param={2} marshal={3} type={4}" , methodattribute , methoddefreturnarray[methodindex] , parammarshalstring , parammarshalstring1 , methoddeftypearray[methodindex]);

//int typeindex = GetTypeForMethod(methodindex);

string nspace= "";

nspace= NameReserved(GetString(TypeDefStruct[typeindex].nspace));

int parenttype = GetParentForNestedType(typeindex);

if ( parenttype  != 0)

nspace = NameReserved(GetString(TypeDefStruct[parenttype].nspace));

bool cat2return = false;

bool cat3returnvaluemarshal = false;

bool cat4 = false;

int howmany = HowManyMethodAttributes(methodattribute);

//cat2return

if ( parammarshalstring == "" )

{

if ( methodattribute == "public static " && !(IsTypeNested(typeindex) && nspace != "") )

cat2return = true;

if ( methodattribute == "private static " && !(IsTypeNested(typeindex) && nspace != "") )

cat2return = true;

if ( methodattribute == "family static " && !(IsTypeNested(typeindex) && nspace != "") )

cat2return = true;

if ( methodattribute == "assembly static " && !IsTypeNested(typeindex) && nspace == "" )

cat2return = true;

 

if ( methodattribute == "public virtual " && !(IsTypeNested(typeindex) && nspace != "") )

cat2return = true;

if ( methodattribute == "private virtual " && !IsTypeNested(typeindex) && nspace == "")

cat2return = true;

if ( methodattribute == "family virtual " && !(IsTypeNested(typeindex) && nspace != "") )

cat2return = true;

if ( methodattribute == "public newslot " && !(IsTypeNested(typeindex) && nspace != "") )

cat2return = true;

if ( methodattribute == "private newslot " && !IsTypeNested(typeindex) && nspace == "")

cat2return = true;

if ( methodattribute == "family newslot " && !(IsTypeNested(typeindex) && nspace != "") )

cat2return = true;

if ( methodattribute == "public final "  )

cat2return = true;

if ( methodattribute == "private final " && !(IsTypeNested(typeindex) && nspace != "") )

cat2return = true;

if ( methodattribute == "family final " )

cat2return = true;

if ( methodattribute == "public abstract " && !IsTypeNested(typeindex) && nspace == "") 

cat2return = true;

if ( methodattribute == "private abstract " && !IsTypeNested(typeindex) && nspace == "")

cat2return = true;

if ( methodattribute == "family abstract " && !IsTypeNested(typeindex) && nspace == "") 

cat2return = true;

if ( methodattribute == "public hidebysig " && !IsTypeNested(typeindex) && nspace == "" )

cat2return = true;

if ( methodattribute == "family hidebysig " && !IsTypeNested(typeindex) && nspace == "" )

cat2return = true;

if ( methodattribute.IndexOf("pinvokeimpl") == -1)

{

if ( howmany == 0 && methoddeftypearray[methodindex].IndexOf("instance ") != -1 )

cat2return = true;

}

}

 

int aa = 0x00;

 

//cat3returnvaluemarshal

if ( parammarshalstring != ""  )

{

if ( methodattribute == "public hidebysig " && nspace == "" && !IsTypeNested(typeindex))

cat3returnvaluemarshal = true;

if ( methodattribute == "family hidebysig " && nspace == "" && !IsTypeNested(typeindex))

cat3returnvaluemarshal = true;

if ( methodattribute == "public virtual " && nspace != "" && !IsTypeNested(typeindex))

cat3returnvaluemarshal = true;

if ( methodattribute == "public virtual " && nspace == "" && !IsTypeNested(typeindex))

cat3returnvaluemarshal = true;

if ( methodattribute == "public virtual " && nspace == "" && IsTypeNested(typeindex))

cat3returnvaluemarshal = true;

if ( methodattribute == "public static " && ! (IsTypeNested(typeindex) && nspace != ""))

cat3returnvaluemarshal = true;

if ( methodattribute == "private static " && ! (IsTypeNested(typeindex) && nspace != ""))

cat3returnvaluemarshal = true;

if ( methodattribute == "family static " && ! (IsTypeNested(typeindex) && nspace != ""))

cat3returnvaluemarshal = true;

 

if ( methodattribute == "assembly static " && !IsTypeNested(typeindex) && nspace == "")

cat3returnvaluemarshal = true;

if ( howmany == 0 && methoddeftypearray[methodindex] == "instance " )

cat3returnvaluemarshal = true;

}

 

//cat4

if (parammarshalstring != "" && methodattribute.IndexOf("pinvokeimpl") == -1)

{

if ( methodattribute == "public virtual " && nspace != "" && IsTypeNested(typeindex))

cat4 = true;

if ( howmany == 0 && IsTypeNested(typeindex) && nspace != "" && methoddeftypearray[methodindex] == "" )

{

bool problem1 = true;

if ( methodindex == aa)

Console.WriteLine("....{0} {1}.." , methoddefreturnarray[methodindex].Length , methoddefreturnarray[methodindex]);

if ( methodattribute == "assembly static " || methodattribute == "public static " || methodattribute == "private static " ||  methodattribute == "family static " )

{

if ( paramattrstring == "" )

problem1 = false;

}

if ( problem1)

cat4 = true;

 

}

if ( methodattribute.IndexOf("hidebysig") != -1)

{

if ( methodattribute.IndexOf("public") != -1 || methodattribute.IndexOf("family") != -1)

{

if ( IsTypeNested(typeindex) || nspace != "" )

cat4 = true;

}

else

cat4 = true;

}

if ( howmany >= 2  )

cat4 = true;

}

 

if ( cat2return )

{

enterformethodattrbute = "";

enterforreturnvalue = "\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);

enterformarshal = "";

}

else if ( cat3returnvaluemarshal)

{

enterformethodattrbute = "";

enterforreturnvalue = "\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);

enterformarshal = "\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);

}

else if ( cat4)

{

enterformethodattrbute = "\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);

enterforreturnvalue = "\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);

enterformarshal = "\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);

}

else

{

enterformethodattrbute = "\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);

enterforreturnvalue = " ";

if (parammarshalstring != "")

enterformarshal = " ";

}

methodstring = methodstring + methodattribute + enterformethodattrbute;

methodstring = methodstring + methoddeftypearray[methodindex] + paramattrstring ;

methodstring = methodstring + methoddefreturnarray[methodindex] + enterforreturnvalue;

methodstring = methodstring +  parammarshalstring + enterformarshal ;

int no = 0;

 

if ( cat2return )

no++;

if ( cat3returnvaluemarshal)

no++;

if ( cat4)

no++;

 

if ( methodindex == aa || no >= 2)

{

Console.WriteLine("...cat2return={0} cat3returnvaluemarshal={1}  cat4={2} howmany={3} methoddeftypearray={4} paramattrstring={5}" , cat2return,cat3returnvaluemarshal,cat4 ,howmany ,methoddeftypearray[methodindex],paramattrstring );

Console.WriteLine("...IsTypeNested={0} nspace={1} parammarshalstring={2}",IsTypeNested(typeindex), nspace,parammarshalstring);

}

if ( methodstring.Length - methodstring.LastIndexOf("\r\n") >= 44 )

{

if ( methodindex == aa || no >= 2)

{

Console.WriteLine("............");

}

methodstring = methodstring.Remove(methodstring.Length-1,1);

methodstring = methodstring + "\r\n" + CreateSpaces(spacesforrest + 8+ spacesfornested);

}

methodstring = methodstring + NameReserved(GetString(MethodStruct[methodindex].name)) ;

if ( methodattribute.IndexOf("privatescope") != -1)

methodstring = methodstring + "$PST06" + methodindex.ToString("X6");

methodstring = methodstring + "(";

Console.WriteLine(methodstring );

}

public int HowManyMethodAttributes(string methodattribute)

{

int count = 0;

if ( methodattribute.IndexOf("hidebysig") != -1)

count++;

if ( methodattribute.IndexOf("newslot") != -1)

count++;

if ( methodattribute.IndexOf("specialname") != -1)

count++;

if ( methodattribute.IndexOf("rtspecialname") != -1)

count++;

if ( methodattribute.IndexOf("final") != -1)

count++;

if ( methodattribute.IndexOf("virtual") != -1)

count++;

if ( methodattribute.IndexOf("abstract") != -1)

count++;

return count;

}

 

e.il

.class t1

{

.method public static void  a1() {}

}

.namespace n1

{

.class t1

{

.method public static void  a2() {}

}

}

.class t2

{

.class t1

{

.method public static void  a3() {}

}

}

.namespace n1

{

.class t2

{

.class t1

{

.method public static [in] int8*  a4() {}

}

}

}

 

Output

.method /*06000001*/ public static void

          a1(

 

.method /*06000002*/ public static void

            a2(

 

.method /*06000003*/ public static void

            a3(

 

.method /*06000004*/ public static

              [in] int8*  a4(

 

This program took us a long time to figure out. You may wonder why as all that it does is simply display  the method declaration out for us. A method declaration consists of the words method, its row number in the method table, followed by the method attributes that the GetMethodAttribute function gives us and we store in the variable methodattribute.

 

This is followed by the return value attributes like in/out for which we have a method GetParamAttrforMethodCalling. We store this string in the paramattrstring variable. Then comes the return type that is stored in the methoddefreturnarray.

 

This is followed by the marshal keyword that is got by the method GetParamAttrforMethodMarshal and stored in the string parammarshalstring. Lastly comes the name of the method. Great, we have fetched all these values before and all that we need to do now is string them together.

 

Right, no wrong. The problem is not of spaces but of the lowly enter. Ildasm can use two or three or four lines to display the method definition. Thus we now need to figure out where the enters are to be placed. This is what is the cause of our troubles today and yours while you read this book.

 

We searched in heaven and hell and could not find the rules of where an enter must be placed. By trial and error we hope that we have succeeded. We first remove the words instance from the variable methodattribute. We do this by first using the method IndexOf which returns either –1 or the position of our string in the string we are looking for.

 

If the string is found, we remove the words instance using the Remove method. The word static however is left as is. This is because the words instance follow the return value, unlike static where they remain separate.

 

We can have a enter at four different places. First at the end of the method attributes, then after the return value. An enter can also come after the marshal keyword and finally before the name of the function if it is larger than a certain length.

 

Thus we initialize three string variables that will store a space or an enter depending upon the circumstance. To figure out where the enter needs be placed we first need to know the type row number that the method belongs to. The parameter typeindex is what contains the type row number for us.

 

We also need to know the namespace the method is in if any. We first assume that the type is in no namespace and hence we can simply store in nspace the name of the namespace given the type. We have with us a method GetParentForNestedType that given the type tells us the parent type which was covered earlier.

 

This function return either the parent type of a zero. Thus if variable parenttype  is non zero, we have a nested type and hence the nspace variable will contain  the namespace of the parent type.

 

We also figured out the hard way that as mentioned earlier where an enter came come so we have three Boolean variables to tells where the enters should happen. The variable cat2return if true tells us that this method should be on two lines with an enter after the return value.

 

This means that the method attributes and the return value is on the same line. The second Boolean cat3returnvaluemarshal is for those cases where the method moves on to three lines and an enter is needed after the return value as well as the marshal keyword. The last cat4 is when the method signature is over 4 lines.

 

After the method attribute, return value as well as the marshal keyword. Before we actually start, there is a useful function called HowManyMethodAttributes. We found out that not only are the enters decided by whether the type is nested or not, whether it is within a namespace or not but also by how many method attributes there are.

 

Thus we need a count of the number of attributes like hidebysig , newslot, specialname, rtspecialname, final, virtual and abstract. We have not counted attributes like the access ones like public or static or pinvokeimpl. We now move on to finding out which category out of our four a method belongs to.

 

The first is when we have the method on two lines and the enter must come after the return value placing the method name on a new line or cat2retrun. To stress test our hypothesis we have used a separate il file for each test. Lets start with public static as the only attributes for our method. 

 

In our il file we try it with four possibilities. In a class with no namespace and no nesting, in a class that is placed within a namespace , in a class that is nested, and finally a nested class as well as in a namespace. What we leaned that as long as the class does not have both a namespace and is nested, the cat2return will be true.

 

Thus the if statement is true for all cases but those where both nspace is not null as well as the type is not nested. This case only applies if the marshal keyword is absent or the variable parammarshalstring is null. The private static and public static also follow the same rules and hence are not mentioned.

 

The deviation is assembly static where cat2return is true only is both the type is not nested and the namespace is empty. Thus we have realized that there was no consistency in where the enters are to be placed.

 

The next three if statements check whether the method attribute is public virtual, private virtual as well as family virtual and that both type is not nested as well as the namespace is not null. Fortunately the public newslot, private newslot and family newslot behave in the same way.

 

However for public final family final, the namespace and nesting is not relevant but for private final both namespace and nesting cannot be true. When it comes to public abstract, private abstract and family abstract, there must be no nesting and the namespace must be null.

 

The same rules also apply to public hidebysig and family hidebysig also. The last rule is pretty different. First the pinvokeimpl attribute must be absent. Then this must apply to an instance method and not a static one. Finally there must be no attribute like newslot, final etc be present.

 

This can happen only if the howmany variable is zero. Thus lets look at a specific case.

 

.class t1

{

.method public void  a1() {}

.method void  a2() {}

}

 

  .method /*06000001*/ public instance void

          a1(

  .method /*06000002*/ privatescope instance void

          a2$PST06000002(

 

Thus if we have no attributes other than the access modifiers and privatescope is the default for no access modifer we meet the last rule.

 

Lets move on to the second condition where we place the message definition on three lines with a enter after the return values as well as after the keyword marshal. This obviously assumes that the keyword marshal is exists. Lets take the case of the attributes public static but with the marshal keyword present unlike earlier.

 

.class t1

{

.method public static void  marshal(int32 )a1() {}

}

.namespace n1

{

.class t1

{

.method public static void  marshal(int32 ) a2() {}

}

}

.class t2

{

.class t1

{

.method public static void  marshal(int32 ) a3() {}

}

}

.namespace n1

{

.class t2

{

.class t1

{

.method public static [in] int8* marshal(int32 ) a4() {}

}

}

}

 

.method /*06000001*/ public static void

          marshal( int32)

          a1(

.method /*06000002*/ public static void

            marshal( int32)

            a2(

.method /*06000003*/ public static void

            marshal( int32)

            a3(

.method /*06000004*/ public static

              [in] int8*

              marshal( int32)

              a4(

 

The same il file as before now with the marshal keyword added. The first three methods come on three lines with a enter after the return value and marshal keyword. When the class is nested as well as the namespace is present, the method falls over four lines.

 

The same rules apply for private static and family static. The odd man out is always assembly static where the type must not be nested as well as the type cannot be present within a namespace. The attributes public hidebysig and family hidebysig are like assembly static. Some things do not change.

 

The attributes public virtual are like public static but we have written them as three separate conditions instead of one. You figure out which one is better for you to understand. The C# compiler does not seem to care.

 

The last rule is like the earlier case where we had a single access attribute and nothing else and a instance function. Some similarity with the cat2return case but not enough to generalize them in code.

 

Lets take the case where everything falls over four lines. This will set the cat4 variable to true. The two general conditions are that the marshal keyword must be present and the pinvokeimpl attribute absent. The simple case of public virtual shows the method definition over four lines only for the most complex case, nesting and  namespace.

 

.namespace n1

{

.class t2

{

.class t1

{

.method public virtual [in] int8* marshal(int32 ) a4() {}

}

}

}

 

.method /*06000001*/ public virtual

              instance [in] int8*

              marshal( int32)

              a4(

 

Thus the method definition is placed over four lines.

 

The second case is more complex. It starts with us having a single access modifier as the howmany variable is zero. Then the type must be nested and the namespace not empty and the method being static and not instance.

 

We set the variable problem1 to true and this sets off the cat4 variable being true. This is demonstrated by methods a1 and a2. The only problem is that it works for attributes like privatescope and famandassem and not for assembly static, public static, private static and family static.

 

Here we have two further conditions that will set the problem1 variable to false and negate the cat4 variable being set to true. The first case is when the param attribute string is null. Thus methods a3 and a4 have no param attributes like in and out and thus problem1 is false and the method do not fall over four lines.

 

There are however cases where the length of the marshal type matters, but we have ignored them.

 

.namespace n1

{

.class t2

{

.class t1

{

.method static [in] int8* marshal(int32 ) a1() {}

.method famandassem static [in] int8* marshal(int32 ) a2() {}

.method public static void  marshal(int8 ) a3() {}

.method public static int8 ** marshal(int32 ) a4() {}

}

}

}

 

.method /*06000001*/ privatescope static

              [in] int8*

              marshal( int32)

              a1$PST06000001(

.method /*06000002*/ famandassem static

              [in] int8*

              marshal( int32)

              a2(

.method /*06000003*/ public static

              void  marshal( int8)  a3(

.method /*06000004*/ public static

              int8**  marshal( int32)  a4(

 

The complexities have just started. If the attributes contain a hidebysig and within this either public or family. In the first set of the il file, the type is nested and both private and public are seen over two lines. Private is the normal case and public because it is either in a nested class or in a namespace. In the  second case, the class is not nested or in a namespace and the private is on four lines but the public only on three.

 

.class t2

{

.class t1

{

.method private hidebysig [in] int8* marshal(int32 ) a1() {}

.method public hidebysig [in] int8* marshal(int32 ) a2() {}

}

}

.class t1

{

.method private hidebysig [in] int8* marshal(int32 ) a1() {}

.method public hidebysig [in] int8* marshal(int32 ) a2() {}

}

 

.method /*06000001*/ private hidebysig

            instance [in] int8*

            marshal( int32)

            a1(

.method /*06000002*/ public hidebysig

            instance [in] int8*

            marshal( int32)

            a2(

.method /*06000003*/ private hidebysig

          instance [in] int8*

          marshal( int32)

          a1(

.method /*06000004*/ public hidebysig instance [in] int8*

          marshal( int32)

          a2(

 

The final case is when we have two or more than two attributes like newslot, virtual etc and we have a marshal keyword, then everything falls over three lines. Now we have to implement what we have doe so far.

 

Thus we have a series of if statements that set the three string variables enterformethodattrbute, enterforreturnvalue and enterformarshal to their values. If cat2return is true, then there should be nothing after the method attribute and we set the enterformethodattrbute variable to null.

 

This also means that some later we will concatenate the methodattribute variable with the  enterformethodattrbute variable and then concatenate the return value stored in the methoddefreturnarray with the enterforreturnvalue variable.

 

As we want an enter after the return value we set the enterforreturnvalue to an enter followed by spaces. These depend upon the nesting level as well as the words .method. Then we come to the second case cat3returnvaluemarshal where we make the string enterformethodattrbute null as no enter after the method attributes but we need a enter after the return value and the enterformarshal.

 

For the cat4 we need an enter placed for all the three variables. Finally we let you on a secret. The most common occurrence is when the everything is placed on two lines with a enter after the method attribute. This places the return value on a new line along with the method name.

 

As this occurs most often we put it in the final statement. This is why our code is s small as the else takes care of everything else. The il file has a small sample.

 

.class zzz

{

.method public newslot virtual final static [in] int8 **a3()

{

}

}

 

.method /*06000001*/ public newslot static final virtual

          [in] int8**  a3(

 

 

The above occurs is the spoilt child and occurs with the maximum frequency. Now we concatenate the strings as explained before to the string methodstring. Finally we have to add the last enter if the length of the string is larger than 44.

 

For some reason ildasm does not like the last line before the method name to exceed 44. We check the length of the string to date and minus the last occurrence of an enter. If this value is greater than 44, we now need to add a enter. But before we do this, we have a space at the end of the line that we need to remove and hence the Remove method removes the last space.

 

Then we add a new enter to the method string. All is not over after adding the name as if this methods attribute is privatescope, we have to add the words $PST with the row number of the method table. We finally add the ( bracket.

 

program36.csc.txt

 

methodstring = methodstring + "(";

int len = 0;

if ( methoddefparamarray[methodindex] != null)

{

int indexofenter = methodstring.LastIndexOf("\r\n");

int lengthofstring = methodstring.Length ;

len = lengthofstring - indexofenter - 2;

methodstring = methodstring + ParamOnMultipleLines (methoddefparamarray[methodindex] , len );

}

else

methodstring = methodstring +  methoddefparamarray[methodindex];

methodstring = methodstring  +  ")";

string implflags = GetMethodAttribute1 (MethodStruct[methodindex].impflags);

methodstring = methodstring + " " + implflags ;

string methodsignature = GetMethodSignatureBytes (methodindex);

methodstring = methodstring + methodsignature;

methodstring = methodstring + "\r\n" + CreateSpaces(spacesforrest+ spacesfornested) + "{" ;

Console.WriteLine(methodstring );

}

}

public string ParamOnMultipleLines (string paramstring , int spaces)

{

if ( paramstring == "")

return "";

if ( paramstring == null)

return "";

string returnstring = "";

if ( paramstring.IndexOf(",") == -1  )

{

paramstring = paramstring.Replace("^" , ",");

return paramstring;

}

char [] chararray = {','};

string [] stringarray = paramstring.Split(chararray);

returnstring = stringarray[0] + ",";

for ( int ii = 1 ; ii < stringarray.Length ; ii++)

{

returnstring = returnstring + "\r\n" + CreateSpaces(spaces) + stringarray[ii] ;

if ( ii != stringarray.Length -1 )

returnstring = returnstring  + ",";

}

returnstring = returnstring.Replace("^" , ",");

return returnstring;

}

public string GetMethodAttribute1 (int methodflags)

{

string returnstring = "";

if ( (methodflags & 0x0003) == 0x0003)

returnstring = returnstring + "runtime ";

else

if ( (methodflags & 0x0001) == 0x0001)

returnstring = returnstring + "native ";

else

returnstring = returnstring + "cil ";

if ( (methodflags & 0x0004) == 0x0004)

returnstring = returnstring + "unmanaged";

else

returnstring = returnstring + "managed";

if ( (methodflags & 0x0080) == 0x0080)

returnstring = returnstring + " preservesig";

if ( (methodflags & 0x0010) == 0x0010)

returnstring = returnstring + " forwardref";

if ( (methodflags & 0x01000) == 0x1000)

returnstring = returnstring + " internalcall";

if ( (methodflags & 0x0020) == 0x0020)

returnstring = returnstring + " synchronized";

if ( (methodflags & 0x0008) == 0x0008)

returnstring = returnstring + " noinlining";

return returnstring;

}

public string GetMethodSignatureBytes (int methodrow)

{

string returnstring =  "\r\n" + CreateSpaces(spacesforrest+ spacesfornested) + "// SIG: ";

int uncompressedbyte;

int signatureindex = MethodStruct[methodrow].signature;

int howmanybytes;

howmanybytes = CorSigUncompressData(blob , signatureindex, out uncompressedbyte);

int count = uncompressedbyte;

if ( howmanybytes == 2)

count++;

for (int j = howmanybytes ; j <= count ; j++)

{

returnstring = returnstring + blob[signatureindex + j ].ToString("X2") ;

if ( j != count )

returnstring = returnstring + " ";

}

return returnstring;

}

 

e.il

.class zzz

{

.method public newslot virtual final static [in] int8 **aa(int8 I , int16 j, int32 k)

{

}

}

 

.method /*06000001*/ public newslot static final virtual

          [in] int8**  aa(int8 I,

                          int16 j,

                          int32 k) cil managed

  // SIG: 00 03 0F 0F 04 04 06 08

  {

 

This program is much simpler than the previous one as we are using it as a breather. The next one obviously will be more problematic to understand. We first place the ( bracket and then we have to place the parameters to the method. If the method has no parameter or a single one very good.

 

If it has more than one, the second one onwards comes on a new line with some spaces so that it is indented one below the other. This we only do if we have parameters and thus we check the value of the methoddefparamarray being non null. We first need to find out where we placed the last enter.

 

This as explained earlier could be at a number of places. We then figure out the length of the string so far and the number of spaces we need to place is the difference between the two and we further need to subtract 2 from this. This is because the data types have to line up one against the other in a lock step fashion.

 

Taking the example of method aa, the last enter has been placed before the name of function and the difference between them will give us in this case the column number where the type name of the first parameter is to be placed.  Looking at it from another viewpoint, we place the type name after the name of the method and the open bracket.

 

After putting the last enter, we added some spaces, we then placed the name of the method and a open bracket. Then we wrote the type name. We want to know how many characters we placed after the last enter bearing in mind that the enter is two characters.

 

We now pass the method ParamOnMultipleLines the array methoddefparamarray that contains the parameters with commas and also the number of spaces. All that we want this method to do is place a enter and len number of spaces after each comma and return such a string that we concatenate with methodstring.

 

The function is pretty simple as we first check for a null or empty string. Remember this function gets called from lots of places. We then check whether we have a comma or not in our paramstring. If we do not then it could only mean that we have only a single parameter and thus we do nothing.

 

At two places in this function we replace a ^ with a , and we will later explain why. Now’s not the time and place. The String class lets us split a string on a series of characters. Whenever someone wants more than one we can give it in an array.

 

Thus the split method requires an array of chars and it will split the string into a series or arrays of strings whenever that character is met. Thus in method aa as we have three parameters and two commas, we will get an array of three strings minus the commas. This happens as we have placed a comma in the chararray array.

 

Had we also placed a semicolon, the Split method would break the string whenever it found a comma or a semicolon. Now we start concatenating the string in the variable returnstring by simply initializing it to the first member of the array stringarray and placing a comma.

 

Then we go in  a loop and keep adding to returnstring an enter and the number of spaces we had calculated earlier. As always we add a comma only if it is not the last member.

 

Now we add the close the bracket and then there are some more attributes that come at the end of the method name. We have a method GetMethodAttribute1 that gets us these attributes and there is a separate field for these attributes in the Method table.

 

If we do not specify an attribute the default is cil managed. We will delve on these attributes in a short while. We now add these final attributes and now we need to display the method signature within comments on a fresh line.

 

We have a method aptly called GetMethodSignatureBytes that does the work for us. This signature we will not explain as we began the create method def method with displaying these bytes off the blob array.

 

We only place a space if it is not the last byte, the same we do for a comma. The reason we increment the count variable by 1 if the howmanybytes variable is 2 as we have taken two bytes for the length of the signature. We also add a enter and a series of spaces as well as the open the brace.

 

e.il

.class zzz

{

.method int8 aa(int8 [3...7,,,0...,45] i)

{

}

}

 

.method /*06000001*/ privatescope instance int8

          aa$PST06000001(int8[3...7,0,0,0,45] i) cil managed

  // SIG: 20 01 04 14 04 05 05 05 00 00 00 2D 05 06 00 00 00 00

 

When we wrote the function ParamOnMultipleLines we did not consider that an array data type can also have commas and in this case we cannot break at the commas. Thus we needed to hide the commas in the array data type. So we came up with an ingenious solution.

 

In the GetArrayType function at three different places we add a comma to the return string. We simply replace these commas with a ! sign. As an example we replace the line returnstring = returnstring + ","; with returnstring = returnstring + "^";.

 

Thus in the param method if we have a single parameter we have replace the ! with a comma and at the end. Then unfortunately a return value can also have arrays and hence the methoddefreturnarray must also have all !’s replaced by commas. Thus in the CreateMethodDefSignature method we must add the line returntypestring = returntypestring.Replace("^",",");

 

This is the quick and dirty way of writing code. Do not think about all the options at the beginning and then keep making changes and watch the program go berserk at the wrong time. Think first and then think again and then write code. Free advise that no one heeds. Now you know what sloppy programming is all about.

 

Program37.csc

string methodsignature = GetMethodSignatureBytes(methodindex);

methodstring = methodstring + methodsignature;

methodstring = methodstring + "\r\n" + CreateSpaces(spacesforrest+ spacesfornested) + "{" ;

int tablerow = entrypointtoken & 0x00ffffff;

if ( tablerow == methodindex )

methodstring = methodstring + "\r\n" + CreateSpaces(spacesforrest + 2 + spacesfornested) + ".entrypoint";

Console.WriteLine(methodstring);

//DisplayCustomAttribute("MethodDef" , methodindex , 2 + spacesforrest + spacesfornested);

//Console.Write(DisplayParamsForMethod(methodindex));

//DisplayMoreParamsForMethod(methodindex);

DisplayAllSecurity( 1 , methodindex );

//DisplayOverrideMethod(methodindex); // interfaces.exe

if (implflags.IndexOf("cil managed") == -1 || MethodStruct[methodindex].rva == 0)

{

bool problem1 = true;

if ( methodattribute.IndexOf("pinvokeimpl") != -1 )

{

if ( methodattribute.IndexOf("pinvokeimpl(/* No map */)") != -1)

{

//problem is that we cannot create one in ilasm because of the No Map and if no map is not there then else gets called

Console.WriteLine("  // Embedded native code");

Console.WriteLine("  //  Disassembly of native methods is not supported.");

Console.WriteLine("  //  Managed TargetRVA = 0x{0}" , MethodStruct[methodindex].rva.ToString("x4"));

Console.WriteLine(CreateSpaces(spacesforrest+ spacesfornested) + "} // end of global method " +  NameReserved(GetString(MethodStruct[methodindex].name)));

}

else

{

methodstring = CreateSpaces(spacesforrest+ spacesfornested) + "}"  ;

Console.Write(methodstring);

}

methodstring = "";

problem1 = false;

}

else if (implflags.IndexOf("runtime managed") != -1)

{

methodstring = "";

}

else if (implflags.IndexOf("runtime unmanaged") != -1)

methodstring = "    //  Method provided by Runtime\r\n";

else if (implflags.IndexOf("native unmanaged") != -1)

methodstring = "    //  Unmanaged TargetRVA = 0x" + MethodStruct[methodindex].rva.ToString("x4") + "\r\n";

else if ( implflags.IndexOf("native") != -1)

{

methodstring = "    //  Disassembly of native methods is not supported.";

methodstring = methodstring + "\r\n    //  Managed TargetRVA = 0x" +  MethodStruct[methodindex].rva.ToString("x4") + "\r\n";

}

else if ( MethodStruct[methodindex].rva == 0 && implflags.IndexOf("unmanaged") == -1 )

{

methodstring = CreateSpaces(spacesforrest+ spacesfornested + 2) + "// Method begins at RVA 0x0\r\n";

}

else

{

methodstring = "";

}

if (problem1)

methodstring = methodstring + CreateSpaces(spacesforrest+ spacesfornested) + "} // end of method " + NameReserved(GetString(TypeDefStruct[typeindex].name)) + "::" + NameReserved(GetString(MethodStruct[methodindex].name)) + "\r\n";

Console.WriteLine(methodstring);

continue;

}

methodstring = "";

methodstring = methodstring + CreateSpaces(spacesforrest + 2 + spacesfornested) +  "// Method begins at RVA 0x" + MethodStruct[methodindex].rva.ToString("x4");

Console.Write(methodstring);

}

}

}

 

e.il

.class abstract zzz

{

.method public static pinvokeimpl("user32.dll" ansi stdcall lasterr ) int8 a1() native unmanaged

{

}

.method static public int8 a0() cil managed

{

.entrypoint

}

.method public int8 a4() runtime managed

{

}

.method public int8 a5() runtime unmanaged

{

}

.method public int8 a6() native unmanaged

{

}

.method public int8 a7() native managed

{

}

.method abstract void  a9() cil managed

{

}

.method public int8 a8() cil unmanaged

{

}

.method void  a10() cil managed preservesig forwardref internalcall synchronized noinlining

{

}

}

//.method public static pinvokeimpl("user32.dll" ansi stdcall lasterr ) int8 a2()

//{

//}

//.method public static pinvokeimpl(/* No map */) unsigned int32  _mainCRTStartup() native unmanaged preservesig{}

 

 

Output1

//Global methods

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.method /*06000001*/ public static pinvokeimpl("user32.dll" ansi lasterr)

        int8  a2() cil managed

// SIG: 00 00 04

{

}

 

Output2

.class /*02000002*/ private abstract auto ansi zzz

       extends [mscorlib/* 23000001 */]System.Object/* 01000001 */

{

  .method /*06000001*/ public static pinvokeimpl("user32.dll" ansi lasterr)

          int8  a1() native unmanaged

  // SIG: 00 00 04

  {

  }

  .method /*06000002*/ public static int8

          a0() cil managed

  // SIG: 00 00 04

  {

    .entrypoint

    // Method begins at RVA 0x2051  .method /*06000003*/ public instance int8

          a4() runtime managed

  // SIG: 20 00 04

  {

  } // end of method zzz::a4

 

  .method /*06000004*/ public instance int8

          a5() runtime unmanaged

  // SIG: 20 00 04

  {

    //  Method provided by Runtime

  } // end of method zzz::a5

 

  .method /*06000005*/ public instance int8

          a6() native unmanaged

  // SIG: 20 00 04

  {

    //  Unmanaged TargetRVA = 0x2054

  } // end of method zzz::a6

 

  .method /*06000006*/ public instance int8

          a7() native managed

  // SIG: 20 00 04

  {

    //  Disassembly of native methods is not supported.

    //  Managed TargetRVA = 0x2055

  } // end of method zzz::a7

 

  .method /*06000007*/ privatescope abstract

          instance void  a9$PST06000007() cil managed

  // SIG: 20 00 01

  {

    // Method begins at RVA 0x0

  } // end of method zzz::a9

 

  .method /*06000008*/ public instance int8

          a8() cil unmanaged

  // SIG: 20 00 04

  {

  } // end of method zzz::a8

 

  .method /*06000009*/ privatescope instance void

          a10$PST06000009() cil managed preservesig forwardref internalcall synchronized noinlining

  // SIG: 20 00 01

  {

    // Method begins at RVA 0x0

  } // end of method zzz::a10

 

} // end of class zzz

 

In this example we once again display some more details about a method. We first want to know whether the method is the first method to be called or does it have the entrypoint directive attached to it. Some years ago we explained the instance variable entrypoint that was stored in the first metadata structure contained the row index of the entrypoint method.

 

We thus knock off the last or high eight bits and store the method row number in the tablerow variable. We then check this value with the variable methodindex and if we find a match we write out the entrypoint directive. Thus method a0 has the entrypoint token and this is why the method is static.

 

We then come across four methods that are commented out and we will deal with them in separate programs following this one. Too many cooks spoil the broth and too many concepts in one program gives Jack a splitting headache. A method at the end also has attributes that are divided into three categories.

 

The first is the nature of code and this can be four of which we do three now. These are il, native and runtime. These are mutually exclusive. The attribute il is the normal one which says the obvious, the code we write is in the IL programming language which is the default.

 

The problem is that using the pinvokeimpl keyword we can specify that the code is somewhere else and hence in a another language and hence the attribute native. For delegates, the code is supplied by the system and hence we have the attribute runtime to describe it. Then we have another complication, managed or unmanaged.

 

Managed code is code written in il that the system guarantees will break no rules and hence is safe to run. Unmanaged code is normally code written with pointers in C# and as no rules can be enforced on it, the system does not guarantee that the code will break no rules and hence unmanaged. Thus we have six combinations, all not valid.

 

Thus native managed makes no sense as the code is somewhere else and the system however cannot take any responsibility for its good behavior. The problem as always is that nobody check or nonsensical combinations. Then finally there are attributes like noinlining that fall into their own separate category.

 

The only attribute that carries code is the one with cil managed. Thus we need to trap those where the attribute is not cl managed and also the rva field that tells us where in memory the code is stored is stored for this function is not zero. If this field is zero, then it only means that there is no code for this method.

 

We first check for the attributes pinvokeimpl. Here we again make a distinction on whether the method is global or not. Functions a1 and a2 have the pinvokeimpl attribute and a2 is global which means that it is outside a type. In C# because of Java, functions outside a class or global are not permitted whereas in il they are.

 

These are artificial rules that programming languages apply for no valid rhyme or reason. Now if we have along with the pinvokeimpl attribute a No map value, then we are specifying that there is no dll that has the code for this method. If we have such a method in our code, ilasm gives us an error.

 

But for some reason there are scores of programs written in other languages that for some reason compile to an executable. One of them is a file called isymwrapper.dll. This .net executable when run through ildasm and then the out through ilasm gives us an error at the pinvokeimpl attribute. The round tripping does not work at all.

 

Thus in the il file we have a example commented out for you to see the error that happens. If the pinvokeimpl attribute takes a No Map attribute, we need to the display three lines along with the rva field. If the no map is absent all that we do is simply display the closing brace and nothing else.

 

We also the methodstring attribute to empty as we have already used the WriteLine function to display its value along with the entrypoint directive outside the if statement. We also set the problem1 bool variable to false as it value by default is true. Now there are some individual cases to handle.

 

If the implflags are runtime managed as in method a4 we need no display anything and hence the methodstring variable is set to false. If the attributes are runtime unmanaged in function a5, or native unmanaged method a6, we need to display another value. Else if it any variation of native like method a7 we display another string.

 

Then if the rva is zero and not unmanaged like method a9 which is abstract and managed we have one more if statement to handle it. Finally for cases that do not fit in any of the above like the likes of method  a8 that does not have the abstract attribute we have the final else that takes of every case which requires an empty string.

 

Method a10 has all the extra attributes tagged along. For all except the pinvokeimpl attribute we have not written the words end of method. Thus only if variable problem1 is not set to false do we set the methodstring to the words end of method.

 

We then also write out the name of the namespace and the name and in all these cases as there is no code, we need to loop back to the start of the for and the next method. If we do not enter this if statement, it only means that we have lots of code and thus we write out the rva of the method and then proceed to display the code that we will display years from now. Lots of other more important work to be done first.

 

We are also displaying one such pinvokeimpl attribute from the file isymwrapper.dll where it is a global method.

 

else if ( implflags.IndexOf("optil") != -1 )

{

methodstring = CreateSpaces(spacesforrest+ spacesfornested + 2) + "// Method begins at RVA 0x" +  MethodStruct[methodindex].rva.ToString("x4") + "\r\n";

methodstring = methodstring + CreateSpaces(spacesforrest+ spacesfornested + 2) + "// Code size       0 (0x0)\r\n";

}

else

{

methodstring = "";

}

 

e.il

.class zzz

{

.method void a1() optil managed

{

}

}

 

Output

  .method /*06000001*/ privatescope instance void

          a1$PST06000001() optil managed

  // SIG: 20 00 01

  {

    // Method begins at RVA 0x2050

    // Code size       0 (0x0)

  } // end of method zzz::a1

 

GetMethodAttribute1

if ( (methodflags & 0x0002) == 0x0002)

returnstring = returnstring + "optil ";

else

returnstring = returnstring + "cil ";

 

There is one last reserved attribute optil that cannot carry any code. Thus in the GetMethodAttribute1 we simply check whether the value is 2 which is the code for the attribute optil. We next add a if statement to check this value of optil to add two lines of code to our method.

 

 

Program38.csc

public void DisplayOverrideMethod (int methodrow)

{

if ( MethodImpStruct == null  )

return;

for ( int ii = 1 ; ii < MethodImpStruct.Length ; ii++)

{

string codeddeftablename = GetMethodDefTable (MethodImpStruct[ii].codeddef );

int codeddefindex = GetMethodDefValue (MethodImpStruct[ii].codeddef );

string codedbodytablename = GetMethodDefTable(MethodImpStruct[ii].codedbody);

int codedbodyindex = GetMethodDefValue(MethodImpStruct[ii].codedbody );

//Console.WriteLine("...codeddeftablename={0} codedbodytablename={1}",codeddeftablename,codedbodytablename);

int typeindex = GetTypeForMethod(codedbodyindex);

if ( typeindex  == MethodImpStruct[ii].classindex && codedbodytablename  == "MethodDef" && codedbodyindex == methodrow)

{

if (codeddeftablename == "MethodRef" )

{

Console.Write(CreateSpaces(spacesforrest+2 + spacesfornested));

Console.Write(".override ");

int typerefindex = GetTypeRefFromMethodRef (codeddefindex);

Console.WriteLine("{0}::{1} /*01{2}::0A{3}*/ " , typerefnames [typerefindex], NameReserved(GetString(MemberRefStruct[codeddefindex].name)) , typerefindex .ToString("X6") , codeddefindex.ToString("X6"));

}

if (codeddeftablename  == "MethodDef" )

{

Console.Write(CreateSpaces(spacesforrest+2 + spacesfornested));

int typedefindex = GetTypeForMethod(codeddefindex);

string nestedtypestring = "";

nestedtypestring = GetNestedTypeAsString (typedefindex);

string nspace = NameReserved(GetString(TypeDefStruct[typedefindex].nspace));

if ( nspace != "")

nspace = nspace + ".";

nspace = nestedtypestring + nspace + NameReserved(GetString(TypeDefStruct[typedefindex].name));

Console.Write(".override {0}/* 02{1} */::{2} " , nspace, typedefindex.ToString("X6"),NameReserved(GetString(MethodStruct[codeddefindex].name)) );

Console.WriteLine("/*02{0}::06{1}*/ " , typedefindex.ToString("X6") , codeddefindex.ToString("X6"));

}

}

}

}

public int GetMethodDefValue(int implcoded)

{

return implcoded >> 1;

}

public string GetMethodDefTable(int implcoded)

{

string returnstring = "";

short tag = (short)(implcoded & (short)0x01);

if ( tag  == 0)

returnstring = returnstring + "MethodDef";

if ( tag  == 1)

returnstring = returnstring + "MethodRef";

return returnstring;

}

public int GetTypeForMethod (int methodrow)

{

int typedefindex = 0;

for ( typedefindex = 1 ; typedefindex < TypeDefStruct.Length - 1  ; typedefindex++)

{

int start = TypeDefStruct[typedefindex].mindex;

int end = TypeDefStruct[typedefindex+1].mindex -1 ;

if ( methodrow >= start && methodrow <= end)

return typedefindex;

}

return typedefindex;

}

public int GetTypeRefFromMethodRef(int methodrefrow)

{

int  memberrefcodedindex = 0;

memberrefcodedindex = MemberRefStruct[methodrefrow].clas;

string tablename = GetMemberRefTable(memberrefcodedindex);

int typerefindex = GetMemberRefValue(memberrefcodedindex);

return typerefindex;

}

public string GetMemberRefTable(int memberrefcodedindex)

{

string returnstring = "";

short tag = (short)(memberrefcodedindex & (short)0x07);

if ( tag  == 0)

returnstring = returnstring + "NotUsed";

if ( tag  == 1)

returnstring = returnstring + "TypeRef";

if ( tag  == 2)

returnstring = returnstring + "ModuleRef";

if ( tag  == 3)

returnstring = returnstring + "MethodDef";

if ( tag  == 4)

returnstring = returnstring + "TypeSpec";

return returnstring;

}

public int GetMemberRefValue(int memberrefcodedindex)

{

return memberrefcodedindex >> 3;

}

}

 

e.il

.assembly extern vijay

{

}

.class zzz

{

.method void a1() native unmanaged

{

.override zzz::a2

.override [vijay]yyy::a3

}

.method void a2() runtime managed

{

}

}

 

Output

.class /*02000002*/ private auto ansi zzz

       extends [mscorlib/* 23000002 */]System.Object/* 01000001 */

{

  .method /*06000001*/ privatescope instance void

          a1$PST06000001() native unmanaged

  // SIG: 20 00 01

  {

    .override zzz/* 02000002 */::a2 /*02000002::06000002*/

    .override [vijay/* 23000001 */]yyy/* 01000002 */::a3 /*01000002::0A000001*/

    //  Unmanaged TargetRVA = 0x2050

  } // end of method zzz::a1

 

  .method /*06000002*/ privatescope instance void

          a2$PST06000002() runtime managed

  // SIG: 20 00 01

  {

  } // end of method zzz::a2

 

} // end of class zzz

 

This program basically looks at the override keyword. This keyword can occur within a class or a method. The simpler version is found in the method and not in the class. All that the keyword override does is specify a method that the method it is in will override. As we are in method a1 we want this method to override method a2 in the same class.

 

In the second override we want this method a1 to override method a3 in class yyy assembly vijay. In the programming world we have the concept of a virtual function which specifies that there is someone somewhere who will override me. The job of the override keyword is to supply the actual function.

 

The concept of Polymorphisms is implemented using the override keyword. The longer of the override does the same thing but as it is in a type, we have to supply both the overridden function and the function that will override it. 

 

We will first have to uncomment the method DisplayOverrideMethod that is passed the method row number as a parameter. Each time we use a override keyword a row gets added to the MethodImpStruct table. The first field if this table is the codeddef field that is a coded index that can store one of two tables, MethodDef or MethodRef.

 

We use the dual methods GetMethodDefTable and GetMethodDefValue to decipher the coded index. This field simply tells us the name of the method that we want to override. The next field codedbody is a similar coded index but it tells us the method name that will override and in our case it has to a MethodDef as we are writing the override keyword in a actual method.

 

Thus deciphering the codedbody is of really use to us as it will give us our method row number.  We now need to know the type that contains the override keyword as it is normally stored in a type. We forget to pass the type this method belongs to even though we have it in the DisplayAllMethods function.

 

We use the GetTypeForMethod to return the type a method belongs to given the method row number.  All that we do in this method is scan all the types that we have present excepting the last. We then figure out the first and the last method row number that this type owns and hence we subtract by one. We then check if our method falls within this range and  then return the type if successful.

 

Finally if no match is found, we return the last type number. We complicated your life by bringing this function as we always have the method type available. We first check three things before we do anything. First the type of the method is in should match the classindex field that stores the type.

 

Then the codedbodytablename must be MethodDef and finally the methodrow parameter must match the codedbodyindex index field. If these three conditions match we are not yet home free as we could have either a MethodDef or a MethodDef method name after the override keyword.

 

Lets take the first override where we specify the name of a method created by us or where the codeddeftablename variable has a value of MethodDef the second if statement. We first write out the spaces and once again get the type for this method. This is unnecessary but the program works.

 

We then need the name of the type and as it can be nested, we take our good old method GetNestedTypeAsString to figure the out all the nested types if any. Then we figure the namespace by looking at the nspace field of the TypeDef array and joining then if the namespace is not null.

 

Must have written this code at least a zillion times by now. Then we finally add the name of the type minus the nested types. We then write out the override keyword with the type number in comments and finally the method row number in comments. The interesting part is when we are overriding a method defined somewhere else.

 

The second override activates the first if statement where the value of variable codeddeftablename is MethodRef as we are overriding a method in the assembly vijay that has a class yyy and the method name is a3.We as before write out the initial spaces and the override keyword.

 

We now use a method GetTypeRefFromMethodRef that gives us a type index from the TypeRef table as the variable codeddefindex gives us a row number in the MethodRef table and not the MethodDef table as before.

 

Once we get this TypeRef row number, all that we do is simply display the corresponding member of the array typerefnames that store the full form from the TypeRef table. I hope you now appreciate all the trouble we took in populating this typerefnames array. Remember within the square brackets you can also have a module name etc.

 

Once we have the TypeRef name, we also display the name of the method which is present in the MemberRef array and also the row number from the TypeRef and MemberRef tables. Thus all that we need to understand is how the method GetTypeRefFromMethodRef does its job. This method is pretty simple as all that we do is pick up the class field from the MemberRef array.

 

This field is a coded index as the class could reside in different places. We had spend a lot of time of this earlier. The table name is really not necessary and just there for debugging purposes and to tell you what are the possible places a type can be. We right shift by 3 to get the TypeRef index that we simply return.

 

We will come back to the more complex form of override a little later and then deal with all the different table types.

 

Program39.csc

public string DisplayParamsForMethod (int methodrow)

{

if ( ParamStruct == null)

return "";

string returnstring = "";

if (ConstantsStruct == null)

return "";

int start=0, end=0;

start = MethodStruct[methodrow].param;

if ( methodrow == (MethodStruct.Length - 1))

end = ParamStruct.Length  ;

else

end = MethodStruct[methodrow+1].param   ;

for ( int ii = 1 ; ii < ConstantsStruct.Length ; ii++)

{

string consttablename = GetHasConstTable(ConstantsStruct[ii].parent);

int constindex = GetHasConstValue(ConstantsStruct[ii].parent);

if ( consttablename == "ParamDef" )

{

if ( constindex >= start && constindex < end && start != end)

{

int seq = ParamStruct[constindex].sequence;

returnstring = returnstring + CreateSpaces(spacesforrest+ spacesfornested+2) + ".param [" + seq.ToString() + "]";

returnstring = returnstring + "/*08" + constindex.ToString("X6") + "*/  = " ;

int val1 = BitConverter.ToInt32(blob , ConstantsStruct[ii].value+1);

if ( ConstantsStruct[ii].dtype == 2 )

{

bool val = BitConverter.ToBoolean(blob , ConstantsStruct[ii].value+1);

if ( val )

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(true)" + "\r\n";

else

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(false)"+ "\r\n";

}

if ( ConstantsStruct[ii].dtype == 0x03 )

{

int val = blob[ConstantsStruct[ii].value+1];

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(0x" + val.ToString("X4") + ")" + "\r\n";

}

if ( ConstantsStruct[ii].dtype == 0x04  || ConstantsStruct[ii].dtype == 0x05)

{

int val = blob[ConstantsStruct[ii].value+1];

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(0x" + val.ToString("X2") + ")" + "\r\n";

}

if ( ConstantsStruct[ii].dtype == 0x06 || ConstantsStruct[ii].dtype == 0x07  )

{

int val = BitConverter.ToInt16(blob , ConstantsStruct[ii].value+1);

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(0x" + val.ToString("X4") + ")" + "\r\n";

}

if ( ConstantsStruct[ii].dtype == 0x08 || ConstantsStruct[ii].dtype == 0x09 )

{

int val = BitConverter.ToInt32(blob , ConstantsStruct[ii].value+1);

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(0x" + val.ToString("X8") + ")" + "\r\n";

}

if ( ConstantsStruct[ii].dtype == 0x0a || ConstantsStruct[ii].dtype == 0x0b )

{

long val = BitConverter.ToInt64(blob , ConstantsStruct[ii].value+1);

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(0x" + val.ToString("X") + ")" + "\r\n";

}

if ( ConstantsStruct[ii].dtype == 0xc ) //float32

{

double val = BitConverter.ToDouble(blob , ConstantsStruct[ii].value+1);

if ( val == 0.10000000000000001)

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(0.10000000000000001)" + "\r\n";

else

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(" + val.ToString() + "."  + ")" + "\r\n";

}

if ( ConstantsStruct[ii].dtype == 0xd )

{

double val = BitConverter.ToDouble(blob , ConstantsStruct[ii].value+1);

if ( val == 0.10000000000000001)

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(0.10000000000000001)" + "\r\n";

else

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(" + val.ToString() + "."  + ")" + "\r\n";

}

if ( ConstantsStruct[ii].dtype == 0xe )

{

int len = blob[ConstantsStruct[ii].value] ;

if ( len >= 1)

{

returnstring = returnstring + "\"";

for ( int jj = 1 ; jj < len ; jj++)

{

if ( blob[ConstantsStruct[ii].value+jj] != 0)

returnstring = returnstring + (char)blob[ConstantsStruct[ii].value+jj];

}

returnstring = returnstring + "\"\r\n" ;

}

else

returnstring = returnstring +  "\"\"" + "\r\n";

}

if ( ConstantsStruct[ii].dtype == 0x12 )

{

returnstring = returnstring +  "nullref" + "\r\n";

}

}

}

}

return returnstring;

}

public int GetHasConstValue(int constcodedindex)

{

return constcodedindex >> 2;

}

public string GetHasConstTable(int constcodedindex)

{

string returnstring = "";

int tag = constcodedindex & 0x03;

if ( tag == 0)

returnstring = returnstring + "FieldDef";

if ( tag == 1)

returnstring = returnstring + "ParamDef";

if ( tag == 2)

returnstring = returnstring + "Property";

return returnstring;

}

}

 

e.il

.class zzz

{

.method void a1(int32 i ) native unmanaged

{

.param[1] = bool(true)

.param[0] = int32(1)

.param[2] = int32(13)

}

.method void a2() native unmanaged

{

.param[0] = int32(11)

.param[1] = int32(12)

}

.method void a3(int32 , int32 , int32, int32 , int32 , int32, int32, int32, int32) native unmanaged

{

.param[1] = char(1)

.param[2] = int8(2)

.param[3] = int16(4)

.param[4] = int32(5)

.param[5] = int64(6)

//.param[6] = float32(6.2)

//.param[7] = float64(7.2)

.param[8] = "ABC"

.param[9] = nullref

}

}

 

Output

.class /*02000002*/ private auto ansi zzz

       extends [mscorlib/* 23000001 */]System.Object/* 01000001 */

{

  .method /*06000001*/ privatescope instance void

          a1$PST06000001(int32 i) native unmanaged

  // SIG: 20 01 01 08

  {

    .param [1]/*08000001*/  = bool(true)

    //  Unmanaged TargetRVA = 0x2050

  } // end of method zzz::a1

 

  .method /*06000002*/ privatescope instance void

          a2$PST06000002() native unmanaged

  // SIG: 20 00 01

  {

    //  Unmanaged TargetRVA = 0x2051

  } // end of method zzz::a2

 

  .method /*06000003*/ privatescope instance void

          a3$PST06000003(int32 A_0,

                         int32 A_1,

                         int32 A_2,

                         int32 A_3,

                         int32 A_4,

                         int32 A_5,

                         int32 A_6,

                         int32 A_7,

                         int32 A_8) native unmanaged

  // SIG: 20 09 01 08 08 08 08 08 08 08 08 08

  {

    .param [1]/*08000002*/  = char(0x0001)

    .param [2]/*08000003*/  = int8(0x02)

    .param [3]/*08000004*/  = int16(0x0004)

    .param [4]/*08000005*/  = int32(0x00000005)

    .param [5]/*08000006*/  = int64(0x6)

    .param [8]/*08000009*/  = "ABC"

    .param [9]/*0800000A*/  = nullref

    //  Unmanaged TargetRVA = 0x2052

  } // end of method zzz::a3

 

} // end of class zzz

 

In this example lets take a close look at the param directive. This directive takes a number in square brackets that represents the parameter position. For the first time we count from 1 and not zero. In the method a1 the param directive that has a value 0 and 2 does not show up as we have only one parameter.

 

Thus if we Write any erroneous param directives, ilasm discards them. Ditto for method a2, both get discarded. Wonder why such error checks are not done always, would make our life much easier. The param directive is used to give a param a value and hence the equal to sign and a data  type along with a value compatible with this data type.

 

All the params constant values are stored in the Constants table and our job know is to display all the param directives, they can be more per function as method a3 demonstrates. A value of zero stands for or represents the return value.

 

The CLI makes sure that every parameter gets  a value but in languages like C++ if a parameter does not have a value, it can get a default one. This is where we have the param directive. Thus if the parameter does not have a value, the tool will use the param directive for a value as the user did not supply one.

 

Thus the CLI has a hands off approach to the param directive and the compilers can do whatever they like with the param directive. We now uncomment the method DisplayParamsForMethod and pass it the method row number. This method is slightly different from the others as it returns a string with a enter at the end.

 

Thus we use the Write and not the WriteLine method to display it. The MethodStruct array tells us the first and last parameter in the Params table that this method owns. As always we store these values in the start and end variables respectively. Now the Constants table stores constants that appear anywhere in our code.

 

Thus the parent field of the Constants table tells us using a coded index which table owns this constant. We have up to three possible owners, a field, property or a param. As we are interested in only the param directive the first if statement checks for the parent name to be ParamDef.

 

The variable constindex will now stand for a row number in the Param table and if it lies between start and end, it is a param directive for this method. If start and end are the same, then the method owns no parameters.

 

Fortunately for us the sequence number is also the parameter number and hence we write out the param directive as well as the sequence number in brackets and the Param table row number in comments. Then we place the equal to sign to be followed by the data type. The field dtype gives us the type of the data type.

 

The Constants table also has a field value that is an index into the blob heap which always begins by a length field. We have always assumed that no one in his right mind will create a constant larger than 127 bytes. Thus if the type is 2, then the data type follows that of method GetType i.e. a bool.

 

We pick up the Boolean value stored one more from the blob array due to the length and display the words true or false. This is shown in method a1. Then moving to method a3, we start with the char data type whose dtype is 3. A value of dtype 3 is that of a byte and hence we pick up the value directly.

 

We also have to chose the format type x2 or x4 accordingly. The value 4 and 5 are that of int8 which we also read one byte of the blob array. 6 and 7 are for int16 and 8 and 9 are for in32. The unsigned data type gives an error ad hence the clubbing. Finally 0xa and 0xb are for int64.

 

In each case we use the relevant BitConverter method to read off the blob array.  The problem with numbers with decimal places is how do we display them. The original ildasm has been written in C and hence we will work with decimal numbers later. All that we say is that dtype values 0xc and 0xd are for such numbers.

 

A value of 0xe stands for a string and hence we look at the first byte the length byte. If it is larger than 1 we have a string that is not null or empty and we now display the string in ascii ignoring the bytes that are zeroes. We also add a double inverted comma at the beginning and at the end and use no data type, just the string.

 

If the string is a null string, we simply display two double inverted commas with nothing in between. Finally a value of 0x12 means the words nullref. There are some special cases like 0, 1 , 0xf , 0x11 whose values are illegal and hence we have not accounted for them.

 

Also if the string does not contain displayable characters, we use the bytearray data type instead. This would look like

.param[1] = bytearray (0C 00 00 00 00 00 00 00 )

This is how the param directive is implemented and we have not seen the last of this directive.

 

Prgram40.csc

Public void DisplayAllMethods()

{

 

Console.WriteLine(methodstring);

continue;

}

string vtentrystring = GetVtentryString(methodindex);

methodstring = vtentrystring;

methodstring = methodstring + CreateSpaces(spacesforrest + 2 + spacesfornested) +  "// Method begins at RVA 0x" + MethodStruct[methodindex].rva.ToString("x4") + "\r\n";

methodstring = methodstring + CreateSpaces(spacesforrest + 2 + spacesfornested) +  "// Code size       0 (0x0)" + "\r\n";

methodstring = methodstring + CreateSpaces(spacesforrest+ spacesfornested) + "} // end of method " + NameReserved(GetString(TypeDefStruct[typeindex].name)) + "::" + NameReserved(GetString(MethodStruct[methodindex].name)) + "\r\n";

Console.WriteLine(methodstring);

}

}

 

int [] methodvtentryarray ;

public void ReadandDisplayVTableFixup()

{

//Console.WriteLine("........{0}" , MethodStruct.Length  );

if ( MethodStruct == null)

return;

methodvtentryarray  = new int[MethodStruct.Length  + 1];

for ( int jj = 1 ; jj <= MethodStruct.Length ; jj++)

methodvtentryarray[jj] = 0;

if ( vtablerva != 0)

{

long save ;

long position = ConvertRVA(vtablerva) ;

if ( position == -1)

return;

mfilestream.Position = position;

Console.WriteLine("// VTableFixup Directory:");

int count1 = vtablesize/8;

vtfixuparray = new string[count1];

for ( int ii = 0 ; ii < count1 ; ii++)

{

vtfixuparray[ii] = ".vtfixup ";

int fixuprva = mbinaryreader.ReadInt32();

Console.WriteLine("//   IMAGE_COR_VTABLEFIXUP[{0}]:" , ii);

Console.WriteLine("//       RVA:               {0}",fixuprva.ToString("x8"));

short count = mbinaryreader.ReadInt16();

Console.WriteLine("//       Count:             {0}", count.ToString("x4"));

short type = mbinaryreader.ReadInt16();

Console.WriteLine("//       Type:              {0}", type.ToString("x4"));

save = mfilestream.Position;

mfilestream.Position = ConvertRVA(fixuprva) ;

int i1 ;

long [] val = new long[count] ;

for ( i1 = 0 ; i1 < count ; i1++)

{

if ( (type&0x01)  == 0x01)

val[i1] = mbinaryreader.ReadInt32();

if ( (type&0x02)  == 0x02)

val[i1] = mbinaryreader.ReadInt64();

if ( (type&0x01)  == 0x01 )

Console.WriteLine("//         [{0}]            ({1})",i1.ToString("x4") , val[i1].ToString("X8"));

if ( (type&0x02)  == 0x02)

Console.WriteLine("//         [{0}]            (         {1})",i1.ToString("x4") , (val[i1]&0xffffffff).ToString("X"));

}

mfilestream.Position = save;

vtfixuparray[ii] = vtfixuparray[ii] + "[" + (i1).ToString("X") + "] ";

if ( (type&0x01)  == 0x01)

vtfixuparray[ii] = vtfixuparray[ii] + "int32 ";

if ( (type&0x02)  == 0x02)

vtfixuparray[ii] = vtfixuparray[ii] + "int64 ";

if ( (type&0x04)  == 0x04)

vtfixuparray[ii] = vtfixuparray[ii] + "fromunmanaged ";

vtfixuparray[ii] = vtfixuparray[ii] + "at D_" + fixuprva.ToString("X8");

vtfixuparray[ii] = vtfixuparray[ii] + " //";

for ( i1 = 0 ; i1 < count ; i1++)

{

if ( (type&0x01)  == 0x01)

vtfixuparray[ii] = vtfixuparray[ii]  + " " + val[i1].ToString("X8");

if ( (type&0x02)  == 0x02)

vtfixuparray[ii] = vtfixuparray[ii] + " " + val[i1].ToString("X16");

}

long index = 0;

if ( val != null)

index = val[0] & 0xffffff;

methodvtentryarray[ii+1] = (int)index; 

}

Console.WriteLine();

}

}

public string GetVtentryString (int methodrow)

{

string returnstring ="";

int kk = GetVtentryInteger(methodrow);

if ( kk >= 1)

returnstring = CreateSpaces(spacesforrest+2) + ".vtentry " + kk.ToString() + " : 1" + "\r\n" ;

return returnstring;

}

public int GetVtentryInteger (int methodrow)

{

int ii ;

for ( ii = 1 ; ii < methodvtentryarray.Length-1  ; ii++)

{

if ( methodvtentryarray[ii] == methodrow)

break;

}

if ( methodvtentryarray[ii] == 0)

return 0;

else

return ii;

}

}

 

 

e.il

.vtfixup [1] int32 fromunmanaged at D_0000000

.vtfixup [1] int32 fromunmanaged at D_0000040

.vtfixup [1] int32 fromunmanaged at D_0000010

.vtfixup [1] int32 fromunmanaged at D_0000020

.data D_0000010 = bytearray ( 03 00 00 06)

.data D_0000020 = bytearray ( 01 00 00 06)

.data D_0000000 = bytearray ( 02 00 00 06)

.data D_0000040 = bytearray ( 04 00 00 06)

.class zzz

{

.method void a1()

{

}

.method void a2()

{

}

.method void a3()

{

}

.method void a4()

{

}

.method void a5()

{

}

}

 

Output

  .method /*06000001*/ privatescope instance void

          a1$PST06000001() cil managed

  // SIG: 20 00 01

  {

    .vtentry 4 : 1

  } // end of method zzz::a1

 

  .method /*06000002*/ privatescope instance void

          a2$PST06000002() cil managed

  // SIG: 20 00 01

  {

    .vtentry 1 : 1

  } // end of method zzz::a2

 

  .method /*06000003*/ privatescope instance void

          a3$PST06000003() cil managed

  // SIG: 20 00 01

  {

    .vtentry 3 : 1

  } // end of method zzz::a3

 

  .method /*06000004*/ privatescope instance void

          a4$PST06000004() cil managed

  // SIG: 20 00 01

  {

    .vtentry 2 : 1

  } // end of method zzz::a4

 

  .method /*06000005*/ privatescope instance void

          a5$PST06000005() cil managed

  // SIG: 20 00 01

  {

  } // end of method zzz::a5

 

For one last time we come back to the function ReadandDisplayVTableFixup. We have displayed the entire function again as we have added some more lines to it. Whenever we have the directive vtfixup, we also have in a method the directive vtentry.

 

This program does two things, it adds the vtentry directive and also ends a cil managed method. As of now we will have no code in our methods and a little later we will only focus on il code. Lets gets everything else out of the way.

 

In our il code we have four of each, four vtfixup directives, four data directives and four methods. In the output we have 4 vtentry directives. In the il file as the first data directive is at D_0000000, it refers to method 2 and the second data directive at D_0000040 to method 4. There is no semblance of order either in the data directives or the vtfixup’s.

 

Thus in the output, method a1 gets a vtentry directive with 4:1 , the second method a2 vtentry 1:1 and the third 3:1. The first change we have made is add a instance array of ints called methodvtentryarray and like all arrays in our program is one larger. Then we set every member to zero in this array.

 

Then at the very end we simply add the method row number to the array. If you see the e.il file again, the lot of vtfixup’s are all not ordered, the data directives are also not ordered. The system figures them as the order of the data directives are not important as is the row number stored in them. The array thus contains row numbers 2,4,3 and 1.

 

This is as the first vtfixup directive has the data directive as D_00 which has row number 2. the second vtfixup is D_40 which is row 4 and the third is D_10 that is 3. Thus the vtfixup’s decide the method row numbers.  This simply means that the method 2 will have a vtentry of 1 as this is the first member of the array, the method 4 a vtentry of 2 as it is the second and the method 3 will have a slot value of 3 and finally method 1 slot 4.

 

We now call the GetVtentryString method that returns the vtentry directive as a string. Here we call another method GetVtentryInteger that simply returns a number and all that we do is add the directive vtentry with this number and a colon with a 1. This we do only of we are returned a non zero. For method a5 we will be returned a zero as in the methodvtentryarray array there is no row number 5 as one of its values.

 

Moving to the GetVtentryInteger method, all that we do here is scan the methodvtentryarray till the Length of the array minus 1 and if we ever meet a match where the methodrow variable has the value of any of our methodvtentryarray  array members we simply break. Now we ask why did the break happen.

 

If it happened because the loop got over or because we met a match. We have two ways of quitting the loop.  If the value of the methodvtentryarray  array was zero then we return zero as no match was found. We could have also checked whether the value of ii is equal to the length of the array minus 1.

 

Else we return the ii or the index member of the methodvtentryarray array that has the matching row number. This is the slot number we use in the vtentry directive.

 

Pogram41.csc

public void DisplayOneType (int typedefindex)

{

DisplayOneTypeDefStart(typedefindex);

DisplaySizeAndPack(typedefindex);

DisplayNestedTypes(typedefindex);

DisplayOverride(typedefindex);

DisplayAllMethods(typedefindex);

DisplayOneTypeDefEnd(typedefindex );

}

public void DisplayOverride (int typedefindex)

{

if ( MethodImpStruct == null  )

return;

for ( int ii = 1 ; ii < MethodImpStruct.Length ; ii++)

{

if ( typedefindex == MethodImpStruct[ii].classindex )

{

string codedeftablename = GetMethodDefTable(MethodImpStruct[ii].codeddef );

int codedefindex = GetMethodDefValue(MethodImpStruct[ii].codeddef );

string codebodytablename = GetMethodDefTable(MethodImpStruct[ii].codedbody);

int codebodyindex = GetMethodDefValue(MethodImpStruct[ii].codedbody );

//Console.WriteLine("...codedeftablename={0} codebodytablename={1} codedefindex={2} codebodyindex={3} ",codedeftablename ,codebodytablename,codedefindex,codebodyindex );

if ( codebodytablename== "MethodDef")

continue;

string finals = "";

if ( codedeftablename == "MethodDef" )

{

finals = CreateSpaces(spacesforrest + spacesfornested);

finals = finals + ".override " ;

int typedefmethodindex = GetTypeForMethod(codedefindex);

finals = finals + NameReserved(GetString(TypeDefStruct[typedefmethodindex ].name)) ;

finals = finals + "/* 02" + typedefmethodindex.ToString("X6") + " */::";

finals = finals + NameReserved(GetString(MethodStruct[codedefindex].name)) + " with";

}

else

{

finals = CreateSpaces(spacesforrest + spacesfornested);

finals = finals + ".override " ;

int typerefindex = GetTypeRefFromMethodRef(codedefindex);

finals = finals + typerefnames[typerefindex] + "::";

finals = finals + NameReserved(GetString(MemberRefStruct[codedefindex].name)) + " with";

}

int typerefindex1 = GetTypeRefFromMethodRef(codebodyindex);

finals = finals + " " + methodreftypearray[codebodyindex];

finals = finals + methodrefreturnarray[codebodyindex] + " " ;

finals = finals + DisplayTypeRefExtends(typerefindex1) + "::";

finals = finals + NameReserved(GetString(MemberRefStruct[codebodyindex].name)) + "(" ;

int len = finals.Length;

string paramstring = ParamOnMultipleLines(methodrefparamarray1[codebodyindex], len) ;

finals = finals + paramstring + ") /* 0A" + codebodyindex.ToString("X6") + " */";

Console.WriteLine(finals);

}

}

}

string [] methodrefparamarray1 ;

string [] methodrefreturnarray ;

string [] methodreftypearray;

 

public void CreateSignatures ()

{

if (MethodStruct != null)

{

methoddefparamarray = new string[MethodStruct.Length];

methoddefparamarray1 = new string[MethodStruct.Length];

methoddefreturnarray = new string[MethodStruct.Length];

methoddeftypearray  = new string[MethodStruct.Length];

methoddefparamcount = new int[MethodStruct.Length];

for ( int l = 1 ; l < MethodStruct.Length ; l++)

{

CreateSignatureForEachType(1 , MethodStruct[l].signature, l);

}

}

//MethodRefTable

if ( MemberRefStruct != null )

{

methodreftypearray = new string[MemberRefStruct.Length];

methodrefreturnarray = new string[MemberRefStruct.Length];

methodrefparamarray1  = new string[MemberRefStruct.Length];

for ( int l = 1 ; l < MemberRefStruct.Length ; l++)

{

CreateSignatureForEachType(2 , MemberRefStruct[l].sig, l);

}

}

}

public void CreateSignatureForEachType (byte type , int index , int row)

{

//Console.WriteLine(".......type={0} row={1} index={2} blob.Length={3} {4}" , type , row.ToString("X") , (ushort)index , blob.Length , (uint)index);

int uncompressedbyte , count , howmanybytes;

howmanybytes = CorSigUncompressData(blob , index , out uncompressedbyte);

count = uncompressedbyte;

byte [] blob1 = new byte[count];

Array.Copy(blob , index + howmanybytes , blob1 , 0 , count);

if ( type == 1)

CreateMethodDefSignature(blob1 , row);

if ( type == 2)

CreateMethodRefSignature(blob1 , row);

}

public void CreateMethodRefSignature (byte [] blobarray , int row)

{

//Console.WriteLine("CreateMethodRefSignature Name={0} row={1} b.Length={2}" , GetString(MemberRefStruct[row].name), row, blobarray.Length);

int aa = -1;

if ( row == aa)

{

Console.WriteLine("row number {0} {1}" , row , GetString(MemberRefStruct[row].name));

for ( int l = 0 ; l < blobarray.Length ; l++)

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

Console.WriteLine();

}

int howmanybytes,uncompressedbyte , index;

index = 0;

howmanybytes= CorSigUncompressData(blobarray , index , out uncompressedbyte);

if ( uncompressedbyte == 0x06 )

{

index = index + howmanybytes;

methodrefreturnarray[row] = GetElementType( index , blobarray , out howmanybytes , 0 , "");

return;

}

methodreftypearray[row] = DecodeFirstByteofMethodSignature(uncompressedbyte , row);;

index = index + howmanybytes;

howmanybytes = CorSigUncompressData(blobarray , index , out uncompressedbyte);

count = uncompressedbyte;

index = index + howmanybytes;

string typestring = GetElementType( index , blobarray , out howmanybytes, 0 , "");

methodrefreturnarray[row] = typestring;

index = index + howmanybytes;

string returnstring = "" ;

methodrefparamarray1[row] = "";

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

{

string sentinel = "";

if ( blobarray[index] == 0x41)

{

sentinel = "..." + "," ;

index = index + 1;

}

typestring  = sentinel + GetElementType( index , blobarray , out howmanybytes , 0 , "") ;

index = index + howmanybytes;

returnstring = returnstring + typestring;

if ( l != count)

returnstring = returnstring  + "," ;

}

if ( index < blobarray.Length && blobarray[index] == 0x41)

returnstring = returnstring +  ",..." ;

methodrefparamarray1[row] = returnstring;

}

 

e.il

.assembly extern vijay

{

}

.class interface iii

{

.method public void aa() cil managed

{

}

}

.namespace mmm

{

.class ccc

{

.method public void abc()

{

}

.method public void pqr()

{

}

.override iii::aa with instance void ccc::abc()

.override zzz::pqr with instance void ccc::abc()

.override [vijay]aaa::pqr with instance void ccc::abc()

.override [vijay]aaa::pqr with instance void [vijay]aaa::iii(int32,...,int32)

}

}

 

Output

.namespace mmm

{

  .class /*02000003*/ private auto ansi ccc

         extends [mscorlib/* 23000002 */]System.Object/* 01000001 */

  {

    .override iii/* 02000002 */::aa with instance void ccc/* 01000002 */::abc() /* 0A000001 */

    .override zzz/* 01000003 */::pqr with instance void ccc/* 01000002 */::abc() /* 0A000001 */

    .override [vijay/* 23000001 */]aaa/* 01000004 */::pqr with instance void ccc/* 01000002 */::abc() /* 0A000001 */

    .override [vijay/* 23000001 */]aaa/* 01000004 */::pqr with instance void [vijay/* 23000001 */]aaa/* 01000004 */::iii(int32,

                                                                                                                         ...,

                                                                                                                         int32) /* 0A000004 */

 

Earlier we worked with the override directive that we placed in a method and now we place them in a type. The first thing we need to do is add a line in the DisplayOneType method called DisplayOverride that will display all the overrides for one type.

 

The placement is important as all the override directives for a type are placed before displaying all the methods. The same basic rules that we mentioned earlier apply here also, which is that a record gets added to the MethodImpl table.

 

We as before check whether the classindex field carries the type that we are currently displaying. We need to understand once again that when we use the override directive, we first specify the method we want to override and then use the with clause to specify the method we want to override it with. As we are referring to a method in the with clause, this will always be a reference to the MethodRef table.

 

In the earlier case there was no with clause that we sued with the override. Thus the codedbody field or the variable codebodytablename will always represent the MemberRef table as earlier it was always Method table.

 

In this case therefore the variable codedefindex taken from the coded index codebodyindex will always be a row number in the MemberRef table. Thus we first check if the codebodytablename table has the value MethodDef and if so we loop back as the DisplayOverrideMethod method will handle it as it is a override directive without the with clause and hence part of a method and not type.

 

Now the first method name that is represented by the codeddef field may be either a MethodDef or a MemberRef. Lets move to the il file first to understand which table value this field has. The first override directive specifies the name of a method aa that is an interface iii.

 

Whenever this case happens we have a table value of MethodDef. In the second case, even though we specify a method pqr in the same class zzz this is a MethodRef and not a MethodDef as we would like to understand. The last two are MemberRef’s as they are represents methods in a Assembly.

 

Thus a MethodDef only comes in when we are dealing with methods in a interface and not methods that we have created ourselves. This is important as if the variable codedeftablename is MethodDef, we need to pick up the type name from the Typedef table and the method name from the Method table.

 

The codedefindex is the row number in the Method table and as we need the type it belongs to we use the GetTypeForMethod method to give us the type.

 

Once we have the type we then simply display the type name without the namespace for some reason and then the type row number in comments and the name of the method from the Method table and finally the clause with. If the else gets called then we know that we have a MemberRef and hence we use the method GetTypeRefFromMethodRef that gives us the row number of this method in the TypeRef table.

 

We have done this method earlier in Program38. We use the typerefnames array to display the Type name and then the codedefindex variable to display the name of the method from the MemberRef table. Now all that is left is the method name and type after the with clause and this is a reference to the MemberRef table.

 

We as before get the TypeRef table index using the codebodyindex variable. We then use the methodreftypearray that gives us the type i.e. instance or static for the MemberRef method. This is the same concept we used for the Method table where we had three arrays store the type, the return value and the parameters.

 

The only difference is the words def are replaced with ref. We will show you in a short while how similar are the MemberRef and Method tables. We then display the return value stored in the methodrefreturnarray array and then the type name from the TypeRef table using a method we used before DisplayTypeRefExtends.

 

We then add a colon and finally add the name of the method from the MemberRef table. Finally the array methodrefparamarray1 gives us the parameters to the method and we use the ParamOnMultipleLines method to display then on different lines. Most of the code used here fortunately has been done before.

 

Lets now look at how we populate the three ref arrays. As always we define three arrays of strings as instance variables. Then we in the CreateSignatures method we initialize these three arrays to the length of the MemberRef table.

 

Then we call the CreateSignatureForEachType function once for each row of the MemberRef table but with the first parameter as 2 and not 1 as for the method table. We also pass the sig offset in the blob heap. In the CreateSignatureForEachType we do nothing different that what we did earlier and call the CreateMethodRefSignature as the value of the type variable is 2 now.

 

In this method CreateMethodRefSignature we first check for the value of the first byte after the length to be 06 or not. If it is then we do not have a full method signature with parameters following but only a data type that we set the methodrefreturnarray to. This is not a reference to a method with parameters but only a return type.

 

The number 6 is actually the type for fields that we will come to later. Like always we have the type byte that we store in the methodreftypearray array and then the count of parameters followed by the return type that we store in the methodrefreturnarray array.

 

Then we loop in a for statement to pick up the parameters and as we are in a MemberRef we have no parameter names. The only addition is the use of three dots or a sentinel …. This as explained before is used for parameters with a variable number of arguments. The type for a sentinel is 0x41 and if we meet one we simply add three dots and a comma.

 

We finally store the parameter list in the array methodrefparamarray1. No major changes in what we have done earlier. The last override uses a sentinel. In case you did not notice both the array typerefnames and DisplayTypeRefExtends return the same result and could have been used in place of each other.

 

There is the lat if statement that we have not explained in the CreateMethodRefSignature. The problem is that when we encounter a sentinel, this is not counted as a valid parameter. The count member does not acknowledge its presence. This is why when we meet a sentinel, we simply increase the index variable by 1.

 

The problem is that if the last parameter is the sentinel, it does not get added to the return string variable at all. Thus we have to check when we exit the for, are we on the sentinel as well as we are not crossing the bounds of the array. A simple MemberRef with the signature bytes will clarify matters for us.

 

20 2 1 8 41 8

.override [vijay]aaa::pqr with instance void [vijay]aaa::iii(int32,...,int32)

 

20 1 1 8 41

.override [vijay]aaa::abc with instance void [vijay]aaa::iii(int32,...)

 

In the case of the first override, the iii method has two parameters as the second byte of the signature tells us. The sentinel byte 0x41 in the middle of the two int32’s does get counted. In the last override, the sentinel byte is at the very end and the count byte is only 1.

 

Program42.csc

public string DisplayFromConstantsTable(int start , int end ,string tablename )

{

string returnstring = "";

for ( int ii = 1 ; ii < ConstantsStruct.Length ; ii++)

{

string consttablename = GetHasConstTable(ConstantsStruct[ii].parent);

int constindex = GetHasConstValue(ConstantsStruct[ii].parent);

if ( consttablename == tablename )

{

if ( (constindex >= start && constindex < end && start != end && tablename == "ParamDef" ) || (tablename == "Property" && start == constindex ) )

{

int seq = ParamStruct[constindex].sequence;

if ( tablename == "ParamDef" )

{

returnstring = returnstring + CreateSpaces(spacesforrest+ spacesfornested+2) + ".param [" + seq.ToString() + "]";

returnstring = returnstring + "/*08" + constindex.ToString("X6") + "*/  = " ;

}

else

returnstring = " = ";

int val1 = BitConverter.ToInt32(blob , ConstantsStruct[ii].value+1);

if ( ConstantsStruct[ii].dtype == 2 )

{

bool val = BitConverter.ToBoolean(blob , ConstantsStruct[ii].value+1);

if ( val )

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(true)" + "\r\n";

else

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(false)"+ "\r\n";

}

}

}

}

return returnstring;

}

public string DisplayParamsForMethod (int methodrow)

{

if ( ParamStruct == null)

return "";

string returnstring = "";

if (ConstantsStruct == null)

return "";

int start=0, end=0;

start = MethodStruct[methodrow].param;

if ( methodrow == (MethodStruct.Length - 1))

end = ParamStruct.Length  ;

else

end = MethodStruct[methodrow+1].param;

returnstring = DisplayFromConstantsTable(start , end ,"ParamDef");

return returnstring;

}

public void DisplayOneType (int typedefindex)

{

DisplayOneTypeDefStart(typedefindex);

DisplaySizeAndPack(typedefindex);

DisplayAllSecurity( 0 , typedefindex);

DisplayNestedTypes(typedefindex);

DisplayOverride(typedefindex);

DisplayAllMethods(typedefindex);

DisplayAllProperties (typedefindex);

DisplayOneTypeDefEnd(typedefindex );

}

 

string [] propertytypearray;

string [] propertyreturnarray ;

string [] propertyparmarray;

 

public void CreateSignatures ()

{

//MethodTable

if (MethodStruct != null)

{

methoddefreturnarray = new string[MethodStruct.Length];

methoddefparamarray  = new string[MethodStruct.Length];

methoddefparamarray1  = new string[MethodStruct.Length];

methoddeftypearray  = new string[MethodStruct.Length];

methoddefparamcount = new int[MethodStruct.Length];

for ( int l = 1 ; l < MethodStruct.Length ; l++)

{

CreateSignatureForEachType(1 , MethodStruct[l].signature, l);

}

}

//MethodRefTable

if ( MemberRefStruct != null )

{

methodreftypearray = new string[MemberRefStruct.Length];

methodrefreturnarray = new string[MemberRefStruct.Length];

methodrefparamarray1  = new string[MemberRefStruct.Length];

for ( int l = 1 ; l < MemberRefStruct.Length ; l++)

{

CreateSignatureForEachType(2 , MemberRefStruct[l].sig, l);

}

}

//propertytable

if ( PropertyStruct != null )

{

propertyparmarray = new string[PropertyStruct.Length];

propertyreturnarray = new string[PropertyStruct.Length];

propertytypearray  = new string[PropertyStruct.Length];

for ( int l = 1 ; l < PropertyStruct.Length ; l++)

{

CreateSignatureForEachType (7, PropertyStruct[l].type, l);

}

}

}

public void CreateSignatureForEachType (byte type , int index , int row)

{

int uncompressedbyte , count , howmanybytes;

howmanybytes = CorSigUncompressData(blob , index , out uncompressedbyte);

count = uncompressedbyte;

byte [] blob1 = new byte[count];

Array.Copy(blob , index + howmanybytes , blob1 , 0 , count);

if ( type == 7)

CreatePropertySignature (blob1 , row);

if ( type == 1)

CreateMethodDefSignature(blob1 , row);

if ( type == 2)

CreateMethodRefSignature(blob1 , row);

}

public void CreatePropertySignature (byte [] blobarray , int row)

{

int aa = -1;

if ( row == aa)

{

for ( int l = 0 ; l < blobarray.Length ; l++)

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

Console.WriteLine();

}

int howmanybytes , uncompressedbyte;

int index = 0;

howmanybytes = CorSigUncompressData(blobarray , index , out uncompressedbyte);

index = index + howmanybytes;

if ( (uncompressedbyte&0x20) == 0x20)

propertytypearray [row] = "instance ";

howmanybytes = CorSigUncompressData(blobarray , index , out uncompressedbyte);

index = index + howmanybytes;

int count = uncompressedbyte;

string typestring = "";

string returnstring = "";

returnstring = GetElementType( index , blobarray , out howmanybytes , 0 , "") ;

propertyreturnarray [row] = returnstring;

index = index + howmanybytes;

returnstring = "";

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

{

typestring = GetElementType( index , blobarray , out howmanybytes , 0 , "") ;

returnstring = returnstring + typestring;

index = index + howmanybytes;

if ( l != count)

returnstring = returnstring + ",";

}

propertyparmarray [row] = returnstring;

}

public void DisplayAllProperties (int typeindex)

{

int ii;

if ( PropertyMapStruct == null || PropertyStruct == null )

return;

for ( ii = 1 ; ii < PropertyMapStruct.Length ; ii++)

{

if ( typeindex == (PropertyMapStruct[ii].parent) )

break;

}

if ( ii == PropertyMapStruct.Length)

return;

int start = PropertyMapStruct[ii].propertylist;

int end;

if ( ii+1 == PropertyMapStruct.Length )

end = PropertyStruct.Length - 1;

else

end =  PropertyMapStruct[ii + 1].propertylist -1;

for ( int propertyrow = start ; propertyrow <= end ; propertyrow++)

{

string returnstring = CreateSpaces(spacesforrest + spacesfornested);

returnstring = returnstring + ".property /*17" + propertyrow.ToString("X6") + "*/ ";

string name = NameReserved(GetString(PropertyStruct[propertyrow].name)) ;

int flags = PropertyStruct[propertyrow].flags;

string propertyattribute = GetPropertyAttribute (flags );

returnstring = returnstring + propertyattribute + propertytypearray[propertyrow] + propertyreturnarray[propertyrow] ;

if ( returnstring.Length >= 41 )

returnstring = returnstring + "\r\n" + CreateSpaces(spacesforrest + spacesfornested)+ CreateSpaces(7);

returnstring = returnstring + " " + name + "(" ;

int len = returnstring.Length;

int len1 = returnstring.LastIndexOf("\r\n");

if ( len1 == -1)

len1 = -2;

string dummy = ParamOnMultipleLines (propertyparmarray[propertyrow], len-len1-2);

returnstring = returnstring + dummy + ")";

returnstring = returnstring + GetPropertyAttributeDefault (flags, propertyrow);

Console.WriteLine(returnstring);

 

Console.WriteLine(CreateSpaces(spacesforrest + spacesfornested) + "{");

for ( int kk = 1 ; kk < MethodSemanticsStruct.Length ; kk++)

{

string attributes = GetMethodSemanticsAttributes (MethodSemanticsStruct[kk].methodsemanticsattributes);

string table = GetHasSemanticsTable (MethodSemanticsStruct[kk].association);

int propertyindex = GetHasSemanticsValue (MethodSemanticsStruct[kk].association);

int methodindex = MethodSemanticsStruct[kk].methodindex;

string methodattribute = GetMethodAttribute(MethodStruct[methodindex].flags , methodindex);

string paramstring = methoddefparamarray1[methodindex];

if ( propertyindex == propertyrow && table == "Property" )

{

string dummy1 = CreateSpaces(spacesforrest + 2 + spacesfornested);

dummy1 = dummy1 + attributes + " /*06" + methodindex.ToString("X6") + "*/ " + methoddeftypearray [methodindex] + methoddefreturnarray[methodindex];

string nestedtypestring  = GetNestedTypeAsString (typeindex);

string name1 = NameReserved(GetString(MethodStruct[methodindex].name)) + "(";

string nspace = NameReserved(GetString(TypeDefStruct[typeindex].nspace));

if ( nspace != "")

nspace = nspace + ".";

string dummy2 =  dummy1 + nestedtypestring + nspace + NameReserved(GetString(TypeDefStruct[typeindex].name)) + "/* 02" + typeindex.ToString("X6") +  " */::" + name1;

int dummyint = dummy2.IndexOf("(");

string dummy3  =  ParamOnMultipleLines(paramstring , dummyint + 1)  ;

dummy3 = dummy3 + ")" + " /* 06" + (methodindex).ToString("X6") + " */";

Console.WriteLine(dummy2 + dummy3);

}

}

Console.Write(CreateSpaces(spacesforrest + spacesfornested));

Console.Write("} ");

Console.WriteLine("// end of property {0}::{1}" , NameReserved(GetString(TypeDefStruct[typeindex].name)) , name);

}

}

public string GetHasSemanticsTable(int asscoiationbyte)

{

string returnstring = "";

int tag = asscoiationbyte & 0x01;

if ( tag == 0 )

returnstring = returnstring + "Event";

if ( tag == 1 )

returnstring = returnstring + "Property";

return returnstring;

}

public int GetHasSemanticsValue(int asscoiationbyte)

{

return asscoiationbyte >> 1;

}

public string GetMethodSemanticsAttributes(short asscoiationbyte)

{

string returnstring = "";

if ( (asscoiationbyte & 0x01) == 0x01)

returnstring = returnstring + ".set";

if ( (asscoiationbyte & 0x02) == 0x02)

returnstring = returnstring + ".get";

if ( (asscoiationbyte & 0x04) == 0x04)

returnstring = returnstring + ".other";

if ( (asscoiationbyte & 0x08) == 0x08)

returnstring = returnstring + "Event Addon";

if ( (asscoiationbyte & 0x10) == 0x10)

returnstring = returnstring + "Event Remove";

if ( (asscoiationbyte & 0x20) == 0x20)

returnstring = returnstring + "Event Fire";

return returnstring;

}

public string GetPropertyAttribute(int flags )

{

string returnstring = "";

if ( (flags&0x200) == 0x200 )

returnstring = returnstring + "specialname ";

if ( (flags&0x400) == 0x400 )

returnstring = returnstring + "rtspecialname ";

return returnstring ;

}

public string GetPropertyAttributeDefault(int flags, int row)

{

string returnstring = "";

if ( (flags&0x1000) == 0x1000)

{

returnstring = DisplayFromConstantsTable ( row , 0 ,"Property");

returnstring = returnstring.Remove(returnstring.Length-2,2);

}

return returnstring;

}

}

 

e.il

.class zzz

{

.method  public static int32  a1()

{

}

.method  public instance void  a2(int32 'value')

{

}

.method  public instance void  a3()

{

}

.method  public static int32  a4()

{

}

.property rtspecialname specialname instance class zzz aa(int32 , int64 , int8) = int32(10)

{

.get int32 zzz::a1()

.set instance void  zzz::a2(int32)

.other instance void  zzz::a3()

}

.property int32  bb()

{

.get int32 zzz::a4()

}

}

 

 

Output

  .property /*17000001*/ specialname instance class zzz/* 02000002 */

          aa(int32,

             int64,

             int8) = int32(0x0000000A)

  {

    .get /*06000001*/ int32 zzz/* 02000002 */::a1() /* 06000001 */

    .set /*06000002*/ instance void zzz/* 02000002 */::a2(int32) /* 06000002 */

    .other /*06000003*/ instance void zzz/* 02000002 */::a3() /* 06000003 */

  } // end of property zzz::aa

  .property /*17000002*/ int32 bb()

  {

    .get /*06000004*/ int32 zzz/* 02000002 */::a4() /* 06000004 */

  } // end of property zzz::bb

 

 

This example will display all the properties that we have in our type. There is really no concept of a property in il. In C#, a property looks like, walks like, talks like a variable. If we have no access to source code, there is no way of knowing whether a entity is a property or a variable.

 

The concept of properties is touted as one of the major features of the C# programming language. When C# code gets converted to il, the property aa gets converted to two methods get_aa and set_aa. Whenever we set the value of a property , the set_aa method replaces the setting of the property in our IL code.

 

In the same vein when we access a property value, this gets replaced by a call to the get_aa method that returns the value of the property. The set_aa method gets called with a parameter value that contains the  new value of our property. Thus properties in il are nothing but two method calls.

 

However in IL we have a property directive which we will now discuss. But before that a small change. Some programs above we displayed the param keyword that had a value taken from the Constants table. Thus in the function DisplayParamsForMethod we first figured out the param numbers that our method owned and then displayed the value from the constants table.

 

At that time we forget that the constants table is also used by two other directives, one the properties directive and the other the fields directive. Thus we did not want to repeat the same code to retrieve the constants value from the constants table and thus broke up the DisplayParamsForMethod into two methods.

 

In this method we first figure out the start and end param numbers and then call the DisplayFromConstantsTable method with the last string parameter being ParamDef and the first and second parameter being the start and end param numbers owned by this method.

 

Then in the DisplayFromConstantsTable we simply display the value from the constants table. It is this method that we will call once again a little later. We then move our focus to the method DisplayOneType where we have added a method DisplayAllProperties that will display for us all the properties associated with a type.

 

But before we come to this method we need to focus on the CreateSignatures method where we populate the string arrays associated with properties. As always we initialize the three string arrays depending upon the length of the Property table. This table has one row for every property directive present in the il code.

 

The variable propertyparmarray will contain the parameters that a property can carry, the propertyreturnarray the return value, methodreftypearray the type of method instance or a null if static. We then call the CreateSignatureForEachType with a value of 7 that stands for a property.

 

In this method we simply call the CreatePropertySignature method that populates the above three string arrays. In this case we call the field that holds the signature type and not sig just for a change of scene. The property signature is much simpler to understand than the two that we did earlier.

 

The first byte will contain the value 0x8 to signify a property. This byte will have 0x20 set if it is a instance method and static is not permitted. Thus a value of 0x28 signifies the presence of instance .We first check the value of the first byte for the presence of instance or 0x20 and if yes we set the corresponding row of the propertytypearray to instance.

 

The next byte is the count of parameters. This is followed by the return type that we store in the propertyreturnarray array. Then a for loop we read all the property parameters in the propertyparmarray array. It is in the DisplayAllProperties method that we actually display the properties directive.

 

But first lets look at all the tables that will be populated by our property keyword. As we have two properties aa and bb the Property table will have two records only. This table has three fields, type that we saw earlier, then the name field and finally the flags field that store one of three flags that we will soon decipher.

 

The property is associated with a type and a type can have more than one property. Thus properties behave like methods and logically should have been stored in the TypeDef table like methods are. Unfortunately properties are not as common as methods and thus a new table PropertyMap got created which stores which properties are associated with which type.

 

This table has two fields, the TypeDef that owns the property and the starting property number. Thus we have to read this table to figure out the first and last property that is owned by the type. This is why in the DisplayAllProperties method we get the variables start and end by reading the PropertyMap table.

 

Now that we have the starting and end property row numbers we in a for loop use the propertyrow variable to iterate though all of them. If we start by placing the spaces, the words property , the property row number in comments and then the name of the property.

 

Now we use the method GetPropertyAttribute to figure out which of the flags the property has. A value of 0x200 means that we have used the flag specialname and the value 0x400 rtspecialname. The value 0x1000 is for having a default value that we display after the properties parameters are displayed.

 

This default will be handled by a different method. Thus we thought that the GetPropertyAttribute will be easy to understand. There is one problem. The rtspecialname attribute if placed by us, is ignored by ilasm and thus for property aa, the flags value is not 0x600

which means both attributes but only 0x200 that means specialname.

 

However if we go into the dll and change the value to 0x600, then ildasm displays both specialname and rtspecialname. By now you understand the depth we have gone to make our book as complete as possible but we can guarantee you major goof ups. We then add this attribute and type of the property along with the return value and then stop.

 

This is because if the length of all this is beyond 41 characters, then we need to put an enter to place everything else like the name and parameters on a new line. Now we add the name of the property and the open brackets and then need the parameters which if more than one have to be placed on a new line.

 

We use the ParamOnMultipleLines to do this for us but it requires the number of spaces to be placed before each parameter. We first calculate the length of the property string up to the open bracket and then also find out the last enter placed.

 

The number of spaces is like before, the length so far minus the last enter minus 2 for the enter. However in most cases unlike methods there will be no enter placed and here we simply subtract 2 for the alignment. We then write call the method GetPropertyAttributeDefault to write out the constant value that the property has been initialized to.

 

We pass it two parameters, the flags and the property row number. We first check if the default flag of 0x1000 is turned on or not. If it is we next call the DisplayFromConstantsTable with the first parameter as the property row number or start, the second parameter as zero as the end has no meaning here and the third being Property as this constant row is owned by the property table.

 

Coming to the DisplayFromConstantsTable method we now have an or in the if statement. The first gets activated for the ParamDef value, the second for if the constindex variable has a row number equal to the property row number passed and if the tablename parameter is Property. Also we have one more if statement that check again for the table name.

 

If it is ParamDef then we need the words param and id it is Property we only need the equal to sign. When we leave this function, we have added an enter and thus we need to remove this enter using the Remove method.

 

Normally the default value for a property is rarely seen or used. Normally a property can have as many as three directives within it, a .get, a .set or a .other. These attributes are stored in the table MethodSemantics. This table is also used by events which the next program will demonstrate.

 

Thus as we have a total of four directives within our two properties, this table will have four records. The field attributes tells us which of the directives we have used. The method GetMethodSemanticsAttributes tells us that we can have as many as six attributes, three for properties as mentioned earlier and three for events.

 

The field association tells is whether this row is owned by a event or a property and the GetHasSemanticsTable returns one of these value and the GetHasSemanticsValue method in our case the property row number.

 

We need the property row number as the MethodSemantics table most important role is to give us the method that will handle the get, set or the other. Thus we do not have to search the method table for the matching method name as there is no rule that says that a set directive for a property aa must have the method name as set_aa.

 

Thus the methodindex variable gives us the method name that we will display after the directive. Thus we scan the entire MethodSemantics table and check if the table variable is Property and the propertyindex is our property number.

 

We first write out the spaces and then the directive stored in the attributes variable and then the method row number in comments. Then we use the methoddeftypearray to write out the words instance for the method, and then the return value.

 

We next use the GetNestedTypeAsString to get the name of the type and then the name and namespace. We then join these together and write out the TypeDef table row number. We finally write out the params of this method and then the end of the braces and the words end of property. A property cannot refer to an method defined in another assembly.

 

There are also a million rules that specify what the parameters to the get, set and the property should have. We have broken all the rules to stress test our program and ilasm does not seem to implement these rules. Over to events.

 

Program43.csc

 

public void DisplayOneType (int typedefindex)

{

DisplayOneTypeDefStart(typedefindex);

DisplaySizeAndPack(typedefindex);

DisplayAllSecurity( 0 , typedefindex);

DisplayNestedTypes(typedefindex);

DisplayOverride(typedefindex);

DisplayAllMethods(typedefindex);

DisplayAllEvents (typedefindex);

DisplayAllProperties(typedefindex);

DisplayOneTypeDefEnd(typedefindex );

}

public void DisplayAllEvents (int typeindex)

{

int ii;

if ( EventMapStruct == null  )

return;

for ( ii = 1 ; ii < EventMapStruct.Length ; ii++)

{

if ( typeindex == (EventMapStruct[ii].index ) )

break;

}

if ( ii == EventMapStruct.Length)

return;

int start = EventMapStruct[ii].eindex;

int end;

if ( ii == EventMapStruct.Length -1 )

end = EventStruct.Length - 1;

else

end =  EventMapStruct[ii + 1].eindex -1;

for ( int eventrow = start ; eventrow <= end ; eventrow++)

{

Console.Write(CreateSpaces(spacesforrest + spacesfornested));

Console.Write(".event /*14{0}*/ {1}" , (eventrow).ToString("X6") , GetPropertyAttribute( EventStruct[eventrow].attr));

string name = NameReserved(GetString(EventStruct[eventrow].name)) ;

string codedtablename = GetTypeDefOrRefTable(EventStruct[eventrow].coded) ;

int codedindex = GetTypeDefOrRefValue(EventStruct[eventrow].coded);

string returnstring = "";

if ( codedtablename == "TypeRef")

returnstring = typerefnames[codedindex] + " /*01" + codedindex.ToString("X6") + "*/";

if ( codedtablename == "TypeDef")

returnstring = typedefnames[codedindex] + " /*02" + codedindex.ToString("X6") + "*/";

Console.Write(returnstring);

Console.Write(" " + name) ;

Console.WriteLine();

Console.Write(CreateSpaces(spacesforrest+ spacesfornested));

Console.WriteLine("{");

for ( int kk = 1 ; kk < MethodSemanticsStruct.Length ; kk++)

{

string attributes = GetMethodSemanticsAttributes(MethodSemanticsStruct[kk].methodsemanticsattributes);

string table = GetHasSemanticsTable(MethodSemanticsStruct[kk].association);

int eventindex = GetHasSemanticsValue(MethodSemanticsStruct[kk].association);

int methodindex = MethodSemanticsStruct[kk].methodindex;

if ( eventindex == eventrow && table == "Event" )

{

Console.Write(CreateSpaces(spacesforrest+2+ spacesfornested));

Console.Write("{0} {1}{2}",  attributes , methoddeftypearray[methodindex],  methoddefreturnarray[methodindex] );

string name1 = NameReserved(GetString(MethodStruct[methodindex].name));

string nspace = NameReserved(GetString(TypeDefStruct[typeindex].nspace));

if ( nspace != "")

nspace = nspace + ".";

nspace = nspace + NameReserved(GetString(TypeDefStruct[typeindex].name));

string dummy = "";

dummy = GetNestedTypeAsString(typeindex);

nspace = dummy + nspace;

Console.Write("{0}/* 02{1} */::{2}({3})" , nspace , typeindex.ToString("X6") , name1 , methoddefparamarray1[methodindex]);

Console.WriteLine(" /* 06{0} */" , (methodindex).ToString("X6"));

}

}

Console.Write(CreateSpaces(spacesforrest+ spacesfornested));

Console.Write("} ");

Console.WriteLine("// end of event {0}::{1}" , NameReserved(GetString(TypeDefStruct[typeindex].name)) , name);

}

}

public string GetMethodSemanticsAttributes(short asscoiationbyte)

{

string returnstring = "";

if ( (asscoiationbyte & 0x01) == 0x01)

returnstring = returnstring + ".set";

if ( (asscoiationbyte & 0x02) == 0x02)

returnstring = returnstring + ".get";

if ( (asscoiationbyte & 0x04) == 0x04)

returnstring = returnstring + ".other";

if ( (asscoiationbyte & 0x08) == 0x08)

returnstring = returnstring + ".addon";

if ( (asscoiationbyte & 0x10) == 0x10)

returnstring = returnstring + ".removeon";

if ( (asscoiationbyte & 0x20) == 0x20)

returnstring = returnstring + ".fire";

return returnstring;

}

}

 

e.il

.assembly extern vijay

{

}

.class zzz

{

.method  public void a1(class [vijay]System.EventHandler)

{

}

.method  public instance void  a2(class [vijay]System.EventHandler)

{

}

.method  public instance void  a3()

{

}

.method  public static int32  a4()

{

}

.method  public instance int32 a5()

{

}

.method  public instance void  a6()

{

}

.method  public instance void  a7()

{

}

.event specialname rtspecialname [vijay]System.EventHandler a

{

.addon instance void zzz::a1(class [vijay]System.EventHandler)

.addon instance void zzz::a2(class [vijay]System.EventHandler)

.removeon instance void zzz::a2(class [vijay]System.EventHandler)

.removeon instance void zzz::a1(class [vijay]System.EventHandler)

.other instance void zzz::a3()

.other int32 zzz::a4()

.fire instance int32 zzz::a5()

.fire instance void zzz::a6()

}

.event specialname rtspecialname zzz b

{

.addon instance void zzz::a7()

}

}

 

  .event /*14000001*/ specialname [vijay/* 23000001 */]System.EventHandler/* 01000002 */ /*01000002*/ a

  {

    .addon instance void zzz/* 02000002 */::a2(class [vijay/* 23000001 */]System.EventHandler/* 01000002 */) /* 06000002 */

    .removeon instance void zzz/* 02000002 */::a1(class [vijay/* 23000001 */]System.EventHandler/* 01000002 */) /* 06000001 */

    .fire instance void zzz/* 02000002 */::a6() /* 06000006 */

    .other instance void zzz/* 02000002 */::a3() /* 06000003 */

    .other int32 zzz/* 02000002 */::a4() /* 06000004 */

  } // end of event zzz::a

  .event /*14000002*/ specialname zzz/* 02000002 */ /*02000002*/ b

  {

    .addon instance void zzz/* 02000002 */::a7() /* 06000007 */

  } // end of event zzz::b

 

In this program we deal with events which are very similar to properties. We add just one function call DisplayAllEvents to the DisplayOneType method that displays all the events for us. The point to be noted is that first the overrides are displayed, followed by methods, followed by events and then properties.

 

Lets now move our focus to the DisplayAllEvents method. The EventMap table stores all the types that have events in a similar way to the PropertyMap table. In the same way we get the variables start and end to store the first and last event owned by this type.

 

In the first for loop we display the initial spaces followed by the event directive and the event row number in comments. We then reuse the method GetPropertyAttribute to display the specialname attribute. Events cannot have default values. We then get the name of the event and unlike properties that have parameters, events have a data type.

 

Thus we do not need a index into the blob heap for the event signature but simply a coded index field coded that tells us whether the type is a TypeRef or a TypeDef. For event a it is a TypeRef and for event b a TypeDef.

 

Depending upon the type reference we simply use the typerefnames or the typedefnames array to give us the full type name. We write out the name and then scan the entire MethodSemantics table for a match of our event. The attributes variable in this case will have three values, addon, removeon and fire.

 

It shares the other directive with properties. Thus we trap those rows that have the table name as Event and the event row matches our event number. We have on purpose for event a have multiple declarations of the same type just to tell you that only the last one is used. However for the other directive all are used.

 

We write out the initial spaces as always, then one of the four attributes, followed by the type of the method from the methoddeftypearray and the return value of the method. The same methodindex variable is used as the offset like we did for properties.

 

We then get the name and namespace of the method and the type that is belongs to using the GetNestedTypeAsString and simply put them together. These methods have parameters that we finally write out in brackets.

 

Thus events are no different other than signature and having one more directive than properties. Thus the short and sweet explanations.

 

Program44.csc

string [] fieldflagsarray ;

string [] fieldparamarray ;

 

public void CreateSignatures ()

{

//MethodTable

if (MethodStruct != null)

{

methoddefreturnarray = new string[MethodStruct.Length];

methoddefparamarray  = new string[MethodStruct.Length];

methoddefparamarray1  = new string[MethodStruct.Length];

methoddeftypearray  = new string[MethodStruct.Length];

methoddefparamcount = new int[MethodStruct.Length];

for ( int l = 1 ; l < MethodStruct.Length ; l++)

{

CreateSignatureForEachType(1 , MethodStruct[l].signature, l);

}

}

//MethodRefTable

if ( MemberRefStruct != null )

{

methodreftypearray = new string[MemberRefStruct.Length];

methodrefreturnarray = new string[MemberRefStruct.Length];

methodrefparamarray1  = new string[MemberRefStruct.Length];

for ( int l = 1 ; l < MemberRefStruct.Length ; l++)

{

CreateSignatureForEachType(2 , MemberRefStruct[l].sig, l);

}

}

//fieldtable

if ( FieldStruct != null )

{

fieldflagsarray = new string[FieldStruct.Length];

fieldparamarray = new string[FieldStruct.Length];

for ( int l = 1 ; l < FieldStruct.Length ; l++)

{

CreateSignatureForEachType (6, FieldStruct[l].sig, l);

}

}

//propertytable

if ( PropertyStruct != null )

{

propertyparmarray = new string[PropertyStruct.Length];

propertyreturnarray = new string[PropertyStruct.Length];

propertytypearray  = new string[PropertyStruct.Length];

for ( int l = 1 ; l < PropertyStruct.Length ; l++)

{

CreateSignatureForEachType(7, PropertyStruct[l].type, l);

}

}

}

public void CreateSignatureForEachType (byte type , int index , int row)

{

int uncompressedbyte , count , howmanybytes;

howmanybytes = CorSigUncompressData(blob , index , out uncompressedbyte);

count = uncompressedbyte;

byte [] blob1 = new byte[count];

Array.Copy(blob , index + howmanybytes , blob1 , 0 , count);

if ( type == 7)

CreatePropertySignature(blob1 , row);

if ( type == 6)

CreateFieldSignature(blob1 , row);

if ( type == 1)

CreateMethodDefSignature(blob1 , row);

if ( type == 2)

CreateMethodRefSignature(blob1 , row);

}

public void CreateFieldSignature (byte [] blobarray , int row)

{

int howmanybytes , uncompressedbyte;

int index = 0;

howmanybytes = CorSigUncompressData(blobarray , index , out uncompressedbyte);

index = index + howmanybytes;

string returnstring = GetElementType(index , blobarray ,  out howmanybytes , 0 , "");

returnstring = returnstring.Replace("^",",");

fieldparamarray [row] = returnstring;

fieldflagsarray [row] = GetFieldAttributes (row);

}

public void DisplayOneType (int typedefindex)

{

DisplayOneTypeDefStart(typedefindex);

DisplaySizeAndPack(typedefindex);

DisplayAllSecurity( 0 , typedefindex);

DisplayNestedTypes(typedefindex);

DisplayOverride(typedefindex);

DisplayAllFields(typedefindex);

DisplayAllMethods(typedefindex);

DisplayAllEvents(typedefindex);

DisplayAllProperties(typedefindex);

DisplayOneTypeDefEnd(typedefindex );

}

public void DisplayAllFields (int typeindex)

{

if ( FieldStruct == null )

return;

int start , startofnext=0;

start =  TypeDefStruct[typeindex].findex ;

if ( typeindex == (TypeDefStruct.Length - 1) )

startofnext= FieldStruct.Length;

else

startofnext = TypeDefStruct[typeindex+1].findex ;

string returnstring = "";

for ( int fieldindex = start ; fieldindex < startofnext ; fieldindex++)

{

if ( typeindex == 1)

returnstring = "";

else

returnstring = CreateSpaces(spacesfornested + spacesforrest) ;

returnstring = returnstring + ".field /*04" + fieldindex.ToString("X6") + "*/" ;

returnstring = returnstring + " "  + fieldflagsarray [fieldindex]  +  fieldparamarray [fieldindex] +  " " + NameReserved(GetString(FieldStruct[fieldindex].name)) ;

if ( fieldflagsarray[fieldindex].IndexOf("privatescope") != -1 )

returnstring = returnstring + "$PST04" + fieldindex.ToString("X6");

string fieldrva = GetFieldRVA(fieldindex);

if ( fieldrva != "")

returnstring = returnstring + " at" + fieldrva;

int len = returnstring.Length;

string str = DisplayFromConstantsTable (fieldindex ,0 ,  "FieldDef");

if ( str != "")

str = str.Remove(str.Length-2,2);

returnstring = returnstring + str;

Console.WriteLine("{0}" , returnstring);

}

}

public string DisplayFromConstantsTable(int start , int end ,string tablename )

{

string returnstring = "";

for ( int ii = 1 ; ii < ConstantsStruct.Length ; ii++)

{

string consttablename = GetHasConstTable(ConstantsStruct[ii].parent);

int constindex = GetHasConstValue(ConstantsStruct[ii].parent);

if ( consttablename == tablename )

{

if ( (constindex >= start && constindex < end && start != end && tablename == "ParamDef" ) || ((tablename == "Property" || tablename == "FieldDef") && start == constindex ) )

{

}

}

}

return returnstring;

}

public string GetFieldAttributes (int fieldrow)

{

int flags = FieldStruct[fieldrow].flags;

string returnstring = "";

string arrayoffset = "";

int ii;

if ( FieldLayoutStruct != null)

{

for ( ii = 1 ; ii < FieldLayoutStruct.Length ; ii++)

{

int jj = FieldLayoutStruct[ii].fieldindex ;

if ( jj == fieldrow )

break;

}

if ( ii != FieldLayoutStruct.Length )

{

int offset = FieldLayoutStruct[ii].offset ;

arrayoffset = "[" + offset.ToString("") + "] ";

}

}

if ( (flags & 0x06) == 0x06)

returnstring = returnstring + "public ";

else

if ( (flags & 0x05) == 0x05)

returnstring = returnstring + "famorassem ";

else

if ( (flags & 0x03) == 0x03)

returnstring = returnstring + "assembly ";

else

if ( (flags & 0x01) == 0x01)

returnstring = returnstring + "private ";

else

if ( (flags & 0x02) == 0x02)

returnstring = returnstring + "famandassem ";

else

if ( (flags & 0x04) == 0x04)

returnstring = returnstring + "family ";

else

returnstring = returnstring + "privatescope ";

if ( (flags & 0x10) == 0x10)

{

int firstfourbits = flags & 0xF;

if ( (firstfourbits == 0x06) || (firstfourbits  == 0x01))

{

returnstring = returnstring + "static " ;

}

else

returnstring = "static " + returnstring;

}

returnstring = arrayoffset + returnstring;

if ( (flags & 0x20) == 0x20)

returnstring = returnstring + "initonly ";

if ( (flags & 0x40) == 0x40)

returnstring = returnstring + "literal ";

if ( (flags & 0x80) == 0x80)

returnstring = returnstring + "notserialized ";

if ( (flags & 0x200) == 0x200)

returnstring = returnstring + "specialname ";

if ( (flags & 0x400) == 0x400)

returnstring = returnstring + "rtspecialname ";

if ( (flags & 0x400) == 0x400)

returnstring = returnstring + "";

returnstring = returnstring +  DecodeParamAttributes ( flags , 0 , fieldrow , 0x1000);

return returnstring;

}

public string GetFieldRVA (int fieldrow)

{

string returnstring = "";

int ii;

if ( FieldRVAStruct == null )

return "";

for ( ii = 1 ; ii < FieldRVAStruct.Length ; ii++)

{

int jj = FieldRVAStruct[ii].fieldi;

if ( jj == fieldrow  )

break;

}

if ( ii != FieldRVAStruct.Length )

{

int rva= FieldRVAStruct[ii].rva ;

returnstring = " D_" + rva.ToString("X8") ;

}

return returnstring;

}

 

e.il

.class zzz

{

.field int32 i

.field [10] public int32 j

.field static public int32 k

.field static family int32 l

.field public static int32 n at vijay

.field public marshal(int32) int32 o

.field public int32 m1 = bool(true)

.field public int32 m2 = int8(2)

.field public int32 m3 = int16(3)

.field public int32 m4 = int32(5)

.field public int32 m5 = int64(6)

.field public  string p = "hi"

.field public  rtspecialname string q

}

.data vijay = int32(11)

 

Output

.class /*02000002*/ private auto ansi zzz

       extends [mscorlib/* 23000001 */]System.Object/* 01000001 */

{

  .pack 1

  .size 0

  .field /*04000001*/ privatescope int32 i$PST04000001

  .field /*04000002*/ [10] public int32 j

  .field /*04000003*/ public static int32 k

  .field /*04000004*/ static family int32 l

  .field /*04000005*/ public static int32 n at D_00004000

  .field /*04000006*/ public  marshal( int32) int32 o

  .field /*04000007*/ public int32 m1 = bool(true)

  .field /*04000008*/ public int32 m2 = int8(0x02)

  .field /*04000009*/ public int32 m3 = int16(0x0003)

  .field /*0400000A*/ public int32 m4 = int32(0x00000005)

  .field /*0400000B*/ public int32 m5 = int64(0x6)

  .field /*0400000C*/ public string p = "hi"

  .field /*0400000D*/ public string q

} // end of class zzz

 

What we call instance variables in a programming language, il calls the same thing fields. Fields are what we use our programs data in memory. Thus fields at the end of the day are nothing but memory locations that have a type. As with everything we have static and instance fields. A static field is one no matter how many instances of the type we create.

 

A instance field can only be used once we instantiate the field and thus each time we do so, a new instance of that field gets created. Thus an instance field is not shared by all the instances like a static field is. Unlike C#, il supports global fields and methods but with the condition that global fields must be static.

 

A field is declared using the directive .field and this is first followed by a number specifying the offset in memory where this field must be stored as for field j which is at an offset of 10. Obviously the layout of the class must be instance and this offset is invalid for global or static fields.

 

This is followed by field attributes like public, private that we did earlier plus some unique for fields. Then comes the obvious the type of the field and then the name. After this comes two optional entities, associating the field with a memory location like in field n or giving it a default value like filed m onwards.

 

All global fields must have such a data label as it specifies where in the PE file the fields data is to be found. For static fields this requirement is optional but normally followed.

 

As always we start with the CreateSignatures method where we initialize the two string arrays, fieldflagsarray which stores the fields attributes and data type of the field wrongly called fieldparamarray as the field has no parameters. We now call the method CreateSignatureForEachType with a value of 6 as this is code given for a field.

 

In this method we simply call the method CreateFieldSignature which is the simplest method we have ever written. All that we do here is to first read the first byte which will always be 6 and this is then followed by the type of field that we store in the array fieldparamarray.

 

But before this we replace the ^ sign with a comma due to the array problem we had earlier. The fieldflagsarray is really not needed as the field attributes are not stored with the signature in the blob array but along with each field in the Field table. Lets move to the fields attributes which get decoded by the method GetFieldAttributes.

 

It is the flags member of the field table that stores the attributes for the fields. The offset per field is stored in the table FieldLayout which has only two fields, one the field table row number and the other the offset. Thus we scan the table FieldLayout and quit out of the loop if we meet our field number.

 

If a match is found, we display the offset in square brackets in decimals and not in hex. Consistency thy name is not ildasm. Then as usual we have the seven access modifiers for the fields. There is only one small problem with the static keyword which has a bit mask of 0x10.

 

The only problem is whether static comes before or after the access modifiers. The problem as all questions asked about life are depends. If we have a public or private these come before the static, the rest of the access modifiers come after like we have with fields k and l respectively.

 

We have the attributes initonly, literal, notserialized, specialname and finally rtspecialname that do not show up like for field q. We now add the method DisplayAllFields in the method DisplayOneType and bear in mind the order.

 

For displaying all fields we do what we did for methods set the start and startofnext to the first and last but one field owned by each type. As we can have global fields we check if the typeindex value is one as this is for global fields and thus set the number of spaces to zero else to be decided by the namespace and nesting levels.

 

We then write out the field directive, the field row number in comments and then the attributes from the fieldflagsarray array and then the data type from the fieldparamarray. A space and then the name and if this has the attribute privatescope, then the name as always get the added string $PST04 and row number.

 

Lets take field n which has the words at followed by the name vijay that is defined using the data directive. The data directive uses the constants seen before to give a memory location in this vijay a value. We may use names like vijay but if you look at the output, it gets replaced by a label D_00004000.

 

If you look at the output generated by ildasm, this data directive has a value of 10 that we will display later. We have a method GetFieldRVA to figure out the at clause for us. All the at clauses are stored in another simple table called the FieldRVA table which as the earlier table has two columns.

 

The first is the rva of the field and the second the field row number. Like before if we meet a match we display the field rva which is a number show above and not a name like vijay. Finally we have to display the default value for the field and use the good old DisplayFromConstantsTable table to do so for us.

 

The second parameter is 0 like for a property. The only change we have made is that the if statement second condition now has checks that the constindex must be equal to start and the table name could be either FieldDef or Property as before. There is no difference in who uses a default value, they all get stored in the constants table.

 

As mentioned before we will revisit this method for handling floating point numbers and now also strings. This ends the discourse on fields.

 

Program45.csc

public void DisplayOneType (int typedefindex)

{

DisplayOneTypeDefStart(typedefindex);

DisplaySizeAndPack(typedefindex);

DisplayCustomAttribute("TypeDef" , typedefindex , 2 + spacefornamespace + spacesfornested);

DisplayAllSecurity( 0 , typedefindex);

DisplayNestedTypes(typedefindex);

DisplayOverride(typedefindex);

DisplayAllFields(typedefindex);

DisplayAllMethods(typedefindex);

DisplayAllEvents(typedefindex);

DisplayAllProperties(typedefindex);

DisplayOneTypeDefEnd(typedefindex );

}

public void DisplayCustomAttribute (string tname , int tabindex , int noofspaces)

{

if (CustomAttributeStruct == null)

return;

string initialspaces;

initialspaces = CreateSpaces(noofspaces);

for ( int ii = 1 ; ii < CustomAttributeStruct.Length ; ii++)

{

string parentcodedtablename = GetHasCustomAttributeTable (CustomAttributeStruct[ii].parent) ;

int parentcodedindex = GetHasCustomAttributeValue(CustomAttributeStruct[ii].parent);

string typecodetable = GetCustomAttributeTypeTable (CustomAttributeStruct[ii].type) ;

int typecodedindex  = GetCustomAttributeTypevalue(CustomAttributeStruct[ii].type);

int tableindexcode = 0;

int tablenumber = 0;

string returnstring = "";

bool custombug = false;

int typeindex ;

string typename = "";

string returnvaluestring = "";

string paramstring  = "";

string dummy = "";

string methodname = "";

string onespace="";

if ( typecodetable == "MethodRef" )

{

tableindexcode = 0x0A;

typeindex= MemberRefStruct[typecodedindex].clas >> 3;

typename = typerefnames[typeindex] ;

returnvaluestring  = methodrefreturnarray [typecodedindex];

paramstring = methodrefparamarray1 [typecodedindex];

tablenumber = 1;

methodname = NameReserved(GetString(MemberRefStruct[typecodedindex].name));

onespace = " ";

}

else

{

tableindexcode = 0x06;

typeindex = GetTypeForMethod (typecodedindex);

typename = typedefnames[typeindex] ;

returnvaluestring = methoddefreturnarray[typecodedindex];

paramstring  = methoddefparamarray1[typecodedindex] ;

tablenumber = 02;

methodname = NameReserved(GetString(MethodStruct[typecodedindex].name));

}

if ( (tname == parentcodedtablename && tabindex == parentcodedindex) || (parentcodedtablename == "TypeRef" && tabindex == 0) )

{

if (typename.IndexOf("System.Diagnostics.DebuggableAttribute") != -1)

custombug = true;

if ( custombug )

{

Console.Write(CreateSpaces(noofspaces));

Console.WriteLine("// --- The following custom attribute is added automatically, do not uncomment -------");

returnstring = returnstring + CreateSpaces(noofspaces) + "//";

if ( initialspaces == "")

returnstring = returnstring + "  ";

}

if ( parentcodedtablename == "TypeRef")

{

returnstring = returnstring + initialspaces + ".custom /*0C" + ii.ToString("X6") +"*/ (" + typerefnames[parentcodedindex];

returnstring = returnstring + "/*" + tablenumber.ToString("X2") + parentcodedindex.ToString("X6") + "*/ ) ";

}

else

returnstring = returnstring + initialspaces + ".custom /*0C" + ii.ToString("X6") + ":" + tableindexcode.ToString("X2") + typecodedindex.ToString("X6") + "*/ ";

returnstring = returnstring + "instance " + returnvaluestring + onespace + typename ;

returnstring = returnstring + "::" +  methodname;

returnstring = returnstring + "(";

if ( noofspaces == 0)

dummy   = ParamOnMultipleLines (paramstring , returnstring.Length + 2);

else if ( paramstring  != null)

dummy   = ParamOnMultipleLines(paramstring , returnstring.Length);

if ( custombug )

{

int ind1 = dummy.IndexOf("\r\n");

dummy= dummy.Insert(ind1+2, CreateSpaces(noofspaces) + "//");

dummy= dummy.Remove(ind1+10, 4);

}

returnstring = returnstring + dummy ;

returnstring = returnstring + ")";

returnstring = returnstring + " /* " + tableindexcode.ToString("X2") + typecodedindex.ToString("X6") + " */";

Console.Write(returnstring);

int index = CustomAttributeStruct[ii].value;

int howmanybytes,uncompressedbyte ;

howmanybytes = CorSigUncompressData(blob , index , out uncompressedbyte);

if ( uncompressedbyte == 0)

{

Console.WriteLine();

continue;

}

index = index + howmanybytes;

byte [] blobarray = new byte[uncompressedbyte];

Array.Copy(blob , index , blobarray , 0 , uncompressedbyte);

bool displayoneline = true;

string displaystring = "";

for ( int  jj = 0 ; jj < uncompressedbyte ; jj++)

{

if ( blobarray[jj] < 0x20)

displayoneline = false;

if ( blobarray[jj] >= 0x7f)

displayoneline = false;

}

if ( displayoneline)

{

displaystring = " = \"";

for ( int  jj = 0 ; jj < uncompressedbyte ; jj++)

{

displaystring = displaystring + (char)blobarray[jj];

}

displaystring = displaystring + "\"";

if ( tname == "MemberRef")

Console.Write(displaystring);

else

Console.WriteLine(displaystring);

}

else

{

int startoffunctionclosebracket = returnstring.LastIndexOf(")");

int lastenter = returnstring.LastIndexOf("\r\n");

if ( lastenter == -1)

lastenter = -2;

int diff2 = startoffunctionclosebracket - lastenter + 19;

Console.Write(" = ( ");

DisplayFormattedColumns (CustomAttributeStruct[ii].value,diff2 , false);

}

}

}

}

public int GetCustomAttributeTypevalue(int attributecodedtype)

{

return attributecodedtype >> 3;

}

public string GetCustomAttributeTypeTable( int attributecodedtype)

{

string returnstring = "";

int tag = attributecodedtype &  0x07;

if ( tag == 0)

returnstring = returnstring + "NotUsed";

if ( tag == 1)

returnstring = returnstring + "NotUsed";

if ( tag == 2)

returnstring = returnstring + "MethodDef";

if ( tag == 3)

returnstring = returnstring + "MethodRef";

if ( tag == 4)

returnstring = returnstring + "NotUsed";

return returnstring;

}

public int GetHasCustomAttributeValue( int attributecodedparent)

{

return attributecodedparent >> 5;

}

public string GetHasCustomAttributeTable ( int attributecodedparent)

{

string returnstring = "";

int tag = attributecodedparent & 0x1F;

if ( tag == 0)

returnstring = returnstring + "MethodDef";

if ( tag == 1)

returnstring = returnstring + "FieldDef";

if ( tag == 2)

returnstring = returnstring + "TypeRef";

if ( tag == 3)

returnstring = returnstring + "TypeDef";

if ( tag == 4)

returnstring = returnstring + "ParamDef";

if ( tag == 5)

returnstring = returnstring + "InterfaceImpl";

if ( tag == 6)

returnstring = returnstring + "MemberRef";

if ( tag == 7)

returnstring = returnstring + "Module";

if ( tag == 8)

returnstring = returnstring + "DeclSecurity";

if ( tag == 9)

returnstring = returnstring + "Property";

if ( tag == 10)

returnstring = returnstring + "Event";

if ( tag == 11)

returnstring = returnstring + "Signature";

if ( tag == 12)

returnstring = returnstring + "ModuleRef";

if ( tag == 13)

returnstring = returnstring + "TypeSpec";

if ( tag == 14)

returnstring = returnstring + "Assembly";

if ( tag == 15)

returnstring = returnstring + "AssemblyRef";

if ( tag == 16)

returnstring = returnstring + "File";

if ( tag == 17)

returnstring = returnstring + "ExportedType";

if ( tag == 18)

returnstring = returnstring + "ManifestResource";

return returnstring;

}

}

 

e.il

.assembly extern vijay

{

}

.class zzz

{

.custom instance void [vijay]a1::.ctor(bool, string ) = ( 41 41 42 )

.custom instance void [vijay]a2::.ctor(bool) = ( 7f 41 41 42 )

}

//.custom ([vijay]a3) instance void [vijay]a4::.ctor()= ( 65 )

 

 

Output

.class /*02000002*/ private auto ansi zzz

       extends [mscorlib/* 23000002 */]System.Object/* 01000001 */

{

  .custom /*0C000001:0A000001*/ instance void [vijay/* 23000001 */]a1/* 01000002 */::.ctor(bool,

                                                                                           string) /* 0A000001 */ = "AAB"

  .custom /*0C000002:0A000002*/ instance void [vijay/* 23000001 */]a2/* 01000003 */::.ctor(bool) /* 0A000002 */ = ( 7F 41 41 42 )                                     // .AAB

} // end of class zzz

 

This example deals with one major feature of the .net world called custom attributes. Using this we are allowed to extend the .net world in directions the original designers did not plan. Thus in the DisplayOneType method we add the DisplayCustomAttribute method with three parameters.

 

The first is the entity contains the custom attribute. You will be surprised to note that there are over 19 entities that this attribute can be placed in. In our program now we will wonder about attributes placed in the TypeDef table only.

 

The second parameter is the row number of the previous parameter as each type can have as many attributes as it likes. Thus the above table-index combination will be present in the CustomAttribute table as a coded index. The last is the indentation to be given to the custom directive.

 

The custom attributes add annotations to any item of the metadata and are used to store anything we like at compile time and access it at runtime. We start with the directive custom followed by the name of a method that has to be ctor.

 

This is followed by the parameters if any and then some bytes in brackets that will contain the values that the parameters need to be initialized with. In our case we have two custom attributes, both refer to the .ctor method or the constructor but the first is a MemberRef as it refers to a .ctor defined in the assembly vijay and the second to a MethodDef as the .ctor lies in the same type.

 

The problem with the initialization bytes is that if they are all ascii, they are displayed one after the other, if there is a single non ascii byte, then they are displayed with brackets and with the ascii values as comments like before. There is one last exception.

 

If the type is System.Diagnostics.DebuggableAttribute only then there are some comments added before the attribute. Lets start with the actual code in the DisplayCustomAttribute method where we scan the CustomAttribute table as each custom attribute is stored here. The first field parent is a coded index that tells us where this custom directive must be written.

 

The method GetHasCustomAttributeTable has up to 19 different tables and two programs down the line we have tried to show you each of the above 19 cases. The parentcodedtablename variable does store the table name and the parentcodedindex the actual row number in that table that owns the custom directive.

 

This is the only coded index that takes up five bits. Then we have one more coded index that tells us whether the method .ctor is a MethodDef or a MethodRef. We have a function GetCustomAttributeTypeTable that decodes the type field and even though it takes up three bits, one bit would have sufficed.

 

We then create a huge list of variables that we set depending upon whether we have a Method definition or a Method reference. We will explain the values we set the variables to when we actually use them. We do this so that we can have one set of statements that handle both cases.  

 

We now have a if statement that decides if we have a match. There are two cases when a match is found. One when both tname and tabindex parameters match the parent field coded index. Two is when we have a value of TypeRef for the table and the tabindex parameter is 0. This special case we will handle a little later.

 

The first thing we do when we meet a match is check whether we want the extra comments if the type is System.Diagnostics.DebuggableAttribute. The typename variable can either be a type in TypeDef or the TypeRef table.

 

As we have the array typerefnames or typedefnames that store the entire name we use the variable typeindex as an offset to this array. We get the type the method is in by either using the GetTypeForMethod method for a MethodDef and the simple method for a MemberRef by reading the clas field of the MemberRef table.

 

The typecodedindex variable is the index into either of the two tables. We keep using the noofspaces to write out the spaces. There will be one case only where the tablename is TypeRef and the spaces are zero. In comments we have shown you a custom attribute that has a open and close bracket and the name of the type ref within it.

 

We first handle this specific this first. We first display the dumber of initialspaces and then the custom directive and the row number denoted by the loop variable ii. Then we have the open bracket and use the parentcodedindex variable as the index into the typerefnames array that contains all the type refs.

 

We then display the tablenumber variable that is either 2 for a typedef or a MethodDef. For a method ref this will be 1 the code for a TypeRef table. In the dame vein the tableindexcode variable is 6 the code for a MethodDef in the else statement or the code for a MethodRef which is 0a.

 

We end by writing out the row number of the parent table stored in parentcodedindex variable. If the parentcodedtablename table is not TypeRef but any of the other 18, then life gets simpler. We display the initial spaces and then the custom directive and the row number in the custom table within comments.

 

This is followed by the MethodRef or MethodDef table number stored in the tableindexcode variable and then the row number in one of these tables. As the constructor is not the static one .cctor, we have to write out the words instance followed by the return value of the constructor.

 

The variable returnvaluestring is set to the methoddefreturnarray array for the case of a MethodDef and the corresponding methodrefreturnarray for a MemberRef. The variable typecodedindex is used as an offset like before. Then comes a problem.

 

In the case of the MemberRef we have not left a space at the end of the each member of the methodrefreturnarray array and thus we set the onespace variable to a space. We do not do this in the case of a MethodDef.

 

We then write out the typename variable that we explained earlier and then the name of the method that can either come from the name field of the MemberRef or MethodDef table. We then open a bracket and then again check for number of spaces. If zero the special case of the TypeRef , we call the ParamOnMultipleLines with two spaces more than the length of the string, if not we just use the length as it would be obvious.

 

The paramstring string that contains all the parameters is once again either the methodrefparamarray1 or the methoddefparamarray1. One small problem if we have added the extra comments our work is not done yet. We first find out the last enter or the last line and then add some comments two chars later with some spaces and comments.

 

These should come at the beginning of the last line. We then need to remove the 4 chars in this case as noofspaces is 2  we have just added to position the parameters  one below the other. Normally we will remove the number of chars we have added in the Insert function.

 

We then close the bracket and write out the table code number for MethodDef or MethodRef and the table row number. All is not over yet as we have to display either the bytes in brackets or a string. We use the value field as an offset into the blob array.

 

We as before use the first bytes to figure out the length of the bytes owned by the custom attribute. We write out a empty enter if there are no bytes at all. We then sue the Copy method to copy these bytes into a fresh and clean array called blobarray. We then set the displayoneline variable to true initially  and then scan the entire blobarray.

 

If we meet a single number that is not a ascii char, we set the displayoneline variable to false. For us a non ascii char is a byte that is less than 32 or larger than 128. If this variable is true we start with a double inverted comma and the write the entire string into the displaystring variable.

 

We then close the double and either place a enter or not depending upon whether the table name is MemberRef or not. A little later we will explain that a call of a method can also have a custom attribute and thus we will have a problem of a extra enter if the custom directive is on a MemberRef. 

 

Finally we have to display the bytes in brackets and thus we first out where we placed the last close function bracket and the last enter. If there was no enter placed then we set the lastenter variable to minus 2 as a enter takes up two bytes. We then get the difference as the subtraction of the two minus 19 bytes.

 

We use 19 bytes as we painstakingly calculated the number of bytes we have after the last close bracket. These include spaces, the comments and the row number and the equal to sign. We call our trusty DisplayFormattedColumns to display the bytes for us.

 

Program46.csc

Public void DisplayAllMethods (int typeindex)

{

int tablerow = entrypointtoken & 0x00ffffff;

if ( tablerow == methodindex )

methodstring = methodstring + "\r\n" + CreateSpaces(spacesforrest + 2 + spacesfornested) + ".entrypoint";

Console.WriteLine(methodstring);

DisplayCustomAttribute ("MethodDef" , methodindex , 2 + spacesforrest + spacesfornested);

DisplayParamsForMethod (methodindex);

DisplayAllSecurity( 1 , methodindex );

}

public string DisplayParamsForMethod (int methodrow)

{

if ( ParamStruct == null)

return "";

string returnstring = "";

int start=0, end=0;

start = MethodStruct[methodrow].param;

if ( methodrow == (MethodStruct.Length - 1))

end = ParamStruct.Length  ;

else

end = MethodStruct[methodrow+1].param;

for ( int index = start ; index < end ; index++)

{

int seq = ParamStruct[index].sequence;

returnstring = CreateSpaces(spacesforrest+ spacesfornested+2) + ".param [" + seq.ToString() + "]" + "/*08" + index.ToString("X6") + "*/";

string str = DisplayFromConstantsTable (index ,0 ,"ParamDef");

if ( str != "")

Console.Write(returnstring + str );

bool b = HasCustomAttribute ("ParamDef" , index);

if ( b && str == "")

Console.WriteLine(returnstring + " ");

DisplayCustomAttribute ("ParamDef" , index , spacesforrest+ spacesfornested+2);

}

return "";

}

public string DisplayFromConstantsTable(int start , int end ,string tablename )

{

string returnstring = "";

if ( ConstantsStruct == null)

return "";

for ( int ii = 1 ; ii < ConstantsStruct.Length ; ii++)

{

string consttablename = GetHasConstTable(ConstantsStruct[ii].parent);

int constindex = GetHasConstValue(ConstantsStruct[ii].parent);

if ( consttablename == tablename )

{

if ( start == constindex )

{

if ( consttablename == "Property")

returnstring = " = ";

else

returnstring = "  = ";

if ( ConstantsStruct[ii].dtype == 2 )

{

bool val = BitConverter.ToBoolean(blob , ConstantsStruct[ii].value+1);

if ( val )

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(true)" + "\r\n";

else

returnstring = returnstring +  GetType(ConstantsStruct[ii].dtype) + "(false)"+ "\r\n";

}

public bool HasCustomAttribute( string tablename , int index)

{

if ( CustomAttributeStruct == null)

return false;

for ( int ii = 1 ; ii < CustomAttributeStruct.Length ; ii++)

{

string parentcodedtablename= GetHasCustomAttributeTable(CustomAttributeStruct[ii].parent) ;

int parentcodedindex= GetHasCustomAttributeValue(CustomAttributeStruct[ii].parent);

if ( parentcodedtablename == tablename  && parentcodedindex == index)

return true;

}

return false;

}

 

e.il

.assembly extern vijay

{

}

.class zzz

{

.method public instance void .ctor(bool i , bool j , int32 k , int64 l , int16 m , int8 n)

{

.custom instance void [vijay]a3::.ctor(bool) = ( 41 41 42 )

.custom instance void [vijay]a31::.ctor(bool)

.param[1]

.param[3]

.custom instance void [vijay]a2::.ctor(bool) = ( 41 41 42 )

.param[2] = int8(1)

.custom instance void [vijay]a1::.ctor(bool) = ( 41 41 42 )

.param[4]

.custom instance void [vijay]a4::.ctor(bool) = ( 41 41 42 )

.param[5] = int16(2)

.param[6]

.custom instance void [vijay]a6::.ctor(bool)

.custom instance void [vijay]a6::.ctor(bool)

.param[7]

}

}

 

Output

  .method /*06000001*/ public specialname rtspecialname

          instance void  .ctor(bool i,

                               bool j,

                               int32 k,

                               int64 l,

                               int16 m,

                               int8 n) cil managed

  // SIG: 20 06 01 02 02 08 0A 06 04

  {

    .custom /*0C000001:0A000001*/ instance void [vijay/* 23000001 */]a3/* 01000002 */::.ctor(bool) /* 0A000001 */ = "AAB"

    .custom /*0C000002:0A000002*/ instance void [vijay/* 23000001 */]a31/* 01000003 */::.ctor(bool) /* 0A000002 */

    .param [2]/*08000002*/  = int8(0x01)

    .custom /*0C000003:0A000004*/ instance void [vijay/* 23000001 */]a1/* 01000005 */::.ctor(bool) /* 0A000004 */ = "AAB"

    .param [3]/*08000003*/

    .custom /*0C000004:0A000003*/ instance void [vijay/* 23000001 */]a2/* 01000004 */::.ctor(bool) /* 0A000003 */ = "AAB"

    .param [4]/*08000004*/

    .custom /*0C000005:0A000005*/ instance void [vijay/* 23000001 */]a4/* 01000006 */::.ctor(bool) /* 0A000005 */ = "AAB"

    .param [5]/*08000005*/  = int16(0x0002)

    .param [6]/*08000006*/

    .custom /*0C000006:0A000006*/ instance void [vijay/* 23000001 */]a6/* 01000007 */::.ctor(bool) /* 0A000006 */

    .custom /*0C000007:0A000006*/ instance void [vijay/* 23000001 */]a6/* 01000007 */::.ctor(bool) /* 0A000006 */

    // Method begins at RVA 0x2050

    // Code size       0 (0x0)

  } // end of method zzz::.ctor

 

We now need to display the attributes that come along with the param directive. If we look at the method DisplayAllMethods we will first see the call of the method DisplayCustomAttribute with the first parameter MethodDef as we want to first display all custom attributes associated with a Method.

 

This will be the first custom attribute in our il file. Then we have rewritten the method DisplayParamsForMethod as this is the method that will earlier displayed all the param directives for us. Lets take a fresh look at the param  directives in our il file.

 

The param directive with 1 will never show up in the output as it requires one of two conditions to be fulfilled. Either it must have a constant value or a custom attribute associated with it. Thus param 3 has a custom attribute following it and thus it qualifies. The param 2 has  both a custom attribute as well as a constant value.

 

Param 3 and 2 are not in sorted order but the system sorts them out before placing them in tables. Param 4 has an attribute, param 5 only a constant value and param 6 has two custom attributes and param 7 is not found in the output as there are only six parameters.

 

Lets put the above in code where we first write out all the custom attributes for a method and then the params and the attributes if any. We start once again by rewriting code and this is the bane of our life. Some programs above we had such a workable DisplayParamsForMethod method and all that gone done the tube. What a life.

 

We first tell ourselves that we have six parameters to the method and thus can have at least 6 rows in the param table. We would you to confirm this and look at the name of the params and the sequence numbers. Thus at most we will write out six .params directives.

 

We prepare ourselves by first picking out the sequence number as they are ordered sequentially in the seq variable with index being the succeeding row numbers in the Param table. We then create the param directive string with the seq variable as the number in brackets and the index variable being used for the row number in the param table as a comment.

 

This string variables value may never be used. We then use the slightly rewritten method DisplayFromConstantsTable to give us any constant value that the param attribute may have. The DisplayFromConstantsTable now uses the same logic as for Fields and Properties, only the first parameter is used, the second end is ignored.

 

For the sake of not changing the other method calls for the method DisplayFromConstantsTable we chose to maintain three parameters when we are using only two. The if statement I also simple, we check for a table name match as well as a row match. If this happens we first write out the initial equal to sign and then the constant value which code is unaltered.

 

The problem is that the property table requires one less space than fields and params. Thus we check the string return value from the method DisplayFromConstantsTable. If it I not null, this only means that we have a constant value and thus the param directive must be displayed.

 

Thus we have a if statement that displays the param directive but we must remember because of our initial design this string has a enter at the end and therefore we use the Write and not WriteLine method. We also now need to figure out whether this param has a custom attribute.

 

Thus we first have a simple method HasCustomAttribute that takes a table name and a row number and tells us whether this entity owns a row in the CustomAttribute table. In this method we scan all the rows of the CustomAttribute table and if the coded index parent gives us a row and table name match we return true and quit the loop.

 

If we move out of the loop, it means no match and we return false. Now that the variable tells us whether we have a custom  attribute associated with our param directive or not, we use a if statement that checks that variable b is true as well as the str variable is null.

 

This is important as if the str variable is non null, the earlier if statement displayed the param directive. We also add a space after the param directive as if it has no default value, it has to have a extra space. Now we use the DisplayCustomAttribute method to display the custom attributes if any associated with this param row number.

 

Continued >>>>