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






4.95/5 (49 votes)
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)
- Official site: http://ladsoft.tripod.com/index.html
- OrangeC/C++ compiler and tool chain on github: https://github.com/LADSoft/OrangeC
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
}
}
}
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
- Code generation for deconstructors
- 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