Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

DotNetPeLib: A Library to Read and Generate .NET Assemblies in C++11

0.00/5 (No votes)
2 Jan 2020 1  
DotNetPELib is a library which abstracts managed information such as namespaces, classes, fields, methods, and instructions. The information can then be used to generate assembly language source files, or PE executables or DLLs.

Introduction

When I gave the idea to David, creator and developer of the C/C++ compiler OrangeC, https://github.com/LADSoft/OrangeC, to generate .NET code from the C code, together I gave the idea of creating a lib. to assist in generating .NET code and .NET assemblies.

Seeing that there was nothing compared to this on the internet, except that libs. written in C#/VB.NET to generate .NET code, the idea was super accepted so he started to develop a lib. in C ++ 11 to generate .NET assemblies to use.

Basically, this lib. is intended for compiler developers, who want or intend to port their compiler to generate .NET code as well.

The main idea of this article is not to show how the library works internally, but, how to use it.

Where to Get the Lib. Code?

You can get the lib code here.

Project That Uses This Lib

Orange C compiler (https://www.codeproject.com/Articles/1128868/Compiling-your-C-code-to-NET)

Let's Build Some Tests and Examples

For these examples, I created a project in VS 2015 and I have added all the lib code in the project.

Simplest Program

In this first example, let's build a most minimal program, a simple class with main method to return a value.

C# representation of code:

namespace ConsoleApp
{
    class Program
    {
        static int Main()
        {
            return 0;
        }
    }
}

For this, it is necessary to know how is this code in MSIL code, for this, we can build this program with C# compiler and decompile the code to see the MSIL code.

MSIL code:

.class private auto ansi beforefieldinit ConsoleApp.Program
    extends [mscorlib]System.Object
{
    .method public hidebysig specialname rtspecialname instance void .ctor () cil managed
    {
        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: nop
        IL_0007: ret
    }

    .method private hidebysig static int32 Main () cil managed
    {
        .entrypoint
        .locals init (
            [0] int32 V_0
        )

        IL_0000: ldc.i4.0
        IL_0001: stloc.0
        IL_0002: ldloc.0
        IL_0003: ret
    }
}

P.S. for this example, I just did a literal translation:

#include <iostream>
#include "DotNetPELib.h"

using namespace DotNetPELib;

void main()
{
 // Create a instance of PELib with assembly name and the CorFlags
 PELib libEntry("test1", PELib::ilonly | PELib::bits32);

 // Get the context of current assemblie working
 DataContainer* working = libEntry.WorkingAssembly();

 // Create the namespace
 Namespace* nameSpace = libEntry.AllocateNamespace("ConsoleApp");
 working->Add(nameSpace); // Add the new namespace at the assemblie

 // Create the class
 Class* cls = libEntry.AllocateClass("Program", Qualifiers::Ansi, -1, -1);
 nameSpace->Add(cls); // Add the new class to the namespace

 // Create the signature of main method
 MethodSignature* sig = libEntry.AllocateMethodSignature("Main", MethodSignature::Managed, cls);
 sig->ReturnType(libEntry.AllocateType(Type::i32, 0)); // Set the return type of method

 // Create the main method
 Method* main = libEntry.AllocateMethod(sig, Qualifiers::Private | 
                Qualifiers::HideBySig | Qualifiers::Static, true);
 
 // Add the instructions to the method main
 main->AddLocal(libEntry.AllocateLocal("V_0", libEntry.AllocateType(Type::i32, 0)));
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_ldc_i4_0));
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_stloc_0));
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_ldloc_0));
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_ret));

 try
 {
  // Try to optimize the MSIL code
  main->Optimize(libEntry);
 }
 catch (PELibError exc)
 {
  std::cout << "Optimizer error: " << exc.what() << std::endl;
 }
 
 cls->Add(main); // Add the method to the class

 libEntry.DumpOutputFile("test1.il", PELib::ilasm, false);  // Generate the MSIL code
 libEntry.DumpOutputFile("test1.exe", PELib::peexe, false); // Generate the .Net Executable
}

Let's look at the output:

.corflags 3

.assembly test1{
}

.namespace 'ConsoleApp' {
.class ansi 'Program' {.method private static hidebysig int32 'Main'(){
 .locals (
  [0] int32 'V_0/0'
 )
 .entrypoint
 .maxstack 1

 ldc.i4.0
 stloc.0
 ldloc.0
 ret
}
}
}

Image 1

Creating the Method's Signatures

To be able to use external methods, from .NET, it's necessary to create the signature of the method, for this, we will create one more source file, with all this signatures will be used in all the source samples:

#include "Signatures.h"

MethodSignature* CreateSignatureForToStringMethod(PELib& libEntry)
{
 // Create the signature of "ToString()" method
 MethodSignature *sig = libEntry.AllocateMethodSignature
                        ("ToString", MethodSignature::Instance, nullptr);
 sig->ReturnType(libEntry.AllocateType(Type::string, 0));
 sig->FullName("[mscorlib]System.Int32::ToString");
 return sig;
}

MethodSignature* CreateSignatureForToStringFormatMethod(PELib& libEntry)
{
 // Create the signature of "Format()" method
 MethodSignature *sig = libEntry.AllocateMethodSignature("ToString", 0, nullptr);
 sig->ReturnType(libEntry.AllocateType(Type::string, 0));
 sig->AddParam(libEntry.AllocateParam("a_0str", libEntry.AllocateType(Type::string, 0)));
 sig->AddParam(libEntry.AllocateParam("a_0obj", libEntry.AllocateType(Type::object, 0)));
 sig->FullName("[mscorlib]System.String::Format");
 return sig;
}

MethodSignature* CreateSignatureWriteMethod(PELib& libEntry)
{
 // Create the signature of "System.Console::WriteLine" method
 MethodSignature *sig = libEntry.AllocateMethodSignature("Write", 0, nullptr);
 sig->ReturnType(libEntry.AllocateType(Type::Void, 0));
 sig->AddParam(libEntry.AllocateParam("strng", libEntry.AllocateType(Type::string, 0)));
 sig->FullName("[mscorlib]System.Console::Write");
 return sig;
}

MethodSignature* CreateSignatureWriteLineMethod(PELib& libEntry)
{
 // Create the signature of "System.Console::WriteLine" method
 MethodSignature *sig = 
    libEntry.AllocateMethodSignature("WriteLine", 0, nullptr);
 sig->ReturnType(libEntry.AllocateType(Type::Void, 0));
 sig->AddParam(libEntry.AllocateParam
    ("strng", libEntry.AllocateType(Type::string, 0)));
 sig->FullName("[mscorlib]System.Console::WriteLine");
 return sig;
}

MethodSignature* CreateSignatureStringLength(PELib& libEntry)
{
 // callvirt instance int32 [mscorlib]System.String::get_Length()
 MethodSignature *sig = libEntry.AllocateMethodSignature
          ("get_Length", MethodSignature::Instance, nullptr);
 sig->ReturnType(libEntry.AllocateType(Type::i32, 0));
 sig->FullName("[mscorlib]System.String::get_Length");
 return sig;
}

MethodSignature* CreateSignatureSubString(PELib& libEntry)
{
 // callvirt instance string[mscorlib]System.String::Substring(int32, int32)
 MethodSignature *sig = libEntry.AllocateMethodSignature
        ("Substring", MethodSignature::Instance, nullptr);
 sig->ReturnType(libEntry.AllocateType(Type::string, 0));
 sig->AddParam(libEntry.AllocateParam
 ("a_iStart", libEntry.AllocateType(Type::i32, 0)));
 sig->AddParam(libEntry.AllocateParam
 ("a_iEnd", libEntry.AllocateType(Type::i32, 0)));
 sig->FullName("[mscorlib]System.String::Substring");
 return sig;
}

MethodSignature* CreateSignatureReadLineMethod(PELib& libEntry)
{
 // Create the signature of "System.Console::ReadLine" method
 MethodSignature *sig = libEntry.AllocateMethodSignature("ReadLine", 0, nullptr);
 sig->ReturnType(libEntry.AllocateType(Type::string, 0));
 sig->FullName("[mscorlib]System.Console::ReadLine");
 return sig;
}

MethodSignature* CreateSignatureConverToIn32Method(PELib& libEntry)
{
 // Create the signature of "System.Console::ReadLine" method
 MethodSignature *sig = libEntry.AllocateMethodSignature("ToInt32", 0, nullptr);
 sig->ReturnType(libEntry.AllocateType(Type::i32, 0));
 sig->AddParam(libEntry.AllocateParam
 ("c_nToConvert", libEntry.AllocateType(Type::string, 0)));
 sig->FullName("[mscorlib]System.Convert::ToInt32");
 return sig;
}

At the moment, while it is not possible to read .NET assemblies to optimize the method call process, there is a simple way to call methods, the developer must give the full name of the method, as you can see in the source, considering the name of lib. it belongs, namespace and class.

Calling a .NET Method

Let's build a simple example (let's consider the C# program as a base example):

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            var hello = "Hello World!";
            Console.WriteLine(hello);
            Console.WriteLine(hello.Length.ToString());
            Console.WriteLine(hello.Substring(0, 4));
        }
    }
}

So, there is a way to generate this kind of code with DotNetPeLib, let's build the code...

#include <iostream>
#include "Signatures.h"

//.class private auto ansi beforefieldinit ConsoleApp.Program
//    extends [mscorlib]System.Object
//{
// .method private hidebysig static void Main () cil managed
// {
//     .entrypoint
//     .locals init (
//         [0] int32 V_0
//     )

//     IL_0000: ldstr "Hello World!"
//     IL_0005: dup
//     IL_0006: call void [mscorlib]System.Console::WriteLine(string)
//     IL_000b: dup
//     IL_000c: callvirt instance int32 [mscorlib]System.String::get_Length()
//     IL_0011: stloc.0
//     IL_0012: ldloca.s V_0
//     IL_0014: call instance string [mscorlib]System.Int32::ToString()
//     IL_0019: call void [mscorlib]System.Console::WriteLine(string)
//     IL_001e: ldc.i4.0
//     IL_001f: ldc.i4.4
//     IL_0020: callvirt instance string [mscorlib]System.String::Substring(int32,  int32)
//     IL_0025: call void [mscorlib]System.Console::WriteLine(string)
//     IL_002a: ret
// }
//}
void main()
{
 // Create a instance of PELib with assembly name and the CorFlags
 PELib libEntry("test2", PELib::ilonly | PELib::bits32);

 // Get the context of current assemblie working
 DataContainer* working = libEntry.WorkingAssembly();

 // add a reference to the assembly
 libEntry.AddExternalAssembly("mscorlib");

 // Create the namespace
 Namespace* nameSpace = libEntry.AllocateNamespace("ConsoleApp");
 working->Add(nameSpace); // Add the new namespace at the assemblie

 // Create the class
 Class* cls = libEntry.AllocateClass("Program", Qualifiers::Ansi, -1, -1);
 nameSpace->Add(cls); // Add the new class to the namespace

 // Create the signature of main method
 MethodSignature* sig = 
   libEntry.AllocateMethodSignature("Main", MethodSignature::Managed, cls);
 sig->ReturnType(libEntry.AllocateType(Type::Void, 0)); // Set the return type of method

 // Create the main method
 Method* main = libEntry.AllocateMethod(sig, Qualifiers::Private | 
 Qualifiers::HideBySig | Qualifiers::Static | Qualifiers::CIL | Qualifiers::Managed, true);

 // Create a local variable to store the lenght of string
 Local* strSize = libEntry.AllocateLocal("i", libEntry.AllocateType(Type::i32, 0));
 main->AddLocal(strSize); // Add the variable to the method
 Operand* strSizeOperand = 
 libEntry.AllocateOperand(strSize); // Create the operand of string size variable

 // Create the assignatures
 MethodSignature* writeLineMethod = CreateSignatureWriteLineMethod(libEntry);
 MethodSignature* toStringSiganature = CreateSignatureForToStringMethod(libEntry);
 MethodSignature* stringLengthSignature = CreateSignatureStringLength(libEntry);
 MethodSignature* substringSignature = CreateSignatureSubString(libEntry);

 // Alocate the string
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldstr, libEntry.AllocateOperand("Hello World!", true)));
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_dup)); // copy the value to stack

 // Call the WriteLineMethod
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_call, 
 libEntry.AllocateOperand(libEntry.AllocateMethodName(writeLineMethod))));
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_dup)); // copy the value to stack

 // Call the get_length() method from instancied string
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_callvirt, 
 libEntry.AllocateOperand(libEntry.AllocateMethodName(stringLengthSignature))));

 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_stloc_0)); // Put result of get_length() at local variable
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldloca_s, strSizeOperand)); // load the value of local variable

 // Call the toString method
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_call, libEntry.AllocateOperand
                       (libEntry.AllocateMethodName(toStringSiganature))));
 
 // Call the WriteLine method
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_call, 
 libEntry.AllocateOperand(libEntry.AllocateMethodName(writeLineMethod))));
 
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldc_i4_0));   // put on stack the start index of substring
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldc_i4_5)); // put on stack the end index of substring
 
 // Call the substring() method from instancied string
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_callvirt, 
 libEntry.AllocateOperand(libEntry.AllocateMethodName(substringSignature))));
 
 // Call the WriteLine method
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_call, 
 libEntry.AllocateOperand(libEntry.AllocateMethodName(writeLineMethod))));
 
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_ret)); // Return

 try
 {
  main->Optimize(libEntry);
 }
 catch (PELibError exc)
 {
  std::cout << "Optimizer error: " << exc.what() << std::endl;
 }
 cls->Add(main);

 libEntry.DumpOutputFile("test2.il", PELib::ilasm, false);  // Generate the MSIL code
 libEntry.DumpOutputFile("test2.exe", PELib::peexe, false); // Generate the .Net Executable
}

Creating and Calling a Method

In this example, we will create a simple method that receives an integer and the method will format a string with this number, so, the base program is:

class Program
{
    static string FormatString(int age)
    {
        string nAge = age.ToString();
        return string.Format("Your age is {0}", age);
    }

    static void Main()
    {
        Console.WriteLine("Enter with your age:");
        int age = Convert.ToInt32(Console.ReadLine());
        Console.WriteLine(FormatString(age));
    }
}

The code to generate a program than do the same thing is:

#include <iostream>
#include "Signatures.h"

//.method private hidebysig static string FormatString (
//        int32 age
//    ) cil managed
//{
//    .locals init (
//        [0] string iString
//    )
//    IL_0000: ldarga.s age
//    IL_0002: call instance string [mscorlib]System.Int32::ToString()
//    IL_0007: stloc.0
//    IL_0008: ldstr "Your age is {0}"
//    IL_000d: ldloc.0
//    IL_000e: call string [mscorlib]System.String::Format(string,  object)
//    IL_0013: ret
//}
MethodSignature* CreateFormatStringMethod(PELib& libEntry, Class* cls)
{
 // Create the signature of main method
 MethodSignature* sig = libEntry.AllocateMethodSignature
 ("FormatString", MethodSignature::Managed, cls);
 sig->ReturnType(libEntry.AllocateType
 (Type::string, 0)); // Set the return type of method

                // Create a parameter
 Param* parames1 = libEntry.AllocateParam
 ("age", libEntry.AllocateType(Type::i32, 0));
 sig->AddParam(parames1);

 // Create the FormatString method
 Method* formatString = libEntry.AllocateMethod(sig, Qualifiers::Private | 
 Qualifiers::HideBySig | Qualifiers::Static | Qualifiers::CIL | Qualifiers::Managed, false);

 // Create the local variable "iString"
 Local* iStringLocal = libEntry.AllocateLocal
 ("iString", libEntry.AllocateType(Type::string, 0));
 formatString->AddLocal(iStringLocal); // Add the variable to the method

 // Create the operand of local variable
 Operand* iStringOperand = libEntry.AllocateOperand(iStringLocal);

 // Load the argument "i"
 formatString->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldarga_s, libEntry.AllocateOperand(parames1)));

 // Call the toString method
 MethodSignature* toStringSiganature = CreateSignatureForToStringMethod(libEntry);
 formatString->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_call, libEntry.AllocateOperand
 (libEntry.AllocateMethodName(toStringSiganature))));

 // Put return value onto stack
 formatString->AddInstruction
 (libEntry.AllocateInstruction(Instruction::i_stloc, iStringOperand));

 // Put the string in stack
 formatString->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldstr, libEntry.AllocateOperand("Your age is {0}", true)));

 // Load the top element of stack
 formatString->AddInstruction(libEntry.AllocateInstruction
                             (Instruction::i_ldloc, iStringOperand));

 // Call the format string method
 MethodSignature* formatStringMethod = CreateSignatureForToStringFormatMethod(libEntry);
 formatString->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_call, libEntry.AllocateOperand
                       (libEntry.AllocateMethodName(formatStringMethod))));

 // Return
 formatString->AddInstruction(libEntry.AllocateInstruction(Instruction::i_ret));

 try
 {
  formatString->Optimize(libEntry);
 }
 catch (PELibError exc)
 {
  std::cout << "Optimizer error: " << exc.what() << std::endl;
 }
 cls->Add(formatString);
 return sig;
}

//.method private hidebysig static void Main () cil managed
//{
//    .entrypoint
//    IL_0000: ldstr "Enter with your age:"
//    IL_0005: call void [mscorlib]System.Console::WriteLine(string)
//    IL_000a: call string [mscorlib]System.Console::ReadLine()
//    IL_000f: call int32 [mscorlib]System.Convert::ToInt32(string)
//    IL_0014: call string ConsoleApp1.Program::FormatString(int32)
//    IL_0019: call void [mscorlib]System.Console::WriteLine(string)
//    IL_001e: ret
//}
void CreateMainMethod(PELib& libEntry, Class* cls, MethodSignature* myFormatStringMethod)
{
 // Create the signature of main method
 MethodSignature* sig = libEntry.AllocateMethodSignature
                        ("Main", MethodSignature::Managed, cls);
 sig->ReturnType(libEntry.AllocateType(Type::Void, 0)); // Set the return type of method

 // Create the main method
 Method* main = libEntry.AllocateMethod(sig, Qualifiers::Private | 
 Qualifiers::HideBySig | Qualifiers::Static | Qualifiers::CIL | Qualifiers::Managed, true);

 // Create the signatures
 MethodSignature* writeMethodSignature = CreateSignatureWriteMethod(libEntry);
 MethodSignature* writeLineMethodSignature = CreateSignatureWriteLineMethod(libEntry);
 MethodSignature* toStringSiganature = CreateSignatureForToStringMethod(libEntry);
 MethodSignature* stringLengthSignature = CreateSignatureStringLength(libEntry);
 MethodSignature* substringSignature = CreateSignatureSubString(libEntry);
 MethodSignature* convertToIn32Signature = CreateSignatureConverToIn32Method(libEntry);
 MethodSignature* readLineSignature = CreateSignatureReadLineMethod(libEntry);

 // Instructions
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldstr, libEntry.AllocateOperand("Enter with your age:", true)));
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_call, libEntry.AllocateOperand
 (libEntry.AllocateMethodName(writeMethodSignature))));
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_call, libEntry.AllocateOperand
 (libEntry.AllocateMethodName(readLineSignature))));
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_call, libEntry.AllocateOperand
 (libEntry.AllocateMethodName(convertToIn32Signature))));
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_call, libEntry.AllocateOperand
 (libEntry.AllocateMethodName(myFormatStringMethod))));
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_call, libEntry.AllocateOperand
 (libEntry.AllocateMethodName(writeLineMethodSignature))));
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_ret));

 try
 {
  main->Optimize(libEntry);
 }
 catch (PELibError exc)
 {
  std::cout << "Optimizer error: " << exc.what() << std::endl;
 }
 cls->Add(main);
}

void main()
{
 // Create a instance of PELib with assembly name and the CorFlags
 PELib libEntry("test3", PELib::ilonly | PELib::bits32);

 // Get the context of current assemblie working
 DataContainer* working = libEntry.WorkingAssembly();

 // add a reference to the assembly
 libEntry.AddExternalAssembly("mscorlib");

 // Create the namespace
 Namespace* nameSpace = libEntry.AllocateNamespace("ConsoleApp");
 working->Add(nameSpace); // Add the new namespace at the assemblie

 // Create the class
 Class* cls = libEntry.AllocateClass("Program", Qualifiers::Ansi, -1, -1);
 nameSpace->Add(cls);     // Add the new class to the namespace

 MethodSignature* myFormatString = CreateFormatStringMethod(libEntry, cls);
 CreateMainMethod(libEntry, cls, myFormatString);

 libEntry.DumpOutputFile("test3.il", PELib::ilasm, false);  // Generate the MSIL code
 libEntry.DumpOutputFile("test3.exe", PELib::peexe, false); // Generate the .NET Executable
}

Creating a Loop

In this example, we will re-use the last example, we will just add a code to loop N times and format a string to show the index position.

The base program for this example is:

namespace ConsoleApp
{
    class Program
    {
        static string FormatString(int i)
        {
            string iString = i.ToString();
            return string.Format("The value of index is: {0}", iString);
        }

        static void Main()
        {
            for(int i=0;i<100;i++)
                Console.WriteLine(FormatString(i));
        }
    }
}
#include <iostream>
#include "Signatures.h"

//.method private hidebysig static string FormatString (
//        int32 i
//    ) cil managed
//{
//    .locals init (
//        [0] string iString
//    )
//    IL_0000: ldarga.s i
//    IL_0002: call instance string [mscorlib]System.Int32::ToString()
//    IL_0007: stloc.0
//    IL_0008: ldstr "The parameter I is equals: {0}"
//    IL_000d: ldloc.0
//    IL_000e: call string [mscorlib]System.String::Format(string,  object)
//    IL_0013: ret
//}
MethodSignature* CreateMethod_FormatString(PELib& libEntry, Class* cls)
{
 // Create the signature of main method
 MethodSignature* sig = libEntry.AllocateMethodSignature
 ("FormatString", MethodSignature::Managed, cls);
 sig->ReturnType(libEntry.AllocateType(Type::string, 0)); // Set the return type of method

 // Create a parameter
 Param* parames1 = libEntry.AllocateParam("i", libEntry.AllocateType(Type::i32, 0));
 sig->AddParam(parames1);

 // Create the FormatString method
 Method* formatString = libEntry.AllocateMethod(sig, Qualifiers::Private | 
 Qualifiers::HideBySig | Qualifiers::Static | Qualifiers::CIL | Qualifiers::Managed, false);

 // Create the local variable "iString"
 Local* iStringLocal = libEntry.AllocateLocal
 ("iString", libEntry.AllocateType(Type::string, 0));
 formatString->AddLocal(iStringLocal); // Add the variable to the method

 // Create the operand of local variable
 Operand* iStringOperand = libEntry.AllocateOperand(iStringLocal);

 // Load the argument "i"
 formatString->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldarga_s, libEntry.AllocateOperand(parames1)));

 // Call the toString method
 MethodSignature* toStringSiganature = CreateSignatureForToStringMethod(libEntry);
 formatString->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_call, libEntry.AllocateOperand
                       (libEntry.AllocateMethodName(toStringSiganature))));

 // Put return value onto stack
 formatString->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_stloc, iStringOperand));

 // Put the string in stack
 formatString->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldstr, libEntry.AllocateOperand
 ("The parameter I is equals: {0}", true))); // Put the string on stack

// Load the top element of stack
 formatString->AddInstruction(libEntry.AllocateInstruction
                             (Instruction::i_ldloc, iStringOperand));

 // Call the format string method
 MethodSignature* formatStringMethod = CreateSignatureForToStringFormatMethod(libEntry);
 formatString->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_call, libEntry.AllocateOperand
                       (libEntry.AllocateMethodName(formatStringMethod))));

 // Return
 formatString->AddInstruction(libEntry.AllocateInstruction(Instruction::i_ret));

 try
 {
  formatString->Optimize(libEntry);
 }
 catch (PELibError exc)
 {
  std::cout << "Optimizer error: " << exc.what() << std::endl;
 }
 cls->Add(formatString);
 return sig;
}

//.method private hidebysig static void Main() cil managed
//{
// .entrypoint
//  .locals init(
//   [0] int32 i
//  )
//
//  IL_0000: ldc.i4.0
//  IL_0001 : stloc.0
//  IL_0002 : br.s IL_0013
//  .loop
//  {
//   IL_0004: ldloc.0
//   IL_0005 : call string ConsoleApp1.Program::FormatString(int32)
//   IL_000a : call void[mscorlib]System.Console::WriteLine(string)
//   IL_000f : ldloc.0
//   IL_0010 : ldc.i4.1
//   IL_0011 : add
//   IL_0012 : stloc.0
//
//   IL_0013 : ldloc.0
//   IL_0014 : ldc.i4.s 100
//   IL_0016 : blt.s IL_0004
//  }
//  IL_0018: ret
//}
void CreateMethod_Main(PELib& libEntry, Class* cls, MethodSignature* formatStringMethod)
{
 // Create the signature of main method
 MethodSignature* sig = libEntry.AllocateMethodSignature
                        ("Main", MethodSignature::Managed, cls);
 sig->ReturnType(libEntry.AllocateType(Type::Void, 0)); // Set the return type of method

 // Create the main method
 Method* main = libEntry.AllocateMethod(sig, Qualifiers::Private | 
 Qualifiers::HideBySig | Qualifiers::Static | Qualifiers::CIL | Qualifiers::Managed, true);

 // Create the local variable "iString"
 Local* indexCnt = libEntry.AllocateLocal("i", libEntry.AllocateType(Type::i32, 0));
 main->AddLocal(indexCnt); // Add the variable to the method

 // Create the operand of local variable
 Operand* iStringOperand = libEntry.AllocateOperand(indexCnt);

 // Create loops
 Operand* loopLabel = libEntry.AllocateOperand("loop");
 Operand* loopLabel1 = libEntry.AllocateOperand("loop1");

 // Create the signatures
 MethodSignature* writeLineSignature = CreateSignatureWriteLineMethod(libEntry);

 // Create the instructions
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_ldc_i4_0));
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_stloc_0));
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_br_s, loopLabel1));
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_label, loopLabel));
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_ldloc_0));
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_call, 
 libEntry.AllocateOperand(libEntry.AllocateMethodName(formatStringMethod))));
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_call, 
 libEntry.AllocateOperand(libEntry.AllocateMethodName(writeLineSignature))));
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldloc_0)); // load the variable 'i'
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldc_i4_1)); // Put the value 1 on top of stack
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_add)); // Add
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_stloc_0)); // put the result on stack
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_label, loopLabel1)); // Loop
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldloc_0)); // load the variable 'i'
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_ldc_i4_s, libEntry.AllocateOperand(100, Operand::i32)));
 main->AddInstruction(libEntry.AllocateInstruction
 (Instruction::i_blt_s, loopLabel)); // loop case it's not equals
 main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_ret)); // exit

 try
 {
  main->Optimize(libEntry);
 }
 catch (PELibError exc)
 {
  std::cout << "Optimizer error: " << exc.what() << std::endl;
 }
 cls->Add(main);
}

//.class private auto ansi beforefieldinit ConsoleApp.Program
//    extends [mscorlib]System.Object
//{
// ...
//}
void main()
{
 // Create a instance of PELib with assembly name and the CorFlags
 PELib libEntry("test4", PELib::ilonly | PELib::bits32);

 // Get the context of current assemblie working
 DataContainer* working = libEntry.WorkingAssembly();

 // add a reference to the assembly
 libEntry.AddExternalAssembly("mscorlib");

 // Create the namespace
 Namespace* nameSpace = libEntry.AllocateNamespace("ConsoleApp");
 working->Add(nameSpace); // Add the new namespace at the assembly

 // Create the class
 Class* cls = libEntry.AllocateClass("Program", Qualifiers::Ansi, -1, -1);
 nameSpace->Add(cls); // Add the new class to the namespace

 MethodSignature* formatString = CreateMethod_FormatString(libEntry, cls);
 CreateMethod_Main(libEntry, cls, formatString);

 libEntry.DumpOutputFile("test4.il", PELib::ilasm, false);  // Generate the MSIL code
 libEntry.DumpOutputFile("test4.exe", PELib::peexe, false); // Generate the .NET Executable
}

What Is Not Implemented Yet

  1. Code generation for deconstructors
  2. Code generation for override methods

Organization of Project in Zip

All the tests are inside of 'test' folder in VS solution.

Any Questions?

Feel free to ask! :)

History

  • 11th February, 2017: Article created

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here