11 March 2024
Let's discuss how our translator converts syntactic constructs from the C# language to C++. We'll explore the translation specifics and the limitations that arise during this process.
Translation occurs on a per-project basis. One C# project is converted into one or two C++ projects. The first project mirrors the C# project, while the second serves as a googletest application for running tests if they exist in the original project. A CMakeLists.txt file is generated for each input project, allowing the creation of projects for most build systems.
Usually, one .cs file corresponds to one .h file and one .cpp file. Typically, type definitions go into header files, while method definitions reside in source code files. However, this is different for template types, where all code remains in header files. Header files containing at least one public definition end up in the include directory, accessible to dependent projects and end users. Header files with only internal definitions go into the source directory.
In addition to code files obtained from translating the original C# code, the translator generates additional files containing service code. Configuration files with entries specifying where to find types from this project in header files are also placed in the output directory. This information is necessary for handling dependent assemblies. Additionally, a comprehensive translation log is stored in the output directory.
using <typename> = ...
enum class
syntax).System::MulticastDelegate
class:public delegate int IntIntDlg(int n);
using IntIntDlg = System::MulticastDelegate<int32_t(int32_t)>;
System.Object
becomes explicit.All of these factors together impose several limitations:
We understand that strictly mimicking C# behavior would require a somewhat different approach. Nevertheless, we chose this logic because it aligns the API of the converted libraries more closely with C++ paradigms. The example below illustrates these features:
C# code:
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++ header file:
#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++ source code:
#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()
{
}
The series of aliases and macros at the beginning of each translated class are used to emulate certain C# mechanisms, primarily GetType
, typeof
, and is
. Hash codes from the .cpp file are used for efficient type comparison. All functions implementing interfaces are virtual, even though this differs from C# behavior.