Added optional object based API for C++.

Change-Id: If927f3ea3fb3723088fa287f24bdd1ad43c8d1d1
Tested: on Linux.
parent b22db6e8
......@@ -40,6 +40,8 @@ flatsamplebinary
flatsamplebinary.exe
flatsampletext
flatsampletext.exe
grpctest
grpctest.exe
snapshot.sh
tests/go_gen
tests/monsterdata_java_wire.mon
......
......@@ -6,8 +6,10 @@ project(FlatBuffers)
option(FLATBUFFERS_CODE_COVERAGE "Enable the code coverage build option." OFF)
option(FLATBUFFERS_BUILD_TESTS "Enable the build of tests and samples." ON)
option(FLATBUFFERS_INSTALL "Enable the installation of targets." ON)
option(FLATBUFFERS_BUILD_FLATLIB "Enable the build of the flatbuffers library" ON)
option(FLATBUFFERS_BUILD_FLATC "Enable the build of the flatbuffers compiler" ON)
option(FLATBUFFERS_BUILD_FLATLIB "Enable the build of the flatbuffers library"
ON)
option(FLATBUFFERS_BUILD_FLATC "Enable the build of the flatbuffers compiler"
ON)
option(FLATBUFFERS_BUILD_FLATHASH "Enable the build of flathash" ON)
option(FLATBUFFERS_BUILD_GRPCTEST "Enable the build of grpctest" OFF)
......@@ -95,7 +97,8 @@ set(FlatBuffers_GRPCTest_SRCS
if(APPLE)
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++ -Wall -pedantic -Werror -Wextra")
"${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++ -Wall -pedantic -Werror
-Wextra")
elseif(CMAKE_COMPILER_IS_GNUCXX)
if(CYGWIN)
set(CMAKE_CXX_FLAGS
......@@ -118,7 +121,8 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -std=c++0x -stdlib=libc++ -Wall -pedantic -Werror -Wextra")
"${CMAKE_CXX_FLAGS} -std=c++0x -stdlib=libc++ -Wall -pedantic -Werror
-Wextra")
if(NOT "${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD")
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -lc++abi")
......@@ -165,7 +169,9 @@ function(compile_flatbuffers_schema_to_cpp SRC_FBS)
string(REGEX REPLACE "\\.fbs$" "_generated.h" GEN_HEADER ${SRC_FBS})
add_custom_command(
OUTPUT ${GEN_HEADER}
COMMAND "${FLATBUFFERS_FLATC_EXECUTABLE}" -c --no-includes --gen-mutable -o "${SRC_FBS_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${SRC_FBS}"
COMMAND "${FLATBUFFERS_FLATC_EXECUTABLE}" -c --no-includes --gen-mutable
--gen-object-api -o "${SRC_FBS_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/${SRC_FBS}"
DEPENDS flatc)
endfunction()
......@@ -174,7 +180,8 @@ function(compile_flatbuffers_schema_to_binary SRC_FBS)
string(REGEX REPLACE "\\.fbs$" ".bfbs" GEN_BINARY_SCHEMA ${SRC_FBS})
add_custom_command(
OUTPUT ${GEN_BINARY_SCHEMA}
COMMAND "${FLATBUFFERS_FLATC_EXECUTABLE}" -b --schema -o "${SRC_FBS_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${SRC_FBS}"
COMMAND "${FLATBUFFERS_FLATC_EXECUTABLE}" -b --schema -o "${SRC_FBS_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/${SRC_FBS}"
DEPENDS flatc)
endfunction()
......
......@@ -81,6 +81,11 @@ Additional options:
- `--gen-mutable` : Generate additional non-const accessors for mutating
FlatBuffers in-place.
`--gen-object-api` : Generate an additional object-based API. This API is
more convenient for object construction and mutation than the base API,
at the cost of efficiency (object allocation). Recommended only to be used
if other options are insufficient.
- `--gen-onefile` : Generate single output file (useful for C#)
- `--gen-all`: Generate not just code for the current schema files, but
......
......@@ -85,6 +85,27 @@ convenient accessors for all fields, e.g. `hp()`, `mana()`, etc:
*Note: That we never stored a `mana` value, so it will return the default.*
## Object based API.
FlatBuffers is all about memory efficiency, which is why its base API is written
around using as little as possible of it. This does make the API clumsier
(requiring pre-order construction of all data, and making mutation harder).
For times when efficiency is less important a more convenient object based API
can be used (through `--gen-object-api`) that is able to unpack & pack a
FlatBuffer into objects and standard STL containers, allowing for convenient
construction, access and mutation.
To use:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
auto monsterobj = GetMonster(buffer)->UnPack();
cout << monsterobj->name; // This is now a std::string!
monsterobj->name = "Bob"; // Change the name.
FlatBufferBuilder fbb;
monsterobj->Pack(fbb); // Serialize into new buffer.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## Reflection (& Resizing)
There is experimental support for reflection in FlatBuffers, allowing you to
......
......@@ -1881,6 +1881,9 @@ One way to solve this is to call `ForceDefaults` on a FlatBufferBuilder to
force all fields you set to actually be written. This, of course, increases the
size of the buffer somewhat, but this may be acceptable for a mutable buffer.
If this is not sufficient, other ways of mutating FlatBuffers may be supported
in your language through an object based API (`--gen-object-api`) or reflection.
See the individual language documents for support.
## JSON with FlatBuffers
......
......@@ -984,6 +984,17 @@ FLATBUFFERS_FINAL_CLASS
return CreateVector(v.data(), v.size());
}
// vector<bool> may be implemented using a bit-set, so we can't access it as
// an array. Instead, read elements manually.
// Background: https://isocpp.org/blog/2012/11/on-vectorbool
Offset<Vector<uint8_t>> CreateVector(const std::vector<bool> &v) {
StartVector(v.size(), sizeof(uint8_t));
for (auto i = v.size(); i > 0; ) {
PushElement(static_cast<uint8_t>(v[--i]));
}
return Offset<Vector<uint8_t>>(EndVector(v.size()));
}
/// @brief Serialize values returned by a function into a FlatBuffer `vector`.
/// This is a convenience function that takes care of iteration for you.
/// @tparam T The data type of the `std::vector` elements.
......@@ -1523,6 +1534,12 @@ class Table {
uint8_t data_[1];
};
// Base class for native objects (FlatBuffer data de-serialized into native
// C++ data structures).
// Contains no functionality, purely documentative.
struct NativeTable {
};
// Helper function to test if a field is present, using any of the field
// enums in the generated code.
// `table` must be a generated table type. Since this is a template parameter,
......
......@@ -338,7 +338,8 @@ struct IDLOptions {
bool skip_unexpected_fields_in_json;
bool generate_name_strings;
bool escape_proto_identifiers;
bool generate_object_based_api;
// Possible options for the more general generator below.
enum Language { kJava, kCSharp, kGo, kMAX };
......@@ -358,6 +359,7 @@ struct IDLOptions {
skip_unexpected_fields_in_json(false),
generate_name_strings(false),
escape_proto_identifiers(false),
generate_object_based_api(false),
lang(IDLOptions::kJava) {}
};
......
......@@ -11,8 +11,10 @@ namespace Sample {
struct Vec3;
struct Monster;
struct MonsterT;
struct Weapon;
struct WeaponT;
enum Color {
Color_Red = 0,
......@@ -36,6 +38,21 @@ enum Equipment {
Equipment_MAX = Equipment_Weapon
};
struct EquipmentUnion {
Equipment type;
flatbuffers::NativeTable *table;
EquipmentUnion() : type(Equipment_NONE), table(nullptr) {}
EquipmentUnion(const EquipmentUnion &);
EquipmentUnion &operator=(const EquipmentUnion &);
~EquipmentUnion();
static flatbuffers::NativeTable *UnPack(const void *union_obj, Equipment type);
flatbuffers::Offset<void> Pack(flatbuffers::FlatBufferBuilder &_fbb) const;
WeaponT *AsWeapon() { return type == Equipment_Weapon ? reinterpret_cast<WeaponT *>(table) : nullptr; }
};
inline const char **EnumNamesEquipment() {
static const char *names[] = { "NONE", "Weapon", nullptr };
return names;
......@@ -52,6 +69,8 @@ MANUALLY_ALIGNED_STRUCT(4) Vec3 FLATBUFFERS_FINAL_CLASS {
float z_;
public:
Vec3() { memset(this, 0, sizeof(Vec3)); }
Vec3(const Vec3 &_o) { memcpy(this, &_o, sizeof(Vec3)); }
Vec3(float _x, float _y, float _z)
: x_(flatbuffers::EndianScalar(_x)), y_(flatbuffers::EndianScalar(_y)), z_(flatbuffers::EndianScalar(_z)) { }
......@@ -64,6 +83,17 @@ MANUALLY_ALIGNED_STRUCT(4) Vec3 FLATBUFFERS_FINAL_CLASS {
};
STRUCT_END(Vec3, 12);
struct MonsterT : public flatbuffers::NativeTable {
std::unique_ptr<Vec3> pos;
int16_t mana;
int16_t hp;
std::string name;
std::vector<uint8_t> inventory;
Color color;
std::vector<std::unique_ptr<WeaponT>> weapons;
EquipmentUnion equipped;
};
struct Monster FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
enum {
VT_POS = 4,
......@@ -112,6 +142,7 @@ struct Monster FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
VerifyEquipment(verifier, equipped(), equipped_type()) &&
verifier.EndTable();
}
std::unique_ptr<MonsterT> UnPack() const;
};
struct MonsterBuilder {
......@@ -170,6 +201,13 @@ inline flatbuffers::Offset<Monster> CreateMonster(flatbuffers::FlatBufferBuilder
return CreateMonster(_fbb, pos, mana, hp, name ? 0 : _fbb.CreateString(name), inventory ? 0 : _fbb.CreateVector<uint8_t>(*inventory), color, weapons ? 0 : _fbb.CreateVector<flatbuffers::Offset<Weapon>>(*weapons), equipped_type, equipped);
}
inline flatbuffers::Offset<Monster> CreateMonster(flatbuffers::FlatBufferBuilder &_fbb, const MonsterT *_o);
struct WeaponT : public flatbuffers::NativeTable {
std::string name;
int16_t damage;
};
struct Weapon FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
enum {
VT_NAME = 4,
......@@ -186,6 +224,7 @@ struct Weapon FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
VerifyField<int16_t>(verifier, VT_DAMAGE) &&
verifier.EndTable();
}
std::unique_ptr<WeaponT> UnPack() const;
};
struct WeaponBuilder {
......@@ -216,6 +255,48 @@ inline flatbuffers::Offset<Weapon> CreateWeapon(flatbuffers::FlatBufferBuilder &
return CreateWeapon(_fbb, name ? 0 : _fbb.CreateString(name), damage);
}
inline flatbuffers::Offset<Weapon> CreateWeapon(flatbuffers::FlatBufferBuilder &_fbb, const WeaponT *_o);
inline std::unique_ptr<MonsterT> Monster::UnPack() const {
auto _o = new MonsterT();
{ auto _e = pos(); if (_e) _o->pos = std::unique_ptr<Vec3>(new Vec3(*_e)); };
{ auto _e = mana(); _o->mana = _e; };
{ auto _e = hp(); _o->hp = _e; };
{ auto _e = name(); if (_e) _o->name = _e->str(); };
{ auto _e = inventory(); if (_e) { for (size_t _i = 0; _i < _e->size(); _i++) { _o->inventory.push_back(_e->Get(_i)); } } };
{ auto _e = color(); _o->color = _e; };
{ auto _e = weapons(); if (_e) { for (size_t _i = 0; _i < _e->size(); _i++) { _o->weapons.push_back(_e->Get(_i)->UnPack()); } } };
{ auto _e = equipped_type(); _o->equipped.type = _e; };
{ auto _e = equipped(); if (_e) _o->equipped.table = EquipmentUnion::UnPack(_e, equipped_type()); };
return std::unique_ptr<MonsterT>(_o);
}
inline flatbuffers::Offset<Monster> CreateMonster(flatbuffers::FlatBufferBuilder &_fbb, const MonsterT *_o) {
return CreateMonster(_fbb,
_o->pos ? _o->pos.get() : 0,
_o->mana,
_o->hp,
_o->name.size() ? _fbb.CreateString(_o->name) : 0,
_o->inventory.size() ? _fbb.CreateVector(_o->inventory) : 0,
_o->color,
_o->weapons.size() ? _fbb.CreateVector<flatbuffers::Offset<Weapon>>(_o->weapons.size(), [&](size_t i) { return CreateWeapon(_fbb, _o->weapons[i].get()); }) : 0,
_o->equipped.type,
_o->equipped.Pack(_fbb));
}
inline std::unique_ptr<WeaponT> Weapon::UnPack() const {
auto _o = new WeaponT();
{ auto _e = name(); if (_e) _o->name = _e->str(); };
{ auto _e = damage(); _o->damage = _e; };
return std::unique_ptr<WeaponT>(_o);
}
inline flatbuffers::Offset<Weapon> CreateWeapon(flatbuffers::FlatBufferBuilder &_fbb, const WeaponT *_o) {
return CreateWeapon(_fbb,
_o->name.size() ? _fbb.CreateString(_o->name) : 0,
_o->damage);
}
inline bool VerifyEquipment(flatbuffers::Verifier &verifier, const void *union_obj, Equipment type) {
switch (type) {
case Equipment_NONE: return true;
......@@ -224,6 +305,29 @@ inline bool VerifyEquipment(flatbuffers::Verifier &verifier, const void *union_o
}
}
inline flatbuffers::NativeTable *EquipmentUnion::UnPack(const void *union_obj, Equipment type) {
switch (type) {
case Equipment_NONE: return nullptr;
case Equipment_Weapon: return reinterpret_cast<const Weapon *>(union_obj)->UnPack().release();
default: return nullptr;
}
}
inline flatbuffers::Offset<void> EquipmentUnion::Pack(flatbuffers::FlatBufferBuilder &_fbb) const {
switch (type) {
case Equipment_NONE: return 0;
case Equipment_Weapon: return CreateWeapon(_fbb, reinterpret_cast<const WeaponT *>(table)).Union();
default: return 0;
}
}
inline EquipmentUnion::~EquipmentUnion() {
switch (type) {
case Equipment_Weapon: delete reinterpret_cast<WeaponT *>(table); break;
default:;
}
}
inline const MyGame::Sample::Monster *GetMonster(const void *buf) { return flatbuffers::GetRoot<MyGame::Sample::Monster>(buf); }
inline Monster *GetMutableMonster(void *buf) { return flatbuffers::GetMutableRoot<Monster>(buf); }
......
......@@ -123,6 +123,7 @@ static void Error(const std::string &err, bool usage, bool show_exe_name) {
" --gen-onefile Generate single output file for C#.\n"
" --gen-name-strings Generate type name functions for C++.\n"
" --escape-proto-ids Disable appending '_' in namespaces names.\n"
" --gen-object-api Generate an additional object-based API\n"
" --raw-binary Allow binaries without file_indentifier to be read.\n"
" This may crash flatc given a mismatched schema.\n"
" --proto Input is a .proto, translate to .fbs.\n"
......@@ -179,6 +180,8 @@ int main(int argc, const char *argv[]) {
opts.mutable_buffer = true;
} else if(arg == "--gen-name-strings") {
opts.generate_name_strings = true;
} else if(arg == "--gen-object-api") {
opts.generate_object_based_api = true;
} else if(arg == "--gen-all") {
opts.generate_all = true;
opts.include_dependence_headers = false;
......
This diff is collapsed.
......@@ -12,6 +12,6 @@
:: See the License for the specific language governing permissions and
:: limitations under the License.
..\flatc.exe --cpp --java --csharp --go --binary --python --js --php --grpc --gen-mutable --no-includes monster_test.fbs monsterdata_test.json
..\flatc.exe --cpp --java --csharp --go --binary --python --js --php --grpc --gen-mutable --gen-object-api --no-includes monster_test.fbs monsterdata_test.json
..\flatc.exe --cpp --java --csharp --go --binary --python --js --php --gen-mutable -o namespace_test namespace_test\namespace_test1.fbs namespace_test\namespace_test2.fbs
..\flatc.exe --binary --schema monster_test.fbs
......@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
../flatc --cpp --java --csharp --go --binary --python --js --php --grpc --gen-mutable --no-includes monster_test.fbs monsterdata_test.json
../flatc --cpp --java --csharp --go --binary --python --js --php --grpc --gen-mutable --gen-object-api --no-includes monster_test.fbs monsterdata_test.json
../flatc --cpp --java --csharp --go --binary --python --js --php --gen-mutable -o namespace_test namespace_test/namespace_test1.fbs namespace_test/namespace_test2.fbs
../flatc --binary --schema monster_test.fbs
This diff is collapsed.
......@@ -33,6 +33,8 @@ MANUALLY_ALIGNED_STRUCT(4) StructInNestedNS FLATBUFFERS_FINAL_CLASS {
int32_t b_;
public:
StructInNestedNS() { memset(this, 0, sizeof(StructInNestedNS)); }
StructInNestedNS(const StructInNestedNS &_o) { memcpy(this, &_o, sizeof(StructInNestedNS)); }
StructInNestedNS(int32_t _a, int32_t _b)
: a_(flatbuffers::EndianScalar(_a)), b_(flatbuffers::EndianScalar(_b)) { }
......
......@@ -154,4 +154,12 @@ inline flatbuffers::Offset<SecondTableInA> CreateSecondTableInA(flatbuffers::Fla
} // namespace NamespaceA
namespace NamespaceC {
} // namespace NamespaceC
namespace NamespaceA {
} // namespace NamespaceA
#endif // FLATBUFFERS_GENERATED_NAMESPACETEST2_NAMESPACEA_H_
......@@ -155,7 +155,8 @@ flatbuffers::unique_ptr_t CreateFlatBufferTest(std::string &buffer) {
}
// example of accessing a buffer loaded in memory:
void AccessFlatBufferTest(const uint8_t *flatbuf, size_t length) {
void AccessFlatBufferTest(const uint8_t *flatbuf, size_t length,
bool pooled = true) {
// First, verify the buffers integrity (optional)
flatbuffers::Verifier verifier(flatbuf, length);
......@@ -218,9 +219,11 @@ void AccessFlatBufferTest(const uint8_t *flatbuf, size_t length) {
TEST_EQ(vecofstrings->Length(), 4U);
TEST_EQ_STR(vecofstrings->Get(0)->c_str(), "bob");
TEST_EQ_STR(vecofstrings->Get(1)->c_str(), "fred");
// These should have pointer equality because of string pooling.
TEST_EQ(vecofstrings->Get(0)->c_str(), vecofstrings->Get(2)->c_str());
TEST_EQ(vecofstrings->Get(1)->c_str(), vecofstrings->Get(3)->c_str());
if (pooled) {
// These should have pointer equality because of string pooling.
TEST_EQ(vecofstrings->Get(0)->c_str(), vecofstrings->Get(2)->c_str());
TEST_EQ(vecofstrings->Get(1)->c_str(), vecofstrings->Get(3)->c_str());
}
auto vecofstrings2 = monster->testarrayofstring2();
if (vecofstrings2) {
......@@ -305,6 +308,77 @@ void MutateFlatBuffersTest(uint8_t *flatbuf, std::size_t length) {
AccessFlatBufferTest(flatbuf, length);
}
// Unpack a FlatBuffer into objects.
void ObjectFlatBuffersTest(uint8_t *flatbuf, std::size_t length) {
// Turn a buffer into C++ objects.
auto monster1 = GetMonster(flatbuf)->UnPack();
// Re-serialize the data.
flatbuffers::FlatBufferBuilder fbb1;
fbb1.Finish(CreateMonster(fbb1, monster1.get()), MonsterIdentifier());
// Unpack again, and re-serialize again.
auto monster2 = GetMonster(fbb1.GetBufferPointer())->UnPack();
flatbuffers::FlatBufferBuilder fbb2;
fbb2.Finish(CreateMonster(fbb2, monster2.get()), MonsterIdentifier());
// Now we've gone full round-trip, the two buffers should match.
auto len1 = fbb1.GetSize();
auto len2 = fbb2.GetSize();
TEST_EQ(len1, len2);
TEST_EQ(memcmp(fbb1.GetBufferPointer(), fbb2.GetBufferPointer(),
len1), 0);
// Test it with the original buffer test to make sure all data survived.
AccessFlatBufferTest(fbb2.GetBufferPointer(), len2, false);
// Test accessing fields, similar to AccessFlatBufferTest above.
TEST_EQ(monster2->hp, 80);
TEST_EQ(monster2->mana, 150); // default
TEST_EQ_STR(monster2->name.c_str(), "MyMonster");
auto &pos = monster2->pos;
TEST_NOTNULL(pos);
TEST_EQ(pos->z(), 3);
TEST_EQ(pos->test3().a(), 10);
TEST_EQ(pos->test3().b(), 20);
auto &inventory = monster2->inventory;
TEST_EQ(inventory.size(), 10UL);
unsigned char inv_data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
for (auto it = inventory.begin(); it != inventory.end(); ++it)
TEST_EQ(*it, inv_data[it - inventory.begin()]);
TEST_EQ(monster2->color, Color_Blue);
auto monster3 = monster2->test.AsMonster();
TEST_NOTNULL(monster3);
TEST_EQ_STR(monster3->name.c_str(), "Fred");
auto &vecofstrings = monster2->testarrayofstring;
TEST_EQ(vecofstrings.size(), 4U);
TEST_EQ_STR(vecofstrings[0].c_str(), "bob");
TEST_EQ_STR(vecofstrings[1].c_str(), "fred");
auto &vecofstrings2 = monster2->testarrayofstring2;
TEST_EQ(vecofstrings2.size(), 2U);
TEST_EQ_STR(vecofstrings2[0].c_str(), "jane");
TEST_EQ_STR(vecofstrings2[1].c_str(), "mary");
auto &vecoftables = monster2->testarrayoftables;
TEST_EQ(vecoftables.size(), 3U);
TEST_EQ_STR(vecoftables[0]->name.c_str(), "Barney");
TEST_EQ(vecoftables[0]->hp, 1000);
TEST_EQ_STR(vecoftables[1]->name.c_str(), "Fred");
TEST_EQ_STR(vecoftables[2]->name.c_str(), "Wilma");
auto &tests = monster2->test4;
TEST_EQ(tests[0].a(), 10);
TEST_EQ(tests[0].b(), 20);
TEST_EQ(tests[1].a(), 30);
TEST_EQ(tests[1].b(), 40);
}
// example of parsing text straight into a buffer, and generating
// text back from it:
void ParseAndGenerateTextTest() {
......@@ -855,7 +929,7 @@ void ValueTest() {
// Test conversion functions.
TEST_EQ(FloatCompare(TestValue<float>("{ Y:cos(rad(180)) }","float"), -1), true);
// Test negative hex constant.
TEST_EQ(TestValue<int>("{ Y:-0x80 }","int") == -128, true);
}
......@@ -993,6 +1067,8 @@ int main(int /*argc*/, const char * /*argv*/[]) {
MutateFlatBuffersTest(flatbuf.get(), rawbuf.length());
ObjectFlatBuffersTest(flatbuf.get(), rawbuf.length());
#ifndef FLATBUFFERS_NO_FILE_TESTS
ParseAndGenerateTextTest();
ReflectionTest(flatbuf.get(), rawbuf.length());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment