본문 바로가기
프로그래밍

플랫 버퍼(FlatBuffers)에 대해서 - C# 환경에서 빌드하기

by bantomak 2023. 7. 12.
 

GitHub - google/flatbuffers: FlatBuffers: Memory Efficient Serialization Library

FlatBuffers: Memory Efficient Serialization Library - GitHub - google/flatbuffers: FlatBuffers: Memory Efficient Serialization Library

github.com

플랫 버퍼(FlatBuffers)란?

FlatBuffers는 C,C++,C#,GO,Java, JavaScript, Lobster, Lua, TypeScript, PHP, Python, Rust를 위한 크로스 플랫폼 직렬화 라이브러리이다.

 

플랫 버퍼의 특징

  • 패킹 / 언패킹 없이 직렬화된 데이터에 엑세스
  • 메모리 효율성 및 속도 증가
  • 생성된 코드가 작고 단일 Header 파일로 쉽게 통합 가능
  • 모든 언어에서 사용하기 편리하게 제공
  • 크로스 플랫폼, 종속성 없이 사용이 가능

 

플랫 버퍼 다운로드 주소

 

Releases · google/flatbuffers

FlatBuffers: Memory Efficient Serialization Library - google/flatbuffers

github.com

 

DLL 파일 빌드

C# 프로젝트 파일에서 임포트할 DLL 파일을 빌드해줍니다.

플랫 버퍼를 다운 받은 폴더에서

 

\flatbuffers-23.5.26\flatbuffers-23.5.26\net\FlatBuffers

C# 을 위해 준비된 경로로 이동해서 터미널을 열고 아래의 내용을 입력해줍니다.

 

dotnet build -f netstandard2.0 "Google.FlatBuffers.csproj"

 

그러면 정상적으로 dll이 생성됩니다. 해당 결과물을 C# 프로젝트에서 참조하여 사용합니다.

 

C# 프로젝트에 종속성 추가

프로젝트 오른쪽 클릭 -> 추가 -> COM 참조 추가

위에서 빌드한 Google.FlatBuffers.dll 파일을 추가

 

플랫 버퍼 스키마 작성

Monster.fbs 파일 생성

 

// Example IDL file for our monster's schema.

enum Color:byte { Red = 0, Green, Blue = 2 }
 
union Equipment { Weapon } // Optionally add more tables.
 
struct Vec3 {
  x:float;
  y:float;
  z:float;
}
 
table Monster {
  pos:Vec3; // Struct.
  mana:short = 150;
  hp:short = 100;
  name:string;
  friendly:bool = false (deprecated);
  inventory:[ubyte];  // Vector of scalars.
  color:Color = Blue; // Enum.
  weapons:[Weapon];   // Vector of tables.
  equipped:Equipment; // Union.
  path:[Vec3];        // Vector of structs.
}
 
table Weapon {
  name:string;
  damage:short;
}
 
root_type Monster;

flatc.exe와 같은 폴더에 두고 컴파일을 진행하자.

 

fbs 파일 컴파일

컴파일 커맨드

flatc [ GENERATOR OPTIONS ] [ -o PATH ] [ -I PATH ] [ -S ] FILES...

      [ -- FILES...]

 

./flatc --csharp Monster.fbs

컴파일 이후에 파일들을 C# 프로젝트 폴더로 옮겨주자.

 

예제 코드

using System;
using Google.FlatBuffers;

namespace FlatBufferExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            var builder = new FlatBufferBuilder(1);

            // Create some weapons for our Monster ('Sword' and 'Axe').
            var weapon1Name = builder.CreateString("Sword");
            var weapon1Damage = 3;
            var weapon2Name = builder.CreateString("Axe");
            var weapon2Damage = 5;

            // Use the `CreateWeapon()` helper function to create the weapons, since we set every field.
            var weaps = new Offset<Weapon>[2];
            weaps[0] = Weapon.CreateWeapon(builder, weapon1Name, (short)weapon1Damage);
            weaps[1] = Weapon.CreateWeapon(builder, weapon2Name, (short)weapon2Damage);

            // Serialize the FlatBuffer data.
            var name = builder.CreateString("Orc");
            var treasure = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            var inv = Monster.CreateInventoryVector(builder, treasure);
            var weapons = Monster.CreateWeaponsVector(builder, weaps);
            var pos = Vec3.CreateVec3(builder, 1.0f, 2.0f, 3.0f);

            Monster.StartMonster(builder);
            Monster.AddPos(builder, pos);
            Monster.AddHp(builder, (short)300);
            Monster.AddName(builder, name);
            Monster.AddInventory(builder, inv);
            Monster.AddColor(builder, Color.Red);
            Monster.AddWeapons(builder, weapons);
            Monster.AddEquippedType(builder, Equipment.Weapon);
            Monster.AddEquipped(builder, weaps[1].Value);
            var orc = Monster.EndMonster(builder);

            builder.Finish(orc.Value); // You could also call `Monster.FinishMonsterBuffer(builder, orc);`.

            // get byte[] packet
            var packet = builder.SizedByteArray();

            // We now have a FlatBuffer that we could store on disk or send over a network.

            // ...Code to store to disk or send over a network goes here...

            // Instead, we are going to access it right away, as if we just received it.

            var bb = new ByteBuffer(packet);

            // Get access to the root:
            var monster = Monster.GetRootAsMonster(bb);

            // For C#, unlike other languages, most values (except for vectors and unions) are available as
            // properties instead of accessor methods.

            // Note: We did not set the `Mana` field explicitly, so we get back the default value.
            Assert(monster.Mana == 150, "monster.Mana", Convert.ToString(monster.Mana),
                   Convert.ToString(150));
            Assert(monster.Hp == 300, "monster.Hp", Convert.ToString(monster.Hp), Convert.ToString(30));
            Assert(monster.Name.Equals("Orc", StringComparison.Ordinal), "monster.Name", monster.Name,
                   "Orc");
            Assert(monster.Color == Color.Red, "monster.Color", Convert.ToString(monster.Color),
                   Convert.ToString(Color.Red));

            var vec = monster.Pos.Value;
            Assert(vec.X == 1.0f, "vec.X",
                   Convert.ToString(vec.X), Convert.ToString(1.0f));
            Assert(vec.Y == 2.0f, "vec.Y",
                   Convert.ToString(vec.Y), Convert.ToString(2.0f));
            Assert(vec.Z == 3.0f, "vec.Z",
                   Convert.ToString(vec.Z), Convert.ToString(3.0f));

            // Get and test the `Inventory` FlatBuffer `vector`.
            for (int i = 0; i < monster.InventoryLength; i++)
            {
                Assert(monster.Inventory(i) == i, "monster.Inventory",
                       Convert.ToString(monster.Inventory(i)), Convert.ToString(i));
            }

            // Get and test the `Weapons` FlatBuffer `vector` of `table`s.
            var expectedWeaponNames = new string[] { "Sword", "Axe" };
            var expectedWeaponDamages = new short[] { 3, 5 };
            for (int i = 0; i < monster.WeaponsLength; i++)
            {
                Assert(monster.Weapons(i).Value.Name.Equals(expectedWeaponNames[i], StringComparison.Ordinal),
                       "monster.Weapons", monster.Weapons(i).Value.Name, expectedWeaponNames[i]);
                Assert(monster.Weapons(i).Value.Damage == expectedWeaponDamages[i], "monster.GetWeapons",
                       Convert.ToString(monster.Weapons(i).Value.Damage),
                       Convert.ToString(expectedWeaponDamages[i]));
            }

            // Get and test the `Equipped` FlatBuffer `union`.
            Assert(monster.EquippedType == Equipment.Weapon, "monster.EquippedType",
                   Convert.ToString(monster.EquippedType), Convert.ToString(Equipment.Weapon));
            var equipped = monster.Equipped<Weapon>().Value;
            Assert(equipped.Name.Equals("Axe", StringComparison.Ordinal), "equipped.Name", equipped.Name,
                   "Axe");
            Assert(equipped.Damage == 5, "equipped.Damage", Convert.ToString(equipped.Damage),
                   Convert.ToString(5));

            Console.WriteLine("The FlatBuffer was successfully created and verified!");
        }

        // A helper function to handle assertions.
        static void Assert(bool assertPassed, string codeExecuted, string actualValue,
                           string expectedValue)
        {
            if (assertPassed == false)
            {
                Console.WriteLine("Assert failed! " + codeExecuted + " (" + actualValue +
                    ") was not equal to " + expectedValue + ".");
                System.Environment.Exit(1);
            }
        }
    }
}

 

정상적으로 빌드가 완료되었다!

 

참고 사이트

 

[FlatBuffers] 플랫버퍼란 무엇인가?

FlatBuffers FlatBuffers 는 C, C++, C#, GO, Java, JavaScript, Lobster, Lua, TypeScript, PHP, Python, Rust를 위한 크로스 플랫폼 직렬화 라이브러리 입니다. FlatBuffers 플랫버퍼를 사용하는 이유?패킹/언 패킹 없이 직렬화

windowshyun.tistory.com

 

댓글