65.9K
CodeProject 正在变化。 阅读更多。
Home

将你的 C 代码编译到 .NET - 第 2 部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (10投票s)

2017年10月12日

CPOL

2分钟阅读

viewsIcon

21889

downloadIcon

706

有了这个新的 OrangeC/C++ 编译器后端,你可以将你的 C 代码编译到 .NET。

关于 OrangeC 到 .NET 的文章

目录

引言

为了支持 .NET 方法、类、属性、枚举等,OrangeC 中实现了一种类似于 C# 的语言支持,例如

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

另一个来自 C++ 的特性,是启用了“using namespace”的使用,例如

using namespace System;
using namespace System::IO;

那么,让我们看看如何使用 OrangeC 编译器的新特性来编译到 .NET。

使用 .NET 类型

.NET 类型不过是 .NET 类,因此,使用 OrangeC,你可以像在 C 中使用普通类型一样使用这些类和方法。为了更容易理解,让我们构建一个小的示例,使用 .NET string

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

通过这段代码,我们创建了类 String 的一个实例,为了方便起见,在编译器内部实现了一些关键字来处理 .NET 中最常见的类,即

  1. 对象
  2. 字符串

因此,要使用这些类型,使用原生关键字,我们有

  1. __object
  2. __string

让我们看一个简单的例子

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

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

让我们再看一个使用 .NET 类型的例子

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

托管和非托管数组

为了避免旧代码和新代码之间的兼容性问题,实现了一种语法变体来区分数组类型,因此,要创建一个托管数组,我们在变量名前使用 [],要创建一个非托管数组,我们在变量名后使用 []。为了更清楚地说明,让我们做一个小例子

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

你可能在想,托管数组和非托管数组到底有什么区别?
所以,为了回答这个问题,让我们看看为每种数组生成的代码(我使用了一个反编译器来获取这段代码)

    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;
    }

数组的导航仍然相同

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;
}

使用 .NET 方法

使用 .NET 方法的方式与 C# 类似

  • <实例>.<方法>([参数]);

不同之处在于 enumstatic

  • [命名空间]::<静态类>::<方法 | 属性 | 枚举>[ ( [参数] ) ];

让我们看一个单一的例子,展示这些情况

#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;
}

让我们构建另一个例子,实现一个接收 .NET 类型作为参数并使用一些复杂方法调用的函数

#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;
}

最后,我们将构建一个混合托管和非托管代码,为此,我们将构建一个链表,其中链表的 struct 持有托管类型

#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;
}

使用 Try / Catch / Finally

异常系统极大地帮助开发人员处理错误,因此 C 代码变得与 .NET 错误系统和异常系统更兼容,我们实现了对 try / catch / finally 的支持,这些命令的使用就像在 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!");
  }
}

历史

  • 2017年10月12日:创建文章
© . All rights reserved.