Kodu C#'tan C++'a Çevirme Kuralları: Temel Bilgiler

Çevirmenimizin sözdizimsel yapıları C# dilinden C++ diline nasıl dönüştürdüğünü tartışalım. Bu süreçte çevirinin ayrıntılarını ve ortaya çıkan sınırlamaları inceleyeceğiz.

Projeler ve derleme birimleri

Çeviri, proje bazında gerçekleşir. Bir C# projesi, bir veya iki C++ projesine dönüştürülür. İlk proje, C# projesini yansıtırken, ikincisi orijinal projede varsa testleri çalıştırmak için bir googletest uygulaması olarak hizmet eder. Her girdi projesi için bir CMakeLists.txt dosyası oluşturulur, bu da çoğu derleme sistemi için projelerin oluşturulmasına olanak tanır.

Genellikle bir .cs dosyası bir .h dosyasına ve bir .cpp dosyasına karşılık gelir. Tip tanımları genellikle başlık dosyalarına yerleştirilirken, yöntem tanımları kaynak kod dosyalarında bulunur. Ancak, şablon türleri için bu farklıdır; burada tüm kod başlık dosyalarında kalır. En az bir genel tanım içeren başlık dosyaları, bağımlı projeler ve son kullanıcılar tarafından erişilebilen include dizinine yerleştirilir. Yalnızca iç tanımları olan başlık dosyaları source dizinine yerleştirilir.

Orijinal C# kodunu çevirerek elde edilen kod dosyalarının yanı sıra, çevirmen hizmet kodu içeren ek dosyalar da oluşturur. Başlık dosyalarındaki bu projenin türlerini nerede bulacağını belirten giriş dosyaları da çıktı dizinine yerleştirilir. Bu bilgi, bağımlı derlemeleri işlemek için gereklidir. Ayrıca, kapsamlı bir çeviri günlüğü çıktı dizininde saklanır.

Kaynak kodunun genel yapısı

  1. C# isim alanları, C++ isim alanlarına eşlenir. İsim alanı kullanım operatörleri C++ karşılıklarına dönüştürülür.
  2. Yorumlar, tip ve yöntem belgeleri hariç olmak üzere olduğu gibi aktarılır ve ayrı bir şekilde ele alınır.
  3. Biçimlendirme, kısmen korunur.
  4. Önişlemci yönergeleri, tüm sabitlerin sözdizimi ağacı oluşturulurken tanımlanması gerektiği için aktarılmaz.
  5. Her dosya, dahil edilen dosyaların bir listesi ile başlar ve ardından türlerin ileri bildirimlerinin bir listesi gelir. Bu listeler, mevcut dosyada belirtilen türlere dayalı olarak oluşturulur, böylece dahil etme listesi mümkün olduğunca azalır.
  6. Tür meta verileri, çalışma zamanında erişilebilen özel veri yapıları olarak oluşturulur. Koşulsuz meta veri oluşturma, derlenmiş kitaplıkların boyutunu önemli ölçüde artırdığından, ihtiyaca göre belirli türler için manuel olarak etkinleştirilir.

Tür tanımları

  1. Tür takma adları, using <typename> = ... sözdizimi kullanılarak çevrilir.
  2. C# numaralandırmaları, C++14 numaralandırmalarına (enum class sözdizimi kullanılarak) eşlenir.
  3. Delegeler, System::MulticastDelegate sınıfının özelleştirmeleri için takma adlara dönüştürülür:
public delegate int IntIntDlg(int n);
using IntIntDlg = System::MulticastDelegate<int32_t(int32_t)>;
  1. C# sınıfları ve yapıları, C++ sınıfları olarak temsil edilir. Arayüzler soyut sınıflar haline gelir. Kalıtım yapısı C#'dekiyle aynıdır ve System.Object'ten gelen zımni kalıtım açık hale getirilir.
  2. Özellikler ve dizinleyiciler, alıcılar ve ayarlayıcılar için ayrı yöntemlere bölünür.
  3. C#'deki sanal işlevler, C++'daki sanal işlevlere karşılık gelir. Arayüz uygulaması da sanal işlev mekanizması kullanılarak gerçekleştirilir.
  4. Jenerik türler ve yöntemler, C++ şablonlarına dönüştürülür.
  5. Finalizörler, yıkıcılar olarak dönüştürülür.

Sınırlamalar

Tüm bu faktörler bir araya geldiğinde birkaç sınırlama ortaya çıkar:

  1. Sanal jenerik yöntemlerin çevirimi desteklenmez.
  2. Arayüz yöntemi uygulamaları sanaldır, orijinal C# kodunda böyle olmasalar bile.
  3. Varolan sanal ve/veya arayüz yöntemleriyle aynı ad ve imzaya sahip yeni yöntemler tanıtmak mümkün değildir. Ancak çevirmen bu tür yöntemleri yeniden adlandırmanıza izin verir.
  4. Türetilmiş bir sınıfta arayüzleri uygulamak için temel sınıf yöntemleri kullanılıyorsa, C# kodunda bulunmayan ek tanımlamalar türetilmiş sınıfta görünür hale gelir.
  5. Çeviri sonrası yapıcı ve sonlandırıcı sırasında sanal yöntemleri çağırmak farklı davranır ve bunun önlenmesi gerekir.

C# davranışını sıkı bir şekilde taklit etmenin biraz farklı bir yaklaşım gerektireceğini anlıyoruz. Bununla birlikte, dönüştürülen kitaplıkların API'sini C++ paradigmalarına daha yakın hale getiren bu mantığı seçtik. Aşağıdaki örnek bu özellikleri açıklar:

C# kodu:

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++ başlık dosyası:

#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++ kaynak kodu:

#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()
{
}

Her çevrilen sınıfın başlangıcındaki takma adlar ve makrolar, özellikle GetType, typeof ve is gibi belirli C# mekanizmalarını taklit etmek için kullanılır. .cpp dosyasındaki karma kodlar etkili tür karşılaştırmaları için kullanılır. Arayüzleri uygulayan tüm işlevler sanaldır, bu C# davranışından farklıdır.

İlgili makaleler