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()
{
PELib libEntry("test1", PELib::ilonly | PELib::bits32);
DataContainer* working = libEntry.WorkingAssembly();
Namespace* nameSpace = libEntry.AllocateNamespace("ConsoleApp");
working->Add(nameSpace);
Class* cls = libEntry.AllocateClass("Program", Qualifiers::Ansi, -1, -1);
nameSpace->Add(cls);
MethodSignature* sig = libEntry.AllocateMethodSignature("Main", MethodSignature::Managed, cls);
sig->ReturnType(libEntry.AllocateType(Type::i32, 0));
Method* main = libEntry.AllocateMethod(sig, Qualifiers::Private |
Qualifiers::HideBySig | Qualifiers::Static, true);
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
{
main->Optimize(libEntry);
}
catch (PELibError exc)
{
std::cout << "Optimizer error: " << exc.what() << std::endl;
}
cls->Add(main);
libEntry.DumpOutputFile("test1.il", PELib::ilasm, false);
libEntry.DumpOutputFile("test1.exe", PELib::peexe, false);
}
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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"
void main()
{
PELib libEntry("test2", PELib::ilonly | PELib::bits32);
DataContainer* working = libEntry.WorkingAssembly();
libEntry.AddExternalAssembly("mscorlib");
Namespace* nameSpace = libEntry.AllocateNamespace("ConsoleApp");
working->Add(nameSpace);
Class* cls = libEntry.AllocateClass("Program", Qualifiers::Ansi, -1, -1);
nameSpace->Add(cls);
MethodSignature* sig =
libEntry.AllocateMethodSignature("Main", MethodSignature::Managed, cls);
sig->ReturnType(libEntry.AllocateType(Type::Void, 0));
Method* main = libEntry.AllocateMethod(sig, Qualifiers::Private |
Qualifiers::HideBySig | Qualifiers::Static | Qualifiers::CIL | Qualifiers::Managed, true);
Local* strSize = libEntry.AllocateLocal("i", libEntry.AllocateType(Type::i32, 0));
main->AddLocal(strSize);
Operand* strSizeOperand =
libEntry.AllocateOperand(strSize);
MethodSignature* writeLineMethod = CreateSignatureWriteLineMethod(libEntry);
MethodSignature* toStringSiganature = CreateSignatureForToStringMethod(libEntry);
MethodSignature* stringLengthSignature = CreateSignatureStringLength(libEntry);
MethodSignature* substringSignature = CreateSignatureSubString(libEntry);
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldstr, libEntry.AllocateOperand("Hello World!", true)));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_dup));
main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_call,
libEntry.AllocateOperand(libEntry.AllocateMethodName(writeLineMethod))));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_dup));
main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_callvirt,
libEntry.AllocateOperand(libEntry.AllocateMethodName(stringLengthSignature))));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_stloc_0));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldloca_s, strSizeOperand));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_call, libEntry.AllocateOperand
(libEntry.AllocateMethodName(toStringSiganature))));
main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_call,
libEntry.AllocateOperand(libEntry.AllocateMethodName(writeLineMethod))));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldc_i4_0));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldc_i4_5));
main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_callvirt,
libEntry.AllocateOperand(libEntry.AllocateMethodName(substringSignature))));
main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_call,
libEntry.AllocateOperand(libEntry.AllocateMethodName(writeLineMethod))));
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);
libEntry.DumpOutputFile("test2.il", PELib::ilasm, false);
libEntry.DumpOutputFile("test2.exe", PELib::peexe, false);
}
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"
MethodSignature* CreateFormatStringMethod(PELib& libEntry, Class* cls)
{
MethodSignature* sig = libEntry.AllocateMethodSignature
("FormatString", MethodSignature::Managed, cls);
sig->ReturnType(libEntry.AllocateType
(Type::string, 0));
Param* parames1 = libEntry.AllocateParam
("age", libEntry.AllocateType(Type::i32, 0));
sig->AddParam(parames1);
Method* formatString = libEntry.AllocateMethod(sig, Qualifiers::Private |
Qualifiers::HideBySig | Qualifiers::Static | Qualifiers::CIL | Qualifiers::Managed, false);
Local* iStringLocal = libEntry.AllocateLocal
("iString", libEntry.AllocateType(Type::string, 0));
formatString->AddLocal(iStringLocal);
Operand* iStringOperand = libEntry.AllocateOperand(iStringLocal);
formatString->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldarga_s, libEntry.AllocateOperand(parames1)));
MethodSignature* toStringSiganature = CreateSignatureForToStringMethod(libEntry);
formatString->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_call, libEntry.AllocateOperand
(libEntry.AllocateMethodName(toStringSiganature))));
formatString->AddInstruction
(libEntry.AllocateInstruction(Instruction::i_stloc, iStringOperand));
formatString->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldstr, libEntry.AllocateOperand("Your age is {0}", true)));
formatString->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldloc, iStringOperand));
MethodSignature* formatStringMethod = CreateSignatureForToStringFormatMethod(libEntry);
formatString->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_call, libEntry.AllocateOperand
(libEntry.AllocateMethodName(formatStringMethod))));
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;
}
void CreateMainMethod(PELib& libEntry, Class* cls, MethodSignature* myFormatStringMethod)
{
MethodSignature* sig = libEntry.AllocateMethodSignature
("Main", MethodSignature::Managed, cls);
sig->ReturnType(libEntry.AllocateType(Type::Void, 0));
Method* main = libEntry.AllocateMethod(sig, Qualifiers::Private |
Qualifiers::HideBySig | Qualifiers::Static | Qualifiers::CIL | Qualifiers::Managed, true);
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);
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()
{
PELib libEntry("test3", PELib::ilonly | PELib::bits32);
DataContainer* working = libEntry.WorkingAssembly();
libEntry.AddExternalAssembly("mscorlib");
Namespace* nameSpace = libEntry.AllocateNamespace("ConsoleApp");
working->Add(nameSpace);
Class* cls = libEntry.AllocateClass("Program", Qualifiers::Ansi, -1, -1);
nameSpace->Add(cls);
MethodSignature* myFormatString = CreateFormatStringMethod(libEntry, cls);
CreateMainMethod(libEntry, cls, myFormatString);
libEntry.DumpOutputFile("test3.il", PELib::ilasm, false);
libEntry.DumpOutputFile("test3.exe", PELib::peexe, false);
}
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"
MethodSignature* CreateMethod_FormatString(PELib& libEntry, Class* cls)
{
MethodSignature* sig = libEntry.AllocateMethodSignature
("FormatString", MethodSignature::Managed, cls);
sig->ReturnType(libEntry.AllocateType(Type::string, 0));
Param* parames1 = libEntry.AllocateParam("i", libEntry.AllocateType(Type::i32, 0));
sig->AddParam(parames1);
Method* formatString = libEntry.AllocateMethod(sig, Qualifiers::Private |
Qualifiers::HideBySig | Qualifiers::Static | Qualifiers::CIL | Qualifiers::Managed, false);
Local* iStringLocal = libEntry.AllocateLocal
("iString", libEntry.AllocateType(Type::string, 0));
formatString->AddLocal(iStringLocal);
Operand* iStringOperand = libEntry.AllocateOperand(iStringLocal);
formatString->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldarga_s, libEntry.AllocateOperand(parames1)));
MethodSignature* toStringSiganature = CreateSignatureForToStringMethod(libEntry);
formatString->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_call, libEntry.AllocateOperand
(libEntry.AllocateMethodName(toStringSiganature))));
formatString->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_stloc, iStringOperand));
formatString->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldstr, libEntry.AllocateOperand
("The parameter I is equals: {0}", true)));
formatString->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldloc, iStringOperand));
MethodSignature* formatStringMethod = CreateSignatureForToStringFormatMethod(libEntry);
formatString->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_call, libEntry.AllocateOperand
(libEntry.AllocateMethodName(formatStringMethod))));
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;
}
void CreateMethod_Main(PELib& libEntry, Class* cls, MethodSignature* formatStringMethod)
{
MethodSignature* sig = libEntry.AllocateMethodSignature
("Main", MethodSignature::Managed, cls);
sig->ReturnType(libEntry.AllocateType(Type::Void, 0));
Method* main = libEntry.AllocateMethod(sig, Qualifiers::Private |
Qualifiers::HideBySig | Qualifiers::Static | Qualifiers::CIL | Qualifiers::Managed, true);
Local* indexCnt = libEntry.AllocateLocal("i", libEntry.AllocateType(Type::i32, 0));
main->AddLocal(indexCnt);
Operand* iStringOperand = libEntry.AllocateOperand(indexCnt);
Operand* loopLabel = libEntry.AllocateOperand("loop");
Operand* loopLabel1 = libEntry.AllocateOperand("loop1");
MethodSignature* writeLineSignature = CreateSignatureWriteLineMethod(libEntry);
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));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldc_i4_1));
main->AddInstruction(libEntry.AllocateInstruction(Instruction::i_add));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_stloc_0));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_label, loopLabel1));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldloc_0));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_ldc_i4_s, libEntry.AllocateOperand(100, Operand::i32)));
main->AddInstruction(libEntry.AllocateInstruction
(Instruction::i_blt_s, loopLabel));
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()
{
PELib libEntry("test4", PELib::ilonly | PELib::bits32);
DataContainer* working = libEntry.WorkingAssembly();
libEntry.AddExternalAssembly("mscorlib");
Namespace* nameSpace = libEntry.AllocateNamespace("ConsoleApp");
working->Add(nameSpace);
Class* cls = libEntry.AllocateClass("Program", Qualifiers::Ansi, -1, -1);
nameSpace->Add(cls);
MethodSignature* formatString = CreateMethod_FormatString(libEntry, cls);
CreateMethod_Main(libEntry, cls, formatString);
libEntry.DumpOutputFile("test4.il", PELib::ilasm, false);
libEntry.DumpOutputFile("test4.exe", PELib::peexe, false);
}
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