From 7d9779fc677372bb5af7b3f10f387116109e67ac Mon Sep 17 00:00:00 2001 From: =?utf8?q?Mikkel=20Fahn=C3=B8e=20J=C3=B8rgensen?= Date: Mon, 28 Mar 2016 22:52:10 +0200 Subject: [PATCH] Move some C specifics out of tutorial and clarify platform support --- docs/source/CUsage.md | 180 ++++++++++++++++++++++++++++++++++++++++-- docs/source/FlatBuffers.md | 2 +- docs/source/Support.md | 2 +- docs/source/Tutorial.md | 190 +++++++-------------------------------------- 4 files changed, 202 insertions(+), 172 deletions(-) diff --git a/docs/source/CUsage.md b/docs/source/CUsage.md index 9397e04..4340387 100644 --- a/docs/source/CUsage.md +++ b/docs/source/CUsage.md @@ -19,31 +19,193 @@ project. - The C Builder Interface (advanced) +## Supported Platforms + +Ubuntu and OS-X are regularly tested during releases. Centos 7.1 +has also been tested. Cross compilation to little-endian ARM has been +reported to work with warnings. + +Windows has not been tested. The `include/flatcc/portable` library is +intended to abstract platform differences, including Windows. User +feedback and patches are welcome. + +Big endian platforms have not been tested and may contain bugs, but care +has been taken to provide support for it. + + +## Modular Object Creation + +In the tutorial we used the call `Monster_create_as_root` to create the +root buffer object since this is easier in simple use cases. Sometimes +we need more modularity so we can reuse a function to create nested +tables and root tables the same way. For this we need the +`flatcc_builder_buffer_create_call`. It is best to keep `flatcc_builder` +calls isolated at the top driver level, so we get: + +
+~~~{.c} + ns(Monster_ref_t) create_orc(flatcc_builder_t *B) + { + // ... same as in the tutorial. + return s(Monster_create(B, ...)); + } + + void create_monster_buffer() + { + uint8_t *buf; + size_t size; + flatcc_builder_t builder, *B; + + // Initialize the builder object. + B = &builder; + flatcc_builder_init(B); + // Only use `buffer_create` without `create/start/end_as_root`. + flatcc_builder_buffer_create(create_orc(B)); + // Allocate and copy buffer to user memory. + buf = flatcc_builder_finalize_buffer(B, &size); + // ... write the buffer to disk or network, or something. + + free(buf); + flatcc_builder_clear(B); + } +~~~ +
+ +The same principle applies with `start/end` vs `start/end_as_root` in +the top-down approach. + + +## Top Down Example + +The tutorial uses a bottom up approach. In C it is also possible to use +a top-down approach by starting and ending objects nested within each +other. In the tutorial there is no deep nesting, so the difference is +limited, but it shows the idea: + +
+
+~~~{.c} + uint8_t treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + size_t treasure_count = c_vec_len(treasure); + ns(Weapon_ref_t) axe; + + // NOTE: if we use end_as_root, we MUST also start as root. + ns(Monster_start_as_root(B)); + ns(Monster_pos_create(B, 1.0f, 2.0f, 3.0f)); + ns(Monster_hp_add(B, 300)); + ns(Monster_mana_add(B, 150)); + // We use create_str instead of add because we have no existing string reference. + ns(Monster_name_create_str(B, "Orc")); + // Again we use create because we no existing vector object, only a C-array. + ns(Monster_inventory_create(B, treasure, treasure_count)); + ns(Monster_color_add(B, ns(Color_Red))); + if (1) { + ns(Monster_weapons_start(B)); + ns(Monster_weapons_push_create(B, flatbuffers_string_create_str(B, "Sword"), 3)); + // We reuse the axe object later. Note that we dereference a pointer + // because push always returns a short-term pointer to the stored element. + // We could also have created the axe object first and simply pushed it. + axe = *ns(Monster_weapons_push_create(B, flatbuffers_string_create_str(B, "Axe"), 5)); + ns(Monster_weapons_end(B)); + } else { + // We can have more control with the table elements added to a vector: + // + ns(Monster_weapons_start(B)); + ns(Monster_weapons_push_start(B)); + ns(Weapon_name_create_str(B, "Sword")); + ns(Weapon_damage_add(B, 3)); + ns(Monster_weapons_push_end(B)); + ns(Monster_weapons_push_start(B)); + ns(Monster_weapons_push_start(B)); + ns(Weapon_name_create_str(B, "Axe")); + ns(Weapon_damage_add(B, 5)); + axe = *ns(Monster_weapons_push_end(B)); + ns(Monster_weapons_end(B)); + } + // Unions can get their type by using a type-specific add/create/start method. + ns(Monster_equipped_Weapon_add(B, axe)); + + ns(Monster_end_as_root(B)); +~~~ +
+ + ## Basic Reflection The C-API does support reading binary schema (.bfbs) files via code generated from the `reflection.fbs` schema, and an [example usage](https://github.com/dvidelabs/flatcc/tree/master/samples/reflection) -shows how to use this. The schema files are pre-generated +shows how to use this. The reflection schema files are pre-generated in the [runtime distribution](https://github.com/dvidelabs/flatcc/tree/master/include/flatcc/reflection). -## Mutating Reflection +## Mutations and Reflection -The C-API does not support mutating reflection like C++ does. +The C-API does not support mutating reflection like C++ does, nor does +the reader interface support mutating scalars (and it is generally +unsafe to do so even after verification). -Although the following isn't reflection, it is possible to create new -buffers using complex objects from existing buffers as source. This can -be very efficient due to direct copy semantics without endian conversion or -temporary stack allocation. +The generated reader interface supports sorting vectors in-place after +casting them to a mutating type because it is not practical to do so +while building a buffer. This is covered in the builder documentation. +The reflection example makes use of this feature to look up objects by +name. + +It is possible to build new buffers using complex objects from existing +buffers as source. This can be very efficient due to direct copy +semantics without endian conversion or temporary stack allocation. Scalars, structs and strings can be used as source, as well vectors of these. It is currently not possible to use an existing table or vector of table as source, but it would be possible to add support for this at some -point. Vectors of strings +point. + + +## Namespaces + +The `FLATBUFFERS_WRAP_NAMESPACE` approach used in the tutorial is convenient +when each function has a very long namespace prefix. But it isn't always +we the best approach. If the namespace is absent, or very simple and +informative, we might as well use the prefix directly. The +[reflection example](https://github.com/dvidelabs/flatcc/blob/master/samples/reflection/bfbs2json.c) +mentioned above uses this approach. + +## Checking for Present Members + +Not all languages support testing if a field is present, but in C we can +elaborate the reader section of the tutorial with tests for this. Recall +that `mana` was set to the default value `150` and therefore shouldn't +be present. + +
+~~~{.c} + int hp_present = ns(Monster_hp_is_present(monster)); // 1 + int mana_present = ns(Monster_mana_is_present(monster)); // 0 +~~~ +
+ +## Alternative ways to add a Union + +In the tutorial we used a single call to add a union. Here we show +different ways to accomplish the same thing. The last form is rarely +used, but is the low-level way to do it. It can be used to group small +values together in the table by adding type and data at different +points in time. + +
+~~~{.c} + ns(Equipment_union_ref_t) equipped = ns(Equipment_as_Weapon(axe)); + ns(Monster_equipped_add(B, equipped)); + // or alternatively + ns(Monster_equipped_Weapon_add(B, axe); + // or alternatively + ns(Monster_equipped_type_add(B, ns(Equipment_Weapon)); + ns(Monster_equipped_add_member(B, axe)); +~~~ +
## Why not integrate with the `flatc` tool? @@ -55,3 +217,5 @@ a complicated intermediate representation would have to be invented. Neither of these alternatives are very attractive, and it isn't a big deal to use the `flatcc` tool instead of `flatc` given that the FlatBuffers C runtime library needs to be made available regardless. + + diff --git a/docs/source/FlatBuffers.md b/docs/source/FlatBuffers.md index 445121c..7da0990 100644 --- a/docs/source/FlatBuffers.md +++ b/docs/source/FlatBuffers.md @@ -131,7 +131,7 @@ sections provide a more in-depth usage guide. in your own programs. - How to [use the generated Go code](@ref flatbuffers_guide_use_go) in your own programs. -- How to [use the generated C code](@ref flatbuffers_guide_use_c) in your +- How to [use FlatBuffers in C with `flatcc`](@ref flatbuffers_guide_use_c) in your own programs. - [Support matrix](@ref flatbuffers_support) for platforms/languages/features. - Some [benchmarks](@ref flatbuffers_benchmarks) showing the advantage of diff --git a/docs/source/Support.md b/docs/source/Support.md index 270f88b..23bf41a 100755 --- a/docs/source/Support.md +++ b/docs/source/Support.md @@ -28,7 +28,7 @@ Buffer verifier | Yes | No | No | No | No | No Testing: basic | Yes | Yes | Yes | Yes | Yes | Yes | Yes | ? | ? Testing: fuzz | Yes | No | No | Yes | Yes | No | No | ? | ? Performance: | Superb | Great | Great | Great | Ok | ? |Superb| ? | ? -Platform: Windows | VS2010 | Yes | Yes | ? | ? | ? | No | ? | ? +Platform: Windows | VS2010 | Yes | Yes | ? | ? | ? | ? | ? | ? Platform: Linux | GCC282 | Yes | ? | Yes | Yes | ? | Yes | ? | ? Platform: OS X | Xcode4 | ? | ? | ? | Yes | ? | Yes | ? | ? Platform: Android | NDK10d | Yes | ? | ? | ? | ? | ? | ? | ? diff --git a/docs/source/Tutorial.md b/docs/source/Tutorial.md index 4547a21..fdf7aca 100644 --- a/docs/source/Tutorial.md +++ b/docs/source/Tutorial.md @@ -234,7 +234,6 @@ FlatBuffer compiler. Once `flatc` is built successfully, compile the schema for your language:
- *Note: If you're working in C, you need to use the separate project [FlatCC](https://github.com/dvidelabs/flatcc) which contains a schema compiler and runtime library in C for C.*
See [flatcc build instructions](https://github.com/dvidelabs/flatcc#building). @@ -391,14 +390,11 @@ The first step is to import/include the library, generated files, etc. #include "monster_builder.h" // Generated by `flatcc`. // Convenient namespace macro to manage long namespace prefix. + #undef ns #define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Sample, x) // Specified in the schema. - // Convenient common namespace macro. - #define nsc(x) FLATBUFFERS_WRAP_NAMESPACE(flatbuffers, x) + // A helper to simplify creating vectors from C-arrays. #define c_vec_len(V) (sizeof(V)/sizeof((V)[0])) - - // The ns macro makes it possible to write `ns(Monster_create(...))` - // instead of `MyGame_Sample_Monster_create(...)`. ~~~
@@ -577,10 +573,10 @@ our `orc` Monster, lets create some `Weapon`s: a `Sword` and an `Axe`.
~~~{.c} - ns(Weapon_ref_t) weapon_one_name = nsc(string_create_str(B, "Sword")); + ns(Weapon_ref_t) weapon_one_name = flatbuffers_string_create_str(B, "Sword"); uint16_t weapon_one_damage = 3; - ns(Weapon_ref_t) weapon_two_name = nsc(string_create_str(B, "Axe")); + ns(Weapon_ref_t) weapon_two_name = flatbuffers_string_create_str(B, "Axe"); uint16_t weapon_two_damage = 5; ns(Weapon_ref_t) sword = ns(Weapon_create(B, weapon_one_name, weapon_one_damage)); @@ -693,14 +689,14 @@ traversal. This is generally easy to do on any tree structures. ~~~{.c} // Serialize a name for our monster, called "Orc". // The _str suffix indicates the source is an ascii-z string. - nsc(string_ref_t) name = nsc(string_create_str(B, "Orc")); + flatbuffers_string_ref_t name = flatbuffers_string_create_str(B, "Orc"); // Create a `vector` representing the inventory of the Orc. Each number // could correspond to an item that can be claimed after he is slain. uint8_t treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - nsc(uint8_vec_ref_t) inventory; + flatbuffers_uint8_vec_ref_t inventory; // `c_vec_len` is the convenience macro we defined earlier. - inventory = nsc(uint8_vec_create(B, treasure, c_vec_len(treasure))); + inventory = flatbuffers_uint8_vec_create(B, treasure, c_vec_len(treasure)); ~~~
@@ -718,13 +714,6 @@ and `Axe`). These are both FlatBuffer `table`s, whose offsets we now store in memory. Therefore we can create a FlatBuffer `vector` to contain these offsets. -
-*Note: If you're using C, there is also an often shorter top-down -approach that avoids storing temporary references because the runtime -has an internal stack. The top-down version is shown at the end of build -section.* -
-
~~~{.cpp} // Place the weapons into a `std::vector`, then convert that into a FlatBuffer `vector`. @@ -794,10 +783,7 @@ section.*
~~~{.c} - // Here we use a top-down approach locally to build a Weapons vector - // in-place instead of creating a temporary external vector to use - // as argument like we did with the `inventory` earlier on, but the - // overall approach is still bottom-up. + // We use the internal builder stack to implement a dynamic vector. ns(Weapon_vec_start(B)); ns(Weapon_vec_push(B, sword)); ns(Weapon_vec_push(B, axe)); @@ -970,31 +956,23 @@ can serialize the monster itself: uint16_t hp = 300; uint16_t mana = 150; - // Create the equipment union. In the C++ language API this is given - // as two arguments to the create call, or as two separate add - // operations for the type and the table reference. In C we create - // a single union value that carries both the type and reference. + // Define an equipment union. `create` calls in C has a single + // argument for unions where C++ has both a type and a data argument. ns(Equipment_union_ref_t) equipped = ns(Equipment_as_Weapon(axe)); - ns(Monster_create_as_root(B, &pos, mana, hp, name, inventory, ns(Color_Red), weapons, equipped)); ~~~
-
-*Note: in C we use `create_as_root` instead of the also valid `create` call -because it simplfies constructing the root object.* -
- -
-
*Note: Since we are passing `150` as the `mana` field, which happens to be the default value, the field will not actually be written to the buffer, since the default value will be returned on query anyway. This is a nice space savings, especially if default values are common in your data. It also means that you do not need to be worried of adding a lot of fields that are only used in a small number of instances, as it will not bloat the buffer if unused.* -

+ +
+
If you do not wish to set every field in a `table`, it may be more convenient to manually set each field of your monster, instead of calling `CreateMonster()`. The following snippet is functionally equivalent to the above code, but provides @@ -1016,14 +994,6 @@ a bit more flexibility. ~~~
-
-*Note: Since we are passing `150` as the `mana` field, which happens to be the -default value, the field will not actually be written to the buffer, since the -default value will be returned on query anyway. This is a nice space savings, -especially if default values are common in your data. It also means that you do -not need to be worried of adding a lot of fields that are only used in a small -number of instances, as it will not bloat the buffer if unused.* -

If you do not wish to set every field in a `table`, it may be more convenient to manually set each field of your monster, instead of calling `create_monster_as_root()`. The following snippet is functionally equivalent to the above code, but provides @@ -1058,11 +1028,6 @@ Second, is the `union`'s data. In our example, the last two things we added to our `Monster` were the `Equipped Type` and the `Equipped` union itself. -
-*Note: In C, several different helpers make these two fields appear as -one field, but they can be added separately.* -
- Here is a repetition these lines, to help highlight them more clearly:
@@ -1110,62 +1075,8 @@ Here is a repetition these lines, to help highlight them more clearly:
~~~{.c} - ns(Equipment_union_ref_t) equipped = ns(Equipment_as_Weapon(axe)); - ns(Monster_equipped_add(B, equipped)); - // or alternatively - ns(Monster_equipped_Weapon_add(B, axe); - // or alternatively - ns(Monster_equipped_type_add(B, ns(Equipment_Weapon)); - ns(Monster_equipped_add_member(B, axe)); -~~~ -
- -
-Here is an alternative top-down approach unique to the C builder -library. -
-~~~{.c} - uint8_t treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - size_t treasure_count = c_vec_len(treasure); - ns(Weapon_ref_t) axe; - - // NOTE: if we use end_as_root, we MUST also start as root. - ns(Monster_start_as_root(B)); - ns(Monster_pos_create(B, 1.0f, 2.0f, 3.0f)); - ns(Monster_hp_add(B, 300)); - ns(Monster_mana_add(B, 150)); - // We use create_str instead of add because we have no existing string reference. - ns(Monster_name_create_str(B, "Orc")); - // Again we use create because we no existing vector object, only a C-array. - ns(Monster_inventory_create(B, treasure, treasure_count)); - ns(Monster_color_add(B, ns(Color_Red))); - if (1) { - ns(Monster_weapons_start(B)); - ns(Monster_weapons_push_create(B, nsc(string_create_str(B, "Sword")), 3)); - // We reuse the axe object later. Note that we dereference a pointer - // because push always returns a short-term pointer to the stored element. - // We could also have created the axe object first and simply pushed it. - axe = *ns(Monster_weapons_push_create(B, nsc(string_create_str(B, "Axe")), 5)); - ns(Monster_weapons_end(B)); - } else { - // We can have more control with the table elements added to a vector: - // - ns(Monster_weapons_start(B)); - ns(Monster_weapons_push_start(B)); - ns(Weapon_name_create_str(B, "Sword")); - ns(Weapon_damage_add(B, 3)); - ns(Monster_weapons_push_end(B)); - ns(Monster_weapons_push_start(B)); - ns(Monster_weapons_push_start(B)); - ns(Weapon_name_create_str(B, "Axe")); - ns(Weapon_damage_add(B, 5)); - axe = *ns(Monster_weapons_push_end(B)); - ns(Monster_weapons_end(B)); - } - // Unions can get their type by using a type-specific add/create/start method. - ns(Monster_equipped_Weapon_add(B, axe)); - - ns(Monster_end_as_root(B)); + // Add union type and data simultanously. + ns(Monster_equipped_Weapon_add(B, axe)); ~~~
@@ -1173,12 +1084,6 @@ After you have created your buffer, you will have the offset to the root of the data in the `orc` variable, so you can finish the buffer by calling the appropriate `finish` method. -
-*Note: C does not have a `finish` call, and it is not needed when we use -`create_as_root` or `start/end_as_root`. For the sake of modularity, it -is sometimes useful to create an object without knowing if it will be a -root. We show this below, but do NOT mix it with the `_as_root` calls.* -
~~~{.cpp} @@ -1229,10 +1134,7 @@ root. We show this below, but do NOT mix it with the `_as_root` calls.*
~~~{.c} - // Alternative approach separating object creation from being root object. - ns(Monster_ref_t) orc = ns(Monster_create(B, ...)); - // `flatcc_` calls should be isolated to top-level driver logic. - flatcc_builder_buffer_create(orc); + // Because we used `Monster_create_as_root`, we do not need a `finish` call in C`. ~~~
@@ -1317,12 +1219,6 @@ deserialize a FlatBuffer. This section requires the same import/include, namespace, etc. requirements as before: -
-*Note: In C there is a separate include file for the reader which is automatically -included by the generated builder header. A standalone reader only depends on header -files while the builder must link with a small runtime library.* -
-
~~~{.cpp} #include "monster_generate.h" // This was generated by `flatc`. @@ -1401,10 +1297,11 @@ files while the builder must link with a small runtime library.*
~~~{.c} + // Only needed if we don't have `#include "monster_builder.h"`. #include "monster_reader.h" + #undef ns #define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Sample, x) // Specified in the schema. - #define nsc(x) FLATBUFFERS_WRAP_NAMESPACE(flatbuffers, x) ~~~
@@ -1565,29 +1462,14 @@ accessors for all non-`deprecated` fields. For example:
~~~{.c} uint16_t hp = ns(Monster_hp(monster)); - // Since 150 is the default, we are reading a value that wasn't stored. uint16_t mana = ns(Monster_mana(monster)); - // This is just a const char *, but it also supports a fast length operation. - nsc(string_t) name = ns(Monster_name(monster)); - size_t name_len = nsc(string_len(name)); + flatbuffers_string_t name = ns(Monster_name(monster)); ~~~
-
-*Note: In C we can check if a field is present. For example `mana` -should not be present because it was set with a default value or not at -all, but `hp` should be present.* - -~~~{.c} - int hp_present = ns(Monster_hp_is_present(monster)); // 1 - int mana_present = ns(Monster_mana_is_present(monster)); // 0 -~~~ -
- - These should hold `300`, `150`, and `"Orc"` respectively. -*Note: We never stored a value in `mana`, so we got the default value of `150`.* +*Note: The default value `150` wasn't stored in `mana`, but we are still able to retrieve it.* To access sub-objects, in the case of our `pos`, which is a `Vec3`: @@ -1659,13 +1541,6 @@ To access sub-objects, in the case of our `pos`, which is a `Vec3`: float x = ns(Vec3_x(pos)); float y = ns(Vec3_y(pos)); float z = ns(Vec3_z(pos)); - - // or alternatively - ns(Vec3_t) pos_vec; - // `pe` indicates endian conversion from protocol to native. - ns(Vec3_copy_from_pe(&pos_vec, pos)); - x = pos_vec.x; - // ... ~~~
@@ -1722,15 +1597,10 @@ FlatBuffers `vector`.
~~~{.c} - // This is a const uint8_t *, but it shouldn't be accessed directly - // to ensure proper endian conversion. Incidentally the uint8 (ubyte) - // is not sensitive to endianness, so we *could* have accessed it directly. - // The compiler likely optimizes this so that it doesn't matter. - nsc(uint8_vec_t) inv = ns(Monster_inventory(monster)); - size_t inv_len = nsc(uint8_vec_len(inv)); - - // If `inv` was not set, it will be null, but the length is still - // valid to read and will then be zero. + // If `inv` hasn't been set, it will be null. It is valid get + // the length of null which will be 0, useful for iteration. + flatbuffers_uint8_vec_t inv = ns(Monster_inventory(monster)); + size_t inv_len = flatbuffers_uint8_vec_len(inv); ~~~
@@ -1795,7 +1665,7 @@ except your need to handle the result as a FlatBuffer `table`: ~~~{.c} ns(Weapon_vec_t) weapons = ns(Monster_weapons(monster)); size_t weapons_len = ns(Weapon_vec_len(weapons)); - // We don't have to use `nsc(string_t)` as type if we don't need fast length access. + // We can use `const char *` instead of `flatbuffers_string_t`. const char *second_weapon_name = ns(Weapon_name(ns(Weapon_vec_at(weapons, 1)))); uint16_t second_weapon_damage = ns(Weapon_damage(ns(Weapon_vec_at(weapons, 1)))); ~~~ @@ -1917,11 +1787,6 @@ We can access the type to dynamically cast the data as needed (since the ## Mutating FlatBuffers -
-*Note: This section does not fully apply to C which has no generated mutation -interface (except for sorting vectors in-place which is an advanced topic).* -
- As you saw above, typically once you have created a FlatBuffer, it is read-only from that moment on. There are, however, cases where you have just received a FlatBuffer, and you'd like to modify something about it before sending it on to @@ -1984,8 +1849,9 @@ mutators like so: ~~~
-~~~{.php} - +~~~{.c} + ~~~
-- 2.7.4