FlatBuffer如何支持C语言?

99ANYc3cd6
预计阅读时长 22 分钟
位置: 首页 C语言 正文

是的,FlatBuffers 完全支持 C 语言,并且为其提供了官方的、高质量的实现,C++ 版本是第一个实现,而 C 语言版本紧随其后,因为 FlatBuffer 最初就是为游戏开发(C++ 为主)和嵌入式系统(C 语言为主)设计的。

flatbuffer 支持c语言
(图片来源网络,侵删)

下面我将详细介绍 FlatBuffers 在 C 语言中的支持情况、如何使用以及它的优缺点。

为什么 FlatBuffers 适合 C 语言?

FlatBuffer 的设计哲学与 C 语言的需求非常契合:

  • 高性能和零拷贝:这是 FlatBuffers 最大的优势,传统的序列化库(如 Protocol Buffers)需要先反序列化整个消息到一个内部结构体,然后再从该结构体复制数据到你的 C 结构体中,FlatBuffer 允许你直接读取底部的内存缓冲区,无需任何拷贝,这对于内存受限或性能要求极高的 C 语言应用(如嵌入式系统、游戏引擎)至关重要。
  • 内存效率:FlatBuffer 数据是紧凑的二进制格式,没有额外的解析开销,反序列化过程就是简单的内存寻址和类型转换,非常轻量。
  • 无需内存分配:读取 FlatBuffer 数据时,不需要在堆上进行任何内存分配,这对于嵌入式系统等难以管理动态内存的环境是一个巨大的优势。
  • 无版本依赖:生成的 C 头文件不依赖于任何 FlatBuffer 的运行时库(除了一些基础类型和宏定义),这使得它非常适合集成到各种项目中。

如何在 C 语言项目中使用 FlatBuffers?

使用 FlatBuffers 主要分为两个步骤:Schema 定义与代码生成序列化与反序列化

定义 Schema 并生成 C 代码

  1. 安装 FlatBuffers 编译器: 你需要先安装 FlatBuffers 的编译器 flatc,你可以从 GitHub Releases 页面下载预编译的二进制文件,或者从源码编译。

    flatbuffer 支持c语言
    (图片来源网络,侵删)
  2. 创建 .fbs Schema 文件: 这是一种类似 IDL(接口定义语言)的文本格式,用来定义你的数据结构。

    monster.fbs 示例:

    namespace my.game;
    enum Color:byte { Red = 0, Green = 1, Blue = 2 }
    struct Vec3 {
      x:float;
      y:float;
      z:float;
    }
    struct Monster {
      pos:Vec3;
      mana:short = 150;
      hp:short = 100;
      name:string;
      color:Color = Blue;
      inventory:[ubyte]; // A vector of unsigned bytes.
      // An enum that refers to the Color enum.
      // This demonstrates how enums can be used in different contexts.
      friendly:bool = false (deprecated);
    }
  3. 使用 flatc 生成 C 代码: 在命令行中运行以下命令:

    flatc --c monster.fbs

    这会生成一个 monster_generated.h 文件,这个头文件包含了所有必要的 C 结构体、枚举、访问函数和序列化/反序列化函数。

    flatbuffer 支持c语言
    (图片来源网络,侵删)

在 C 代码中使用生成的代码

生成的 monster_generated.h 文件为你提供了所有工具,你不需要手动创建复杂的二进制数据,而是通过一个构建器来完成。

序列化示例 (serialize_monster.c):

#include <stdio.h>
#include <string.h>
#include "monster_generated.h" // 包含生成的头文件
int main() {
    // 1. 初始化一个 FlatBufferBuilder,它是一个构建器,用于序列化数据。
    // 参数是初始缓冲区大小,可以按需增长。
    FlatBufferBuilder builder(1024);
    // 2. 创建并嵌入数据,注意顺序:先创建依赖项(如 Vec3),再创建主对象(如 Monster)。
    // 创建 Vec3
    Vec3Builder pos_builder(builder);
    pos_builder.add_x(1.0f);
    pos_builder.add_y(2.0f);
    pos_builder.add_z(3.0f);
    auto pos = pos_builder.Finish();
    // 创建 inventory 数组
    unsigned char inventory[] = { 0, 1, 2, 3, 4 };
    auto inventory_vector = builder.CreateVector(inventory, 5);
    // 创建 name 字符串
    auto name = builder.CreateString("MyMonster");
    // 3. 创建 Monster 结构体
    MonsterBuilder monster_builder(builder);
    monster_builder.add_pos(pos);
    monster_builder.add_name(name);
    monster_builder.add_color(Color_Blue); // 使用 Color 枚举
    monster_builder.add_inventory(inventory_vector);
    auto monster = monster_builder.Finish();
    // 4. 完成缓冲区
    builder.Finish(monster);
    // 5. 获取指向二进制缓冲区的指针和大小
    uint8_t *buffer_ptr = builder.GetBufferPointer();
    int size = builder.GetSize();
    // buffer_ptr 包含了序列化后的 FlatBuffer 数据
    // 你可以将其写入文件、发送到网络等...
    printf("Serialized monster buffer size: %d\n", size);
    // 写入文件
    // FILE *f = fopen("monster.bin", "wb");
    // fwrite(buffer_ptr, size, 1, f);
    // fclose(f);
    return 0;
}

反序列化/读取示例 (read_monster.c):

#include <stdio.h>
#include "monster_generated.h" // 包含生成的头文件
int main() {
    // 假设我们从某个地方读取了二进制数据到 buffer 中
    // 为了演示,我们复用上面序列化的逻辑
    FlatBufferBuilder builder(1024);
    // ... (序列化代码与上面相同) ...
    Vec3Builder pos_builder(builder);
    pos_builder.add_x(1.0f);
    pos_builder.add_y(2.0f);
    pos_builder.add_z(3.0f);
    auto pos = pos_builder.Finish();
    unsigned char inventory[] = { 0, 1, 2, 3, 4 };
    auto inventory_vector = builder.CreateVector(inventory, 5);
    auto name = builder.CreateString("MyMonster");
    MonsterBuilder monster_builder(builder);
    monster_builder.add_pos(pos);
    monster_builder.add_name(name);
    monster_builder.add_color(Color_Blue);
    monster_builder.add_inventory(inventory_vector);
    auto monster = monster_builder.Finish();
    builder.Finish(monster);
    uint8_t *buffer_ptr = builder.GetBufferPointer();
    // --- 反序列化开始 ---
    // 1. 验证缓冲区是否有效(可选,但推荐)
    if (!MonsterBufferHasIdentifier(buffer_ptr)) {
        printf("Invalid FlatBuffer file (wrong identifier)!\n");
        return 1;
    }
    // 2. 获取根对象指针
    auto monster_obj = GetMonster(buffer_ptr);
    // 3. 直接读取数据,无需拷贝!
    printf("Monster name: %s\n", monster_obj->name()->c_str());
    printf("Monster HP: %d\n", monster_obj->hp());
    printf("Monster position: x=%f, y=%f, z=%f\n",
           monster_obj->pos()->x(),
           monster_obj->pos()->y(),
           monster_obj->pos()->z());
    // 4. 读取数组
    printf("Monster inventory: ");
    auto inventory_vec = monster_obj->inventory();
    for (int i = 0; i < inventory_vec->size(); i++) {
        printf("%d ", inventory_vec->Get(i));
    }
    printf("\n");
    return 0;
}

C 语言版本与 C++ 版本的对比

特性 C 语言版本 C++ 版本
API 风格 函数式 APImonster_obj->name()->c_str()-> 实际上是宏或函数调用,语法上模拟了指针访问。 面向对象 API,更像原生 C++,可以直接访问成员变量,monster_obj.name()->c_str()
易用性 语法稍显冗长,需要记住函数名,但概念清晰。 更自然、更简洁,符合 C++ 程序员的习惯。
类型安全 编译时类型安全,但运行时通过 void* 传递,可能需要更多小心。 完全的类型安全,利用 C++ 的模板和重载。
性能 几乎相同,两者都实现了核心的零拷贝读取,C 版本在某些极致优化的场景下可能有一点点优势,但通常可以忽略不计。 几乎相同
功能 功能齐全,支持所有核心特性。 功能最全,支持一些 C 版本没有的高级特性,如 std::string 字符串视图、反射等。

优缺点总结

优点:

  • 极致性能:零拷贝读取,解析速度极快。
  • 内存高效:二进制格式紧凑,读取时无需额外内存分配。
  • 强前向/后向兼容性:Schema 的演进非常灵活。
  • 无依赖:读取时无需任何运行时库。
  • 跨平台:生成的是纯 C 代码,可轻松移植到任何有 C 编译器的平台。

缺点:

  • 非随机访问:与 Protocol Buffers 不同,你不能通过字段名直接访问任意字段,FlatBuffer 是一个连续的字节流,读取必须从根开始,按顺序进行,虽然访问函数做了优化,但这是一种设计上的权衡。
  • Schema 依赖:读取数据时,必须要有与数据版本完全匹配或兼容的 Schema(.fbs 文件)来生成头文件。
  • C API 的学习曲线:对于习惯了 C++ 的开发者来说,C 语言的函数式 API 可能需要一点时间来适应。

FlatBuffers 对 C 语言的支持是官方、成熟且高性能的。 如果你正在开发一个对性能和内存有严苛要求的 C/C++ 项目,尤其是在嵌入式、游戏、高频交易或网络协议等领域,FlatBuffers 是一个非常优秀的选择,它的零拷贝特性和无依赖读取能力完美契合了 C 语言的设计哲学。

-- 展开阅读全文 --
头像
C语言getline后为何要free?
« 上一篇 2025-12-18
C语言为何用public static?与Java有何不同?
下一篇 » 2025-12-18

相关文章

取消
微信二维码
支付宝二维码

目录[+]