Move some C specifics out of tutorial and clarify platform support

parent 9b8c91c9
......@@ -19,31 +19,193 @@ project.
- The C Builder Interface (advanced) <https://github.com/dvidelabs/flatcc/blob/master/doc/builder.md>
## 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:
<div class="language-c">
~~~{.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);
}
~~~
</div>
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:
<div class="language-c">
<br>
~~~{.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));
~~~
</div>
## 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.
<div class="language-c">
~~~{.c}
int hp_present = ns(Monster_hp_is_present(monster)); // 1
int mana_present = ns(Monster_mana_is_present(monster)); // 0
~~~
</div>
## 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.
<div class="language-c">
~~~{.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));
~~~
</div>
## 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.
......@@ -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
......
......@@ -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 | ? | ? | ? | ? | ? | ? | ?
......
......@@ -234,7 +234,6 @@ FlatBuffer compiler.
Once `flatc` is built successfully, compile the schema for your language:
<div class="language-c">
*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.*
<br>
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(...)`.
~~~
</div>
......@@ -577,10 +573,10 @@ our `orc` Monster, lets create some `Weapon`s: a `Sword` and an `Axe`.
</div>
<div class="language-c">
~~~{.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));
~~~
</div>
......@@ -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.
<div class="language-c">
*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.*
</div>
<div class="language-cpp">
~~~{.cpp}
// Place the weapons into a `std::vector`, then convert that into a FlatBuffer `vector`.
......@@ -794,10 +783,7 @@ section.*
</div>
<div class="language-c">
~~~{.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));
~~~
</div>
<div class="language-c">
*Note: in C we use `create_as_root` instead of the also valid `create` call
because it simplfies constructing the root object.*
</div>
<div class="language-cpp">
<br>
*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.*
<br><br>
<div class="language-cpp">
<br>
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.
~~~
</div>
<div class="language-c">
<br>
*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.*
<br><br>
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.
<div class="language-c">
*Note: In C, several different helpers make these two fields appear as
one field, but they can be added separately.*
</div>
Here is a repetition these lines, to help highlight them more clearly:
<div class="language-cpp">
......@@ -1110,62 +1075,8 @@ Here is a repetition these lines, to help highlight them more clearly:
</div>
<div class="language-c">
~~~{.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));
~~~
</div>
<div class="language-c">
Here is an alternative top-down approach unique to the C builder
library.
<br>
~~~{.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));
~~~
</div>
......@@ -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.
<div class="language-c">
*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.*
</div>
<div class="language-cpp">
~~~{.cpp}
......@@ -1229,10 +1134,7 @@ root. We show this below, but do NOT mix it with the `_as_root` calls.*
</div>
<div class="language-c">
~~~{.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`.
~~~
</div>
......@@ -1317,12 +1219,6 @@ deserialize a FlatBuffer.
This section requires the same import/include, namespace, etc. requirements as
before:
<div class="language-c">
*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.*
</div>
<div class="language-cpp">
~~~{.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.*
</div>
<div class="language-c">
~~~{.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)
~~~
</div>
......@@ -1565,29 +1462,14 @@ accessors for all non-`deprecated` fields. For example:
<div class="language-c">
~~~{.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));
~~~
</div>
<div class="language-c">
*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
~~~
</div>
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;
// ...
~~~
</div>
......@@ -1722,15 +1597,10 @@ FlatBuffers `vector`.
</div>
<div class="language-c">
~~~{.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);
~~~
</div>
......@@ -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
<div class="language-c">
*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).*
</div>
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:
~~~
</div>
<div class="language-c">
~~~{.php}
<API for in-place mutating FlatBuffers will not be supported in C.>
~~~{.c}
<API for in-place mutating FlatBuffers will not be supported in C
(except in-place vector sorting is possible).>
~~~
</div>
......
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