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
{
}
.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);