Règles de traduction du code de C# à C++ : Principes de base

Voyons comment notre traducteur convertit les constructions syntaxiques du langage C# en C++. Nous explorerons les spécificités de la traduction et les limitations qui apparaissent au cours de ce processus.

Projets et unités de compilation

La traduction se fait sur une base par projet. Un projet C# est converti en un ou deux projets C++. Le premier projet est le miroir du projet C#, tandis que le second sert d'application googletest pour exécuter des tests s'ils existent dans le projet original. Un fichier CMakeLists.txt est généré pour chaque projet d'entrée, permettant la création de projets pour la plupart des systèmes de construction.

Habituellement, un fichier .cs correspond à un fichier .h et un fichier .cpp. Typiquement, les définitions de types vont dans les fichiers d'en-tête, tandis que les définitions de méthodes résident dans les fichiers de code source. Cependant, c'est différent pour les types de modèles, où tout le code reste dans les fichiers d'en-tête. Les fichiers d'en-tête contenant au moins une définition publique se retrouvent dans le répertoire include, accessible aux projets dépendants et aux utilisateurs finaux. Les fichiers d'en-tête avec uniquement des définitions internes vont dans le répertoire source.

En plus des fichiers de code obtenus en traduisant le code C# original, le traducteur génère des fichiers supplémentaires contenant du code de service. Les fichiers de configuration avec des entrées spécifiant où trouver les types de ce projet dans les fichiers d'en-tête sont également placés dans le répertoire de sortie. Ces informations sont nécessaires pour gérer les assemblages dépendants. De plus, un journal de traduction complet est stocké dans le répertoire de sortie.

Structure générale du code source

  1. Les espaces de noms C# sont mappés aux espaces de noms C++. Les opérateurs d'utilisation d'espace de noms sont transformés en leurs équivalents C++.
  2. Les commentaires sont transférés tels quels, à l'exception de la documentation des types et des méthodes, qui est traitée séparément.
  3. Le formatage est partiellement préservé.
  4. Les directives de préprocesseur ne sont pas transférées car toutes les constantes doivent être définies lors de la construction de l'arbre syntaxique.
  5. Chaque fichier commence par une liste de fichiers inclus, suivie d'une liste de déclarations anticipées de types. Ces listes sont générées en fonction des types mentionnés dans le fichier actuel afin que la liste des inclusions soit aussi minimale que possible.
  6. Les métadonnées de type sont générées sous forme de structures de données spéciales accessibles en temps d'exécution. Comme la génération inconditionnelle de métadonnées augmente considérablement la taille des bibliothèques compilées, elle est activée manuellement pour des types spécifiques selon les besoins.

Définitions des types

  1. Les alias de type sont traduits en utilisant la syntaxe using <typename> = ...
  2. Les énumérations C# sont traduites en énumérations C++14 (en utilisant la syntaxe enum class).
  3. Les Délégués sont transformés en alias pour les spécialisations de la classe System::MulticastDelegate :
public delegate int IntIntDlg(int n);
using IntIntDlg = System::MulticastDelegate<int32_t(int32_t)>;
  1. Les classes et structures C# sont représentées comme des classes C++. Les interfaces deviennent des classes abstraites. La structure d'héritage reflète celle de C#, et l'héritage implicite de System.Object devient explicite.
  2. Les propriétés et les indexeurs sont divisés en méthodes séparées pour les getters et les setters.
  3. Les fonctions virtuelles en C# correspondent aux fonctions virtuelles en C++. La mise en œuvre de l'interface est également réalisée à l'aide du mécanisme des fonctions virtuelles.
  4. Les types et méthodes génériques sont transformés en modèles C++.
  5. Les finalisateurs sont convertis en destructeurs.

Limites

L'ensemble de ces facteurs impose plusieurs limites :

  1. La traduction des méthodes génériques virtuelles n'est pas prise en charge.
  2. Les implémentations des méthodes d'interface sont virtuelles, même si elles ne l'étaient pas dans le code C# d'origine.
  3. L'introduction de nouvelles méthodes avec les mêmes noms et signatures que les méthodes virtuelles et/ou d'interface existantes n'est pas possible. Toutefois, le traducteur vous permet de renommer ces méthodes.
  4. Si des méthodes de la classe de base sont utilisées pour implémenter des interfaces dans une classe dérivée, des définitions supplémentaires apparaissent dans la classe dérivée qui n'étaient pas présentes dans le code C#.
  5. L'appel de méthodes virtuelles pendant la construction et la finalisation se comporte différemment après la traduction, et doit être évité.

Nous comprenons que l'imitation stricte du comportement du langage C# nécessiterait une approche quelque peu différente. Néanmoins, nous avons choisi cette logique parce qu'elle aligne l'API des bibliothèques converties plus étroitement avec les paradigmes C++. L'exemple ci-dessous illustre ces caractéristiques :

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

Fichier d'en-tête 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>();
        }
    }
};

Code source 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()
{
}

La série d'alias et de macros au début de chaque classe traduite est utilisée pour émuler certains mécanismes C#, principalement GetType, typeof et is. Les codes de hachage du fichier .cpp sont utilisés pour une comparaison efficace des types. Toutes les fonctions implémentant des interfaces sont virtuelles, même si cela diffère du comportement du C#.

Articles liés :