11 marzo 2024
Vamos a discutir cómo nuestro traductor convierte construcciones sintácticas del lenguaje C# a C++. Exploraremos las particularidades de la traducción y las limitaciones que surgen durante este proceso.
La traducción se realiza por proyecto. Un proyecto de C# se convierte en uno o dos proyectos de C++. El primer proyecto es un espejo del proyecto de C#, mientras que el segundo sirve como una aplicación googletest para ejecutar pruebas si existen en el proyecto original. Se genera un archivo CMakeLists.txt para cada proyecto de entrada, permitiendo la creación de proyectos para la mayoría de los sistemas de construcción.
Normalmente, un archivo .cs corresponde a un archivo .h y un archivo .cpp. Por lo general, las definiciones de tipos van en archivos de cabecera, mientras que las definiciones de métodos residen en archivos de código fuente. Sin embargo, esto es diferente para los tipos de plantilla, donde todo el código permanece en archivos de cabecera. Los archivos de cabecera que contienen al menos una definición pública terminan en el directorio include, accesible para proyectos dependientes y usuarios finales. Los archivos de cabecera con solo definiciones internas van al directorio de fuente.
Además de los archivos de código obtenidos de la traducción del código C# original, el traductor genera archivos adicionales que contienen código de servicio. Los archivos de configuración con entradas que especifican dónde encontrar tipos de este proyecto en archivos de cabecera también se colocan en el directorio de salida. Esta información es necesaria para manejar ensamblajes dependientes. Además, un registro de traducción completo se almacena en el directorio de salida.
using <typename> = ...
enum class
).System::MulticastDelegate
:public delegate int IntIntDlg(int n);
using IntIntDlg = System::MulticastDelegate<int32_t(int32_t)>;
System.Object
se convierte en explícita.Todos estos factores juntos imponen varias limitaciones:
Entendemos que imitar estrictamente el comportamiento de C# requeriría un enfoque algo diferente. Sin embargo, elegimos esta lógica porque alinea la API de las bibliotecas convertidas más estrechamente con los paradigmas de C++. El siguiente ejemplo ilustra estas características:
Código 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);
}
}
Archivo de cabecera 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ódigo fuente 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 serie de alias y macros al principio de cada clase traducida se utilizan para emular ciertos mecanismos de C#, principalmente GetType
, typeof
y is
. Los códigos hash del archivo .cpp se utilizan para una comparación de tipos eficiente. Todas las funciones que implementan interfaces son virtuales, aunque esto difiere del comportamiento de C#.