11 三月 2024

从C#到C++代码转换的规则:基础知识

让我们来讨论一下我们的翻译器是如何将 C# 语言的语法结构转换为 C++ 语言的。我们将探讨翻译的具体细节以及在此过程中出现的限制。

项目和编译单元

翻译是基于每个项目进行的。一个C#项目被转换成一个或两个C++项目。第一个项目是C#项目的镜像,而第二个项目如果在原始项目中存在测试,则作为googletest应用程序来运行测试。每个输入项目都会生成一个CMakeLists.txt文件,允许为大多数构建系统创建项目。

通常,一个.cs文件对应一个.h文件和一个.cpp文件。通常,类型定义放在头文件中,而方法定义位于源代码文件中。但是,对于模板类型,所有代码都保留在头文件中。包含至少一个公共定义的头文件最终会出现在include目录中,可供依赖项目和最终用户访问。仅包含内部定义的头文件进入source目录。

除了从原始C#代码翻译得到的代码文件外,翻译器还生成包含服务代码的附加文件。配置文件中的条目指定在头文件中从哪里找到该项目的类型,也放在输出目录中。这些信息对于处理依赖的程序集是必要的。此外,一个全面的翻译日志存储在输出目录中。

源代码的一般结构

  1. C#命名空间映射到C++命名空间。命名空间使用运算符被转换为 C++ 的对应运算符。
  2. 除了类型和方法文档之外,注释按原样转移,类型和方法文档单独处理。
  3. 格式部分保留。
  4. 预处理指令不会转移,因为所有常量必须在语法树构建期间定义。
  5. 每个文件都以包含文件列表开始,然后是类型的前向声明列表。这些列表是基于当前文件中提到的类型生成的,以使包含列表尽可能最小。
  6. 类型元数据作为运行时可访问的特殊数据结构生成。由于无条件的元数据生成会显著增加编译库的大小,因此根据需要手动为特定类型启用。

类型定义

  1. 类型别名使用语法 using <typename> = ...翻译。
  2. C# 枚举 映射为 C++14 枚举(使用 enum class 语法)。
  3. 委托被转换为 System::MulticastDelegate 类特殊化的别名:
public delegate int IntIntDlg(int n);
using IntIntDlg = System::MulticastDelegate<int32_t(int32_t)>;
  1. C#类和结构被表示为C++类。接口变成抽象类。继承结构与 C# 一致,从 System.Object 的隐式继承变为显式继承。
  2. 属性和索引器被拆分成单独的获取器和设置器方法。
  3. C# 中的虚拟函数与 C++ 中的虚拟函数相对应。接口的实现也是通过虚函数机制实现的。
  4. 通用类型和方法被转化为C++模板
  5. 终结器被转换为析构器

限制

所有这些因素共同造成了一些限制:

  1. 不支持虚拟泛型方法的翻译
  2. 接口方法的实现是虚拟的,即使它们在原始 C# 代码中不是虚拟的。
  3. 不允许引入与现有虚拟和/或接口方法具有相同名称和签名的新方法。不过,翻译器允许您重新命名此类方法。
  4. 如果基类方法被用于实现派生类中的接口,则派生类中会出现 C# 中没有的附加定义。
  5. 在构造和最终化过程中调用虚拟方法,在翻译后会有不同的表现,应避免使用。

我们知道,严格模仿 C# 行为需要采用某种不同的方法。尽管如此,我们还是选择了这种逻辑,因为它使转换后库的应用程序接口与 C++ 范例更加接近。下面的示例说明了这些特性:

C# 代码

using System;

public class Base
{
    public virtual void Foo1()
    { }
    public void Bar()
    { }
}
public interface IFoo
{
    void Foo1();
    void Foo2();
    void Foo3();
}
public interface IBar
{
    void Bar();
}
public class Child : Base, IFoo, IBar
{
    public void Foo2()
    { }
    public virtual void Foo3()
    { }
    public T Bazz<T>(object o) where T : class
    {
        if (o is T)
            return (T)o;
        else
            return default(T);
    }
}

C++ 头文件

#pragma once

#include <system/object_ext.h>
#include <system/exceptions.h>
#include <system/default.h>
#include <system/constraints.h>

class Base : public virtual System::Object
{
    typedef Base ThisType;
    typedef System::Object BaseType;
    
    typedef ::System::BaseTypesInfo<BaseType> ThisTypeBaseTypesInfo;
    RTTI_INFO_DECL();
    
public:

    virtual void Foo1();
    void Bar();
};

class IFoo : public virtual System::Object
{
    typedef IFoo ThisType;
    typedef System::Object BaseType;
    
    typedef ::System::BaseTypesInfo<BaseType> ThisTypeBaseTypesInfo;
    RTTI_INFO_DECL();
    
public:

    virtual void Foo1() = 0;
    virtual void Foo2() = 0;
    virtual void Foo3() = 0;
};

class IBar : public virtual System::Object
{
    typedef IBar ThisType;
    typedef System::Object BaseType;
    
    typedef ::System::BaseTypesInfo<BaseType> ThisTypeBaseTypesInfo;
    RTTI_INFO_DECL();
    
public:

    virtual void Bar() = 0;
};

class Child : public Base, public IFoo, public IBar
{
    typedef Child ThisType;
    typedef Base BaseType;
    typedef IFoo BaseType1;
    typedef IBar BaseType2;
    
    typedef ::System::BaseTypesInfo<BaseType, BaseType1, BaseType2> ThisTypeBaseTypesInfo;
    RTTI_INFO_DECL();
    
public:

    void Foo1() override;
    void Bar() override;
    void Foo2() override;
    void Foo3() override;
    template <typename T>
    T Bazz(System::SharedPtr<System::Object> o)
    {
        assert_is_cs_class(T);
        
        if (System::ObjectExt::Is<T>(o))
        {
            return System::StaticCast<typename T::Pointee_>(o);
        }
        else
        {
            return System::Default<T>();
        }
    }
};

C++ 源代码

#include "Class1.h"
RTTI_INFO_IMPL_HASH(788057553u, ::Base, ThisTypeBaseTypesInfo);
void Base::Foo1()
{
}
void Base::Bar()
{
}
RTTI_INFO_IMPL_HASH(1733877629u, ::IFoo, ThisTypeBaseTypesInfo);
RTTI_INFO_IMPL_HASH(1699913226u, ::IBar, ThisTypeBaseTypesInfo);
RTTI_INFO_IMPL_HASH(3787596220u, ::Child, ThisTypeBaseTypesInfo);
void Child::Foo1()
{
    Base::Foo1();
}
void Child::Bar()
{
    Base::Bar();
}
void Child::Foo2()
{
}
void Child::Foo3()
{
}

每个翻译类开头的一系列别名和宏用于模拟某些 C# 机制,主要是 GetTypetypeofis。.cpp 文件中的哈希代码用于有效的类型比较。所有实现接口的函数都是虚拟的,尽管这与 C# 的行为不同。

相关新闻

相关视频

相关文章