C#에서 C++로 코드를 번역하는 규칙: 기본 사항

번역기가 C# 언어에서 C++로 구문 구조를 변환하는 방법에 대해 논의해 보겠습니다. 이 과정에서 발생하는 번역의 세부 사항과 제한 사항을 살펴보겠습니다.

프로젝트와 컴파일 단위

번역은 프로젝트별로 이루어집니다. 하나의 C# 프로젝트는 하나 또는 두 개의 C++ 프로젝트로 변환됩니다. 첫 번째 프로젝트는 C# 프로젝트를 반영하며, 두 번째 프로젝트는 원본 프로젝트에 테스트가 존재하는 경우 테스트를 실행하기 위한 googletest 애플리케이션으로 사용됩니다. 각 입력 프로젝트에 대해 CMakeLists.txt 파일이 생성되어 대부분의 빌드 시스템에 대한 프로젝트 생성을 허용합니다.

보통 하나의 .cs 파일은 하나의 .h 파일과 하나의 .cpp 파일에 해당합니다. 일반적으로 타입 정의는 헤더 파일에 들어가고, 메소드 정의는 소스 코드 파일에 위치합니다. 그러나 템플릿 타입의 경우 모든 코드가 헤더 파일에 남아 있습니다. 적어도 하나의 공개 정의를 포함하는 헤더 파일은 include 디렉토리에 들어가 의존 프로젝트와 최종 사용자가 접근할 수 있습니다. 내부 정의만 있는 헤더 파일은 source 디렉토리로 갑니다.

원본 C# 코드의 번역에서 얻은 코드 파일 외에도, 번역기는 서비스 코드를 포함하는 추가 파일을 생성합니다. 이 프로젝트의 타입을 헤더 파일에서 어디에서 찾을 수 있는지를 지정하는 항목이 포함된 구성 파일도 출력 디렉토리에 배치됩니다. 이 정보는 의존 어셈블리를 처리하는 데 필요합니다. 또한, 포괄적인 번역 로그가 출력 디렉토리에 저장됩니다.

소스 코드의 일반적인 구조

  1. C# 네임스페이스C++ 네임스페이스에 매핑됩니다. 네임스페이스 사용 연산자는 C++ 동등물로 변환됩니다.
  2. 주석은 그대로 전송되지만, 타입 및 메소드 문서화는 별도로 처리됩니다.
  3. 포맷팅은 부분적으로 보존됩니다.
  4. 모든 상수는 구문 트리 구성 중에 정의되어야 하기 때문에 전처리 지시문은 전송되지 않습니다.
  5. 각 파일은 포함된 파일 목록으로 시작하여 현재 파일에 언급된 타입을 기반으로 생성된 타입의 전방 선언 목록이 뒤따릅니다. 이 목록은 포함 목록이 가능한 최소한이 되도록 생성됩니다.
  6. 타입 메타데이터는 런타임에 접근 가능한 특별한 데이터 구조로 생성됩니다. 무조건적인 메타데이터 생성은 컴파일된 라이브러리의 크기를 크게 증가시키기 때문에, 필요에 따라 특정 타입에 대해 수동으로 활성화됩니다.

유형 정의

  1. 유형 별칭using <typename> = ... 구문을 사용하여 변환됩니다.
  2. C# 열거형C++14 열거형에 매핑됩니다(enum class 구문 사용).
  3. 대리자System::MulticastDelegate 클래스의 특수화를 위한 별칭으로 변환됩니다.
public delegate int IntIntDlg(int n);
using IntIntDlg = System::MulticastDelegate<int32_t(int32_t)>;
  1. C# 클래스 및 구조체C++ 클래스로 표현됩니다. 인터페이스는 추상 클래스가 됩니다. 상속 구조는 C#의 상속 구조를 반영하며, System.Object의 암시적 상속은 명시적 상속이 됩니다.
  2. 프로퍼티와 인덱서는 게터와 세터에 대해 별도의 메서드로 분리됩니다.
  3. C#의 가상 함수는 C++의 가상 함수에 해당합니다. 인터페이스 구현도 가상 함수의 메커니즘을 사용하여 이루어집니다.
  4. 일반 유형 및 메서드C++ 템플릿으로 변환됩니다.
  5. 파이널라이저소멸자로 변환됩니다.

제한 사항 ## 제한 사항

이러한 모든 요인으로 인해 몇 가지 제한 사항이 적용됩니다:

  1. 가상 제네릭 메서드의 번역은 지원되지 않습니다.
  2. 인터페이스 메서드 구현은 원래 C# 코드에 없더라도 가상의 메서드입니다.
  3. 기존 가상 메서드 및/또는 인터페이스 메서드와 동일한 이름 및 서명을 가진 새로운 메서드를 도입할 수 없습니다. 그러나 번역기를 사용하면 이러한 메서드의 이름을 변경할 수 있습니다.
  4. 기본 클래스 메서드가 파생 클래스에서 인터페이스를 구현하는 데 사용되는 경우, C#에는 없는 추가 정의가 파생 클래스에 나타납니다.
  5. 구축 및 최종화 중에 가상 메서드를 호출하면 번역 후 다르게 동작하므로 피해야 합니다.

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# 동작과는 다르지만 가상입니다.

관련 기사