กฎการแปลโค้ดจาก C# ไปเป็น C++: พื้นฐาน

เรามาพูดคุยกันว่านักแปลของเราแปลงโครงสร้างวากยสัมพันธ์จากภาษา C# ไปเป็น C++ ได้อย่างไร เราจะสำรวจข้อมูลเฉพาะของการแปลและข้อจำกัดที่เกิดขึ้นระหว่างกระบวนการนี้

โครงการและหน่วยการรวบรวม

การแปลเกิดขึ้นเป็นรายโครงการ หนึ่งโปรเจ็กต์ C# จะถูกแปลงเป็นหนึ่งหรือสองโปรเจ็กต์ C++ โปรเจ็กต์แรกสะท้อนโปรเจ็กต์ C# ในขณะที่โปรเจ็กต์ที่สองทำหน้าที่เป็นแอปพลิเคชัน googletest สำหรับเรียกใช้การทดสอบหากมีอยู่ในโปรเจ็กต์ดั้งเดิม ไฟล์ CMakeLists.txt ถูกสร้างขึ้นสำหรับแต่ละโปรเจ็กต์อินพุต ซึ่งช่วยให้สามารถสร้างโปรเจ็กต์สำหรับระบบ build ส่วนใหญ่ได้

โดยปกติแล้ว ไฟล์ .cs หนึ่งไฟล์จะสอดคล้องกับไฟล์ .h หนึ่งไฟล์ และไฟล์ .cpp หนึ่งไฟล์ โดยทั่วไปแล้ว คำจำกัดความของประเภทจะอยู่ในไฟล์ส่วนหัว ในขณะที่คำจำกัดความของวิธีการจะอยู่ในไฟล์ซอร์สโค้ด อย่างไรก็ตาม สิ่งนี้จะแตกต่างออกไปสำหรับประเภทเทมเพลต โดยที่โค้ดทั้งหมดจะยังคงอยู่ในไฟล์ส่วนหัว ไฟล์ส่วนหัวที่มีคำจำกัดความสาธารณะอย่างน้อยหนึ่งรายการจะจบลงในไดเร็กทอรีรวม ซึ่งสามารถเข้าถึงได้โดยโปรเจ็กต์และผู้ใช้ปลายทาง ไฟล์ส่วนหัวที่มีคำจำกัดความภายในเท่านั้นจะเข้าสู่ไดเร็กทอรีต้นทาง

นอกจากไฟล์โค้ดที่ได้รับจากการแปลโค้ด 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. คุณสมบัติและตัวทำดัชนี แบ่งออกเป็นวิธีที่แยกกันสำหรับ getters และ setters
  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# ก็ตาม

บทความที่เกี่ยวข้อง