11 марта 2024
Поговорим о том, каким образом наш транслятор преобразует синтаксические конструкции языка C# в C++. Рассмотрим особенности преобразования и возникающие ограничения.
Трансляция производится попроектно. Один проект C# преобразуется в один или два проекта C++. Первый проект аналогичен проекту C#, второй представляет собой googletest-приложение для запуска тестов, если они присутствуют в исходном проекте. Для каждого входного проекта генерируется файл CMakeLists.txt, который позволяет создавать проекты для большинства сборочных систем.
В большинстве случаев одному файлу .cs соответствует один файл .h и один файл .cpp. Обычно определения типов попадают в заголовочный файл, а определения методов – в файл исходного кода, но это не так для шаблонных типов, весь код которых остаётся в заголовочных файлах. Заголовочные файлы, в которых присутствует, по крайней мере, одно публичное определение, попадают в каталог включаемых файлов, доступных зависимым проектам и конечным пользователям. Заголовочные файлы, включающие лишь непубличные (internal) определения, попадают в каталог с исходниками.
Кроме файлов с кодом, полученных трансляцией оригинального кода C#, транслятор создаёт дополнительные файлы, содержащие некоторый сервисный код. Также в выходной каталог помещаются конфигурационные файлы с записями о том, в каких заголовочных файлах искать типы из данного проекта. Эта информация требуется при преобразовании зависимых сборок. Также в выходную директорию ложится полный лог транслирования.
using <typename> = ...
enum class
).System::MulticastDelegate
:public delegate int IntIntDlg(int n);
using IntIntDlg = System::MulticastDelegate<int32_t(int32_t)>;
System.Object
становится явным.Всё это вместе задаёт несколько ограничений:
Понятно, что строгая имитация поведения C# требовала бы несколько иного подхода. Тем не менее, мы предпочли следовать именно такой логике, поскольку в этом случае API конвертированных библиотек в наиболее полной мере соответствует парадигмам 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#, прежде всего: GetType
, typeof
и is
. Хэш-коды из файла .cpp используются для быстрого сравнения типов. Все функции, реализующие интерфейсы, виртуальны, хотя в C# это не так.