11 März 2024
Lassen Sie uns darüber sprechen, wie unser Übersetzer syntaktische Konstrukte aus der C#-Sprache in C++ umwandelt. Wir werden die spezifischen Übersetzungsregeln und die auftretenden Einschränkungen erkunden.
Die Übersetzung erfolgt pro Projekt. Ein C#-Projekt wird in ein oder zwei C++-Projekte umgewandelt. Das erste Projekt spiegelt das C#-Projekt wider, während das zweite als googletest-Anwendung für die Ausführung von Tests dient, sofern sie im ursprünglichen Projekt vorhanden sind. Für jedes Eingangsprojekt wird eine CMakeLists.txt-Datei generiert, die die Erstellung von Projekten für die meisten Build-Systeme ermöglicht.
Normalerweise entspricht eine .cs-Datei einer .h-Datei und einer .cpp-Datei. Typdefinitionen werden in der Regel in Headerdateien platziert, während Methodendefinitionen in Quelldateien verbleiben. Bei Vorlagetypen ist dies jedoch anders, hier bleibt der gesamte Code in Headerdateien. Headerdateien mit mindestens einer öffentlichen Definition landen im include-Verzeichnis und sind für abhängige Projekte und Endbenutzer zugänglich. Headerdateien mit nur internen Definitionen werden in das source-Verzeichnis verschoben.
Neben den aus der ursprünglichen C#-Codeübersetzung gewonnenen Code-Dateien generiert der Übersetzer zusätzliche Dateien mit Servicecode. Konfigurationsdateien mit Einträgen, die angeben, wo im Headerdateien die Typen dieses Projekts zu finden sind, werden ebenfalls im Ausgabeverzeichnis platziert. Diese Informationen sind für die Verarbeitung abhängiger Assemblys erforderlich. Zusätzlich wird ein umfassendes Übersetzungsprotokoll im Ausgabeverzeichnis gespeichert.
using <typename> = ...
übersetzt.enum class
).System::MulticastDelegate
umgewandelt:public delegate int IntIntDlg(int n);
using IntIntDlg = System::MulticastDelegate<int32_t(int32_t)>;
System.Object
wird explizit gemacht.All diese Faktoren zusammen führen zu mehreren Einschränkungen:
Wir verstehen, dass eine strikte Nachahmung des C#-Verhaltens einen etwas anderen Ansatz erfordern würde. Dennoch haben wir diese Logik gewählt, weil sie die API der konvertierten Bibliotheken näher an die C++-Paradigmen anpasst. Das folgende Beispiel veranschaulicht diese Funktionen:
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++-Headerdatei:
#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++-Quellcode:
#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()
{
}
Die Reihe von Aliassen und Makros am Anfang jeder übersetzten Klasse dient dazu, bestimmte C#-Mechanismen zu emulieren, hauptsächlich GetType
, typeof
und is
. Hash-Codes aus der .cpp-Datei werden für effiziente Typvergleiche verwendet. Alle Funktionen, die Schnittstellen implementieren, sind virtuell, obwohl dies vom C#-Verhalten abweicht.