Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / ASM

Compiling Your C Code to .NET - Part 2

5.00/5 (10 votes)
14 Dec 2019CPOL2 min read 21K   677  
With this new OrangeC/C++ compiler back-end, you can compile your C code to .NET.

Articles about OrangeC to .NET

Table of Contents

Introduction

To allow the support of .NET methods, classes, properties, enums .... has been implemented in OrangeC, a support for C# like language, for example:

C++
int a = 10;
printf("%s", a.ToString());

Another feature, that came from C++, has been enabled to use is the "using namespace", for example:

C++
using namespace System;
using namespace System::IO;

So, let's look how to use those new features of OrangeC compiler to .NET.

Using .NET Types

The .NET types are nothing more than .NET classes, so, with OrangeC, you can use these classes and methods as normal types in C. To make it more simple to understand, let's build a small example, using a .NET string class:

C++
int main()
{
  System::String str("Hello World!!");
  return 0;
}

With this code, we created an instance of class String, for convenience, was implementing inside of compiler, some keywords to handle the most common classes of .NET, that is:

  1. Object
  2. String

So, to use these types using the native keyword, we have:

  1. __object
  2. __string

Let's look at a small example:

C++
int main()
{
  __string str("Hello World!!");
  __object obj = (__object)str;

  printf("%s\n", str);
  printf("%s\n", (__string)obj);
  return 0;
}

Let's look at one more of using .NET types example:

C++
int main()
{
  System::DateTime dt(2017,10,10);
  return 0;
}

Managed and Unmanaged Arrays

To avoid problems of compatibility between legacy code and new codes, a syntactic variation was implemented to differentiate between arrays types, so, to create a managed array, we use the [] before the variable name, and, to create an unmanaged array, we use the [] after the variable name. To make it more clear, let's make a small example:

C++
int main()
{                         
  int []a = { 0, 1, 2, 3, 4, 5 };   // Managed array
  int b[] = { 0, 1, 2, 3, 4, 5 };   // Unmanaged array
 
  return 0;
}

Maybe you are thinking, what exactly is the difference between the managed and unmanaged array?
So, to answer this, let's look at the generated code to each kind of array (I have used a decompiler to get this code):

C#
public static unsafe int main(int argc, void* argv)
{
    // The managed array
    int[] a = new int[] { 0, 1, 2, 3, 4, 5 };

    // The unmanaged array
    int32[24] b = null;
    *(&b + 4) = 1;
    *(&b + 8) = 2;
    *(&b + 12) = 3;
    *(&b + 16) = 4;
    *(&b + 20) = 5;
    return 0;
}

The navigation into array is still the same:

C++
int main()
{                         
  int []a = { 0, 1, 2, 3, 4, 5 }; // Managed array
  int b[] = { 0, 1, 2, 3, 4, 5 }; // Unmanaged array 
 
  printf("Printing all elements of managed array: ");
  for(int i=0;i<a.Length;i++) // Look at the usage of property "Length"
  {
    if(i != 0)
      printf(", ");
    printf("%d", a[i]);
  }
  printf("\n");
 
  printf("Printing all elements of unmanaged array: ");
  for(int i=0;i<sizeof(b)/sizeof(int);i++)
  {
    if(i != 0)
      printf(", ");
    printf("%d", a[i]);
  }
 
  printf("\n");
  return 0;
}

Using .NET Methods

The usage of .NET methods is like C#:

  • <Instance>.<Method>([args]);

The difference is with enums and static class

  • [Namespace]::<Static class>::<Method | Property | Enum>[ ( [args] ) ];

Let's look at one single example, that shows these cases:

C++
#include <stdio.h>
#include <stdlib.h>

using namespace System;
using namespace System::Runtime;
using namespace System::Reflection;
using namespace System::Reflection::Emit;

int main()
{
  AppDomain ad = AppDomain::CurrentDomain;
  AssemblyName am("TestAsm");
                                                 
  AssemblyBuilder ab = ad.DefineDynamicAssembly(am, AssemblyBuilderAccess::Save);
  ModuleBuilder mb = ab.DefineDynamicModule("testmod", "TestAsm.exe");
  TypeBuilder tb = mb.DefineType("mytype", TypeAttributes::Public);
  MethodBuilder metb = tb.DefineMethod("hi", MethodAttributes::Public | 
                                        MethodAttributes::Static, NULL, NULL);
  ab.SetEntryPoint(metb);
    
  ILGenerator il = metb.GetILGenerator(); 
  il.EmitWriteLine("Hello World");
  il.Emit(OpCodes::Ret);
  tb.CreateType();
  ab.Save("TestAsm.exe");

  Console::WriteLine("TestAsm.exe created!");

  return 0;
}

Let's build one more example, implementing a function that receives a .NET type as parameter and uses some sophisticated methods call:

C++
#include <stdio.h>

void test(System::DateTime dt, System::String &str)
{
  str = dt.ToString("dd/MM/yyyy");
}

int main()
{                         
  __string str;
  System::DateTime dt1 = System::DateTime::Today;
  System::DateTime dt2 = dt1.AddDays(30);
 
  str =  dt1.ToString("dd,MM,yyyy") + "/" + dt2.ToString("dd.MM.yy");
 
  __string part1 = str.Substring(0, str.IndexOf("/"));
  __string part2 = str.Substring(str.IndexOf("/") + 1);
 
  str = str.Replace(",", "").Replace("/", "").Replace("10", "Hi!");
 
  printf("Part1 = %s\nPart2 = %s\nNew string = %s\n", part1, part2, str);
 
  test(dt1, str);
 
  printf("%s\n", str);  
 
  return 0;
}

Finally, we will build a mix of managed and unmanaged code, for this, we will build a linked list, where the struct of linked list hold managed types:

C++
#include <stdio.h>
#include <stdlib.h>

int getAge(System::DateTime reference, System::DateTime birthday);

typedef struct node
{
  __string name;
  System::DateTime bornDate;
 
  struct node* next;
} node_t;

void push(node_t* head, __string name, System::DateTime born)
{
    node_t* current = head;
    while (current->next != NULL)
        current = current->next;
       
    current->next = (node_t*)malloc(sizeof(node_t));
    current->next->name = name;
    current->next->bornDate = born;
    current->next->next = NULL;
}

void print_list(node_t * head) {
    node_t * current = head;

    System::DateTime today = System::DateTime::Today;
    while (current != NULL) {
        System::Console::WriteLine(((__string)"").PadLeft(80, System::Convert::ToChar("-")));
        System::Console::WriteLine("Name: {0}\nBorn date: {1}\nAge: {2}\n", 
        current->name, current->bornDate.ToString("dd/MM/yyyy"), getAge(today, current->bornDate));
        current = current->next;
    }
}

int getAge(System::DateTime reference, System::DateTime birthday)
{
  int age = reference.Year - birthday.Year;
  if(System::DateTime::op_LessThan(reference, birthday.AddYears(age)))
    age--;
    
  return age;
}

int main()
{            
  node_t* head = (node_t*)malloc(sizeof(node_t));
  head->name = "Alexandre";
  head->bornDate = System::DateTime::Today;
  head->next = NULL;
 
  System::DateTime today = System::DateTime::Today;
  for(int i=0;i<15;i++)
    push(head, "Teste", today.AddDays(i + 1).AddYears(-24));   
    
  print_list(head);
      
  return 0;
}

Using Try / Catch / Finally

Exception systems greatly help the developer to handle errors so that C code becomes more compatible with the .NET error system and exception system, we implement support for try / catch / finally, the usage of these commands like in C#, so, let's take a look:

C++
using namespace System;
using namespace System::IO;

int main()
{
  __try
  {
    File.Open("some file that doesn't exist", FileMode::Open);
  }
  __catch (Exception e)
  {
    Console::WriteLine(e.Message);
  }
  __finally
  {
    printf("Finally!");
  }
}

History

  • 12-Oct-2017: Article created

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)