发布于 

FlatBuffer Startup

What’s FlatBuffers?

FlatBuffers is a cross platform serialization library.

How to Use it?

Normally, there are five steps:

(1). Define a schema: Write a schema file to define the structure.

(2). Generate C++(or other languages) code: Use flatc to generate C++ code to access and construct serialized data.

(3). Invoke C++ API to serialize the object(encode): Use FlatBufferBuilder class to construct a flat binary buffer.

(4). Buffer Persistency: Store and send your buffer file.

(5). Invoke C++ API to deserialize(decode): Read the buffer back and obtain the pointer to the root.

Build It From Source

Firstly, let’s start to build it with follow steps.

$ git clone https://github.com/google/flatbuffers.git
$ cd flatbuffers
$ cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -S . -B ./build
$ cd build
$ make -j
$ make output && make install DESTDIR=./output

Check the contents in output/, the file hierarchy of installed package is as follows:

$ tree
.
└── usr
└── local
├── bin
│   └── flatc # ---> Flatbuffer compiler for code generation.
├── include
│   └── flatbuffers
│   ├── allocator.h
│   ├── array.h
│   ├── base.h
│   ├── bfbs_generator.h
│   ├── buffer.h
│   ├── buffer_ref.h
│   ├── code_generators.h
│   ├── default_allocator.h
│   ├── detached_buffer.h
│   ├── flatbuffer_builder.h
│   ├── flatbuffers.h # ---> main header needs to include
│   ├── flatc.h
...
│   └── verifier.h
└── lib
├── cmake
│   └── flatbuffers # ---> cmake config for importing flatbuffer
│   ├── BuildFlatBuffers.cmake
│   ├── FlatBuffersTargets-release.cmake
│   ├── FlatBuffersTargets.cmake
│   ├── FlatcTargets-release.cmake
│   ├── FlatcTargets.cmake
│   ├── flatbuffers-config-version.cmake
│   └── flatbuffers-config.cmake
├── libflatbuffers.a # ---> static library needs to link
└── pkgconfig
└── flatbuffers.pc

10 directories, 41 files

Define The Schema And Generate the C++ header

Write a demo schema: monster.fbs with the reference of the offical tutorial.

namespace MyGame.Sample;

enum Color:byte { Red = 0, Green, Blue = 2 }

union Equipment { Weapon } // Optionally add more tables

struct Vec3 {
x: float; // Here default value is unspecified, given it as 0
y: float;
z: float;
}

table Monster {
pos: Vec3; // Struct
mana: short = 150;
hp: short = 100;
name: string;
friendly: bool = false (deprecated); // Will not genenate access function for deprecated field
inventory: [ubyte]; // Vector of scalars
color: Color = Blue; // Enum
weapons: [Weapon]; // Vector of tables
equipped:Equipment; // Union
path: [Vec3]; // Vector of struct
}

table Weapon {
name: string;
damage: short;
}

// What will be the root table for the serialized data
root_type Monster;

Q: Difference between struct and table?

A: struct is better for data structure that will no change, since they use less memory and have faster lookup.

Use flatc:

$ ./flatc --cpp monster.fbs

Some common options:

  • --cpp: Generated a C++ header.
  • -o PATH: Output all generated files to PATH(absolute or relative), if omited, PATH will be the current directory.

Use FlatBuffers in Your Own CMake Project

(1). Import FlatBuffers as a third-party library.

include(FetchContent)

FetchContent_Declare(
flatbuffers
GIT_REPOSITORY https://github.com/google/flatbuffers.git
GIT_TAG acf39ff056df8c9e5bfa32cf6f7b5e6b87a90544 # version 22.12.06
)

FetchContent_MakeAvailable(flatbuffers)

(2). Use flatc to generate C++ header with schema, and link flatbuffers in demo target.

set(MONSTER_SCHEMA "${CMAKE_CURRENT_LIST_DIR}/monster.fbs")
set(MONSTER_GENERATED_HEADER monster_generated.h)
set(MONSTER_GENERATED_DIR ${CMAKE_CURRENT_BINARY_DIR})

add_custom_command(OUTPUT ${MONSTER_GENERATED_HEADER}
COMMAND flatc --cpp -o ${MONSTER_GENERATED_DIR} ${MONSTER_SCHEMA}
DEPENDS ${MONSTER_SCHEMA} flatc
)
add_custom_target(gen_monster_fbs DEPENDS ${MONSTER_GENERATED_HEADER})

add_executable(flatbuffers_demo flatbuffers_demo.cpp)
target_link_libraries(flatbuffers_demo PRIVATE flatbuffers)
target_include_directories(flatbuffers_demo PRIVATE ${MONSTER_GENERATED_DIR})
add_dependencies(flatbuffers_demo gen_monster_fbs)

(3). Write demo C++ code to implement monster serialization and deserialization.

(a). Monster Serialization

void SerializeMonster(uint8_t *&buf, int &size) {
flatbuffers::FlatBufferBuilder builder(1024);

/* Use basic builder to create all fields in monster */
auto weapon_one_name = builder.CreateString("Sword");
short weapon_one_damage = 3;

auto weapon_two_name = builder.CreateString("Axe");
short weapon_two_damage = 5;

auto sword = CreateWeapon(builder, weapon_one_name, weapon_one_damage);
auto axe = CreateWeapon(builder, weapon_two_name, weapon_two_damage);

auto name = builder.CreateString("Orc");
unsigned char treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto inventory = builder.CreateVector(treasure, 10);

std::vector<flatbuffers::Offset<Weapon>> weapon_vector;
weapon_vector.push_back(sword);
weapon_vector.push_back(axe);
auto weapons = builder.CreateVector(weapon_vector);

Vec3 points[] = {Vec3(1.0f, 2.0f, 3.0f), Vec3(4.0f, 5.0f, 6.0f)};
auto path = builder.CreateVectorOfStructs(points, 2);

auto position = Vec3(1.0f, 2.0f, 3.0f);

int hp = 300;
int mana = 150;

/* Create Monster */
// Usage 1:set all fileds at once
// auto orc = CreateMonster(builder, &position, mana, hp, name,
// inventory, Color_Red, weapons,
// Equipment_Weapon, axe.Union(), path);

// Usage 2: set specified filed as needed
MonsterBuilder monster_builder(builder);
monster_builder.add_pos(&position);
monster_builder.add_hp(hp);
monster_builder.add_name(name);
monster_builder.add_mana(mana);
monster_builder.add_inventory(inventory);
monster_builder.add_color(Color_Red);
monster_builder.add_weapons(weapons);
monster_builder.add_equipped_type(Equipment_Weapon);
monster_builder.add_equipped(axe.Union());
monster_builder.add_path(path);
auto orc = monster_builder.Finish();

/* Invoke Finish() to end the serializing procedure. */
builder.Finish(orc);

/* Get the serializing result: a byte buffer. */
buf = builder.GetBufferPointer();
size = builder.GetSize();
}

(b). Monster Deserialization

void DeserializeMonster(const uint8_t *buf) {
/* Get deserialized root object: monster *//
auto monster = GetMonster(buf);

/* Get each field of monster */
auto pos = monster->pos();
auto hp = monster->hp();
auto name = monster->name();
auto mana = monster->mana();
auto inventory = monster->inventory();
auto color = monster->color();
auto weapons = monster->weapons();
auto equipment_type = monster->equipped_type();
auto path = monster->path();

/* Access each field and print them */
cout << "pos: { x: " << pos->x() << ", y: " << pos->y() << ", z: " << pos->z() << " }" << endl;
cout << "hp: " << hp << endl;
cout << "name: " << name->str() << endl;
cout << "mana: " << mana << endl;

cout << "inventory: { ";
for (decltype(inventory->size()) i = 0; i < inventory->size(); i++) {
cout << static_cast<uint32_t>((*inventory)[i]);
cout << (i < inventory->size() - 1 ? ", " : " ");
}
cout << "}" << endl;

cout << "color: " << EnumNameColor(color) << endl;
cout << "weapons: { ";
for (decltype(weapons->size()) i = 0; i < weapons->size(); i++) {
const auto &weapon = (*weapons)[i];
cout << "{name: " << weapon->name()->str() << ", damage: " << weapon->damage() << "}";
cout << (i < weapons->size() - 1 ? ", " : " ");
}
cout << "}" << endl;

cout << "equipment_type: " << EnumNameEquipment(equipment_type) << endl;
if (equipment_type == MyGame::Sample::Equipment_Weapon) {
auto equipped_weapon = monster->equipped_as_Weapon();
cout << "equipped: weapon {name: " << equipped_weapon->name()->str() << ", damage: " << equipped_weapon->damage() << "}";
}
cout << "}" << endl;

cout << "path: {";
for (decltype(path->size()) i = 0; i < path->size(); i++) {
const auto &path_elem = (*path)[i];

cout << "{ pos: x: " << path_elem->x() << ", y: " << path_elem->y() << ", z: " << path_elem->z() << " }";
cout << (i < path->size() - 1 ? ", " : " ");
}
cout << "}" << endl;
}

Demo output:

Serialize Monster, buf: 0x7f77ae008b3c, size: 196
Deserialize Monster:
pos: { x: 1, y: 2, z: 3 }
hp: 300
name: Orc
mana: 150
inventory: { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
color: Red
weapons: { {name: Sword, damage: 3}, {name: Axe, damage: 5} }
equipment_type: Weapon
equipped: weapon {name: Axe, damage: 5}}
path: {{ pos: x: 1, y: 2, z: 3 }, { pos: x: 4, y: 5, z: 6 } }

The complete code is available at github VincentZhu007/flatbuffers_demo.

More Reference