11 3月 2024
私たちのトランスレーターがC#言語の構文構造をC++に変換する方法について話し合いましょう。翻訳の詳細と、このプロセス中に発生する制限について説明します。
翻訳はプロジェクトごとに行われます。1つのC#プロジェクトは、1つまたは2つのC++プロジェクトに変換されます。最初のプロジェクトはC#プロジェクトのミラーであり、2番目のプロジェクトは、元のプロジェクトにテストが存在する場合にテストを実行するgoogletestアプリケーションとして機能します。各入力プロジェクトにはCMakeLists.txtファイルが生成され、ほとんどのビルドシステムのプロジェクトを作成できます。
通常、1つの.csファイルは1つの.hファイルと1つの.cppファイルに対応します。通常、型定義はヘッダーファイルに入り、メソッド定義はソースコードファイルに置かれます。ただし、テンプレート型の場合はすべてのコードがヘッダーファイルに残ります。少なくとも1つの公開定義を含むヘッダーファイルはincludeディレクトリに入り、依存するプロジェクトとエンドユーザーがアクセスできます。内部定義のみを含むヘッダーファイルは、sourceディレクトリに入ります。
元の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()
{
}
各変換されたクラスの始めにある一連のエイリアスとマクロは、主にGetType
、typeof
、is
などのC#のメカニズムをエミュレートするために使用されます。効率的な型比較のために、.cppファイルからのハッシュコードが使用されます。インターフェースを実装するすべての関数は仮想ですが、これはC#の動作とは異なります。