Apache Arrow C ABI 详解:C Data Interface 与 C Stream Interface 的结构、语义与实战
2026/9/23 17:47:55 网站建设 项目流程

Apache Arrow C ABI 详解:C Data Interface 与 C Stream Interface 的结构、语义与实战

【免费下载链接】arrowApache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing项目地址: https://gitcode.com/gh_mirrors/arrow12/arrow

Apache Arrow 的 C ABI(docs/source/cpp/api/c_abi.rst)以三个自包含的 C 结构体ArrowSchemaArrowArrayArrowArrayStream为基石,定义了跨语言、跨运行时的列式数据零拷贝交换协议。本文以此为骨架,结合 C Data Interface 规范 与 C Stream Interface 规范 的完整定义,以及 cpp/src/arrow/c/abi.h 与 cpp/src/arrow/c/bridge.cc 的源码实现,系统讲解格式字符串编码、结构体字段语义、内存管理与 release 回调约定、流式接口协议,并给出可直接复制的 C 生产者/消费者代码示例。读完本文,你将能够在自己的项目中不依赖 Arrow 库,直接实现 Arrow 数据的导出、导入与流式传输。

什么是 Arrow C ABI:三个结构体组成的“列式数据通用语”

C ABI 页面文档的核心是一句话:只要三个 C 结构体,就能让任何项目与 Arrow 格式进行列式数据交换。这三个结构体定义在 cpp/src/arrow/c/abi.h,并被设计为可以原样拷贝进任意项目的源码(约 50 行 C 代码),因此也被称为“Arrow C data interface 的自由站立定义”。

#ifndef ARROW_C_DATA_INTERFACE #define ARROW_C_DATA_INTERFACE #define ARROW_FLAG_DICTIONARY_ORDERED 1 #define ARROW_FLAG_NULLABLE 2 #define ARROW_FLAG_MAP_KEYS_SORTED 4 struct ArrowSchema { // Array type description const char* format; const char* name; const char* metadata; int64_t flags; int64_t n_children; struct ArrowSchema** children; struct ArrowSchema* dictionary; // Release callback void (*release)(struct ArrowSchema*); // Opaque producer-specific data void* private_data; }; struct ArrowArray { // Array data description int64_t length; int64_t null_count; int64_t offset; int64_t n_buffers; int64_t n_children; const void** buffers; struct ArrowArray** children; struct ArrowArray* dictionary; // Release callback void (*release)(struct ArrowArray*); // Opaque producer-specific data void* private_data; }; #endif // ARROW_C_DATA_INTERFACE

从源码结构看,abi.h 中的定义与规范文档完全一致,并用#pragma onceextern "C"包裹以兼容 C/C++ 混编。同一头文件还顺带定义了ArrowDeviceArray(设备数据接口,用于 CUDA/ROCm 等非 CPU 内存)与ArrowDeviceArrayStream,本文聚焦 CPU 侧的三个核心结构体。

设计目标与边界

依据 C Data Interface 规范 中的 "Goals" 与 "Non-goals" 章节,这套接口的设计意图非常明确:

  • 目标:暴露 ABI 稳定的接口;让第三方项目能以极小的投入实现支持(包括部分支持);实现同一进程内独立运行时与组件之间的零拷贝共享;紧贴 Arrow 数组概念避免再造一层编组层;无需编译期或运行期依赖 Arrow 软件项目本身。
  • 非目标:不提供模仿高级运行时(C++/Java)操作的 C API;不涉及跨进程数据共享与持久化存储(那是 IPC 格式的职责)。

与 IPC 格式的取舍

规范文档明确比较了两条路线,选择 C data interface 的理由包括:无 Flatbuffers 依赖、无需缓冲区重组(数据已经是逻辑 Arrow 格式)、天然零拷贝、易于从零重实现、C 定义极小可拷贝、通过自定义 release 回调管理资源生命周期;而 IPC 格式的优势则是跨进程与跨机器、支持持久化、可流式组合更多特性(校验、压缩等)、不要求显式 C 数据访问。简言之:进程内共享数据用 C ABI,跨进程/持久化用 IPC。

类型描述:format 字符串全表

ArrowSchema.format是一个 UTF-8 的 null 结尾字符串,只描述顶层类型;嵌套类型的子类型在children中分别描述,元数据在metadata中单独编码。format 字符串设计为极易解析(哪怕用纯 C)。

基础类型(单字符格式串)

FormatArrow 类型FormatArrow 类型
nnulllint64
bbooleanLuint64
cint8efloat16
Cuint8ffloat32
sint16gfloat64
Suint16zbinary
iint32Zlarge binary
Iuint32uutf-8 string
Ularge utf-8 string

视图与变长类型

FormatArrow 类型
vzbinary view
vuutf-8 view
d:19,10decimal128 [precision 19, scale 10]
d:19,10,NNNdecimal,位宽 = NNN [precision 19, scale 10]
w:42fixed-width binary [42 bytes]

时间类型(以t开头)

FormatArrow 类型FormatArrow 类型
tdDdate32 [days]tDuduration [microseconds]
tdmdate64 [milliseconds]tDnduration [nanoseconds]
ttstime32 [seconds]tiMinterval [months]
ttmtime32 [milliseconds]tiDinterval [days, time]
ttutime64 [microseconds]tininterval [month, day, nanoseconds]
ttntime64 [nanoseconds]tss:.../tsm:...timestamp [秒/毫秒] + 时区
tDsduration [seconds]tsu:.../tsn:...timestamp [微秒/纳秒] + 时区

注意:timestamp 的时区字符串在冒号:后原样附加、不加引号;即使时区为空,冒号也必须保留(如tsm:)。

嵌套类型(以+开头)

FormatArrow 类型FormatArrow 类型
+llist+sstruct
+Llarge list+mmap
+vllist-view+ud:I,J,...dense union(type ids I,J...)
+vLlarge list-view+us:I,J,...sparse union(type ids I,J...)
+w:123fixed-sized list [123 items]+rrun-end encoded

嵌套类型的两个特殊约定:map 类型唯一子类型必须命名为entries,其本身是(key, value)双子的 struct;run-end encoded 类型有两个子类型,第一个是整型的run_ends,第二个是values

格式串实例

规范文档给出的实例是理解嵌套编码的最佳路径:

  • 字典编码的decimal128(precision=12, scale=5)数组,索引类型为int16:父数组 format 为s,其dictionary指向的字典值数组 format 为d:12,5
  • list<uint64>:父 format 为+l,唯一子类型 format 为L
  • large_list_view<uint64>:父 format 为+vL,唯一子类型 format 为L
  • struct<ints: int32, floats: float32>:format 为+s,两个子类型名字为intsfloats,format 分别为if
  • map<string, float64>:format 为+m,子类型名为entries(format+s),孙类型名为keyu)与valueg);
  • sparse_union<ints: int32, floats: float32>(type ids 4,5):format 为+us:4,5
  • run_end_encoded<int32, float32>:format 为+r,子类型run_endsi)与valuesf)。

字典编码没有专属格式串:父 format 编码的是索引类型,字典值类型读取自ArrowSchema.dictionary扩展类型(Extension Array)的 format 编码的是存储类型,扩展类型信息编码在 metadata 中——ARROW:extension:name键记录扩展名,ARROW:extension:metadata键记录参数化扩展类型的序列化信息,与 IPC 格式的约定一致。

字段语义逐项拆解

ArrowSchema 字段

  • format(必填):null 结尾 UTF-8 类型描述字符串;嵌套类型的子类型不在此编码,而在children中。消费者可以只支持部分类型,但应明确文档化该限制。
  • name(可选):字段或数组名,主要用于重建嵌套类型的子字段。可省略(为 NULL 或空串)。
  • metadata(可选):二进制字符串(null 结尾),编码类型元数据。格式为:int32键值对数量 N,随后每对键值按“int32字节长度 + 原始字节”交替排列;整数使用本机字节序。例如元数据[('key1', 'value1')]在小端机器上编码为\x01\x00\x00\x00\x04\x00\x00\x00key1\x06\x00\x00\x00value1。若省略,该字段必须为 NULL(而非空字符串)。
  • flags(可选):按位或组合。ARROW_FLAG_NULLABLE(2) 表示字段语义上可空(与实际是否含 null 值无关);ARROW_FLAG_DICTIONARY_ORDERED(1) 表示字典索引顺序语义有意义;ARROW_FLAG_MAP_KEYS_SORTED(4) 表示 map 每个 value 内的键已排序。省略时必须为 0。
  • n_children(必填):子类型数量。
  • children(可选):指向每个子类型的指针数组,必须有n_children个指针;仅当n_children为 0 时可为 NULL。
  • dictionary(可选):指向字典值类型的指针;仅当该 schema 表示字典编码类型时必须存在,否则必须为 NULL。
  • release(必填):生产者提供的释放回调。
  • private_data(可选):生产者私有数据的不透明指针,消费者不得处理,其生命周期由生产者的 release 回调管理。

ArrowArray 字段

  • length(必填):数组的逻辑长度(条目数)。
  • null_count(必填):null 条目数;尚未计算时可为 -1。
  • offset(必填):逻辑偏移量(缓冲区物理起点之前的条目数),必须为 0 或正数。生产者可以声明只产出 0 偏移数组;消费者可以不支持非 0 偏移,但需文档化。
  • n_buffers(必填):物理缓冲区数量,由数据类型决定(参见 Columnar 格式规范),binary/utf-8 view 类型比 Columnar 规范多一个缓冲区(见下文“Binary view 数组”)。子数组的缓冲区不计入。
  • buffers(必填):指向每个物理缓冲区起始位置的指针数组,共n_buffers个。生产者必须保证每个连续缓冲区大到足以容纳length + offset个按 Columnar 规范编码的值;建议(非强制)按原始数据类型对齐内存地址。缓冲区指针仅可在两种情况下为 NULL:null 位图且null_count为 0;或对应缓冲区字节大小为 0。
  • n_children/children/dictionary:语义同 ArrowSchema 对应字段(children 数量由数据类型决定;dictionary 指向字典值数组)。
  • release(必填):生产者提供的释放回调。
  • private_data(可选):同 ArrowSchema。

Binary view 数组的额外缓冲区

规范特别注明:binary/utf-8 view 数组会追加一个缓冲区,以int64_t记录每个变长数据缓冲区的长度。该缓冲区是必需的,因为这类数组的缓冲区长度无法从数组其他数据中平凡推导。

为什么需要两个结构体?

规范文档专辟一节回答:同一类型/模式描述往往适用于多个(可能很短的)数据批次。把ArrowSchema在对话开始时单独传递一次,避免每个批次都重复导出/导入类型描述的开销;某些生产者 API 的类型固定,甚至无需传递类型;而一次性交换数据时,也可以在同一 API 调用中同时传递ArrowSchemaArrowArray

内存管理与 release 回调:谁分配、谁释放

ArrowSchemaArrowArray遵循相同的内存管理约定。规范中把在生产者和消费者之间传递的那个结构称为“基础结构体”(base structure,不含子结构)。

分配规则

  • 基础结构体应由消费者在栈上或堆上分配,生产者 API 接收指向消费者分配结构体的指针。
  • 结构体指向的所有数据(format/metadata 字符串、缓冲区指针数组、children 指针数组等)必须由生产者分配和维护。
  • 消费者不得干预这些成员的生命周期,唯一影响数据生命周期的方式是调用基础结构体的release回调。

已释放状态

结构体已释放的标志是release回调被置为 NULL。消费者在读取和解释结构体数据之前应检查release是否为 NULL,并按需报错。这与 helpers.h 中ArrowSchemaIsReleased/ArrowSchemaMarkReleased等内联辅助函数一一对应:ArrowSchemaRelease在非 released 时调用回调并断言回调确实把release置空。

消费者视角

消费者在不再使用基础结构体时必须调用其release,但绝不能调用任何子结构体(包括可选的 dictionary)的 release——子结构体的释放由生产者负责。调用 release 之后,消费者不得再访问基础结构体及其关联数据。

生产者视角

生产者的 release 回调必须遵守四条硬性规则:

  1. 必须遍历所有子结构体(含 dictionary)并调用它们各自的 release 回调;
  2. 必须释放结构体直接拥有的所有数据区域(buffers、children 成员等);
  3. 必须通过把release置 NULL 标记结构体已释放;
  4. 不得假设结构体仍位于最初产出时的内存位置(消费者可以移动结构体,见下节),因此需要额外生命周期信息(如 C++ 的shared_ptr)时必须通过private_data承载。

规范给出了一个可直接套用的 release 回调模板:

static void ReleaseExportedArray(struct ArrowArray* array) { // This should not be called on already released array assert(array->release != NULL); // Release children for (int64_t i = 0; i < array->n_children; ++i) { struct ArrowArray* child = array->children[i]; if (child->release != NULL) { child->release(child); assert(child->release == NULL); } } // Release dictionary struct ArrowArray* dict = array->dictionary; if (dict != NULL && dict->release != NULL) { dict->release(dict); assert(dict->release == NULL); } // TODO here: release and/or deallocate all data directly owned by // the ArrowArray struct, such as the private_data. // Mark array released array->release = NULL; }

在 Arrow C++ 实现中,bridge.cc 的ReleaseExportedSchema正是按这套约定编写的:先递归释放所有 children 与 dictionary(并断言每个子结构在回调后确实已标记 released),再释放private_data指向的导出私有数据对象,最后ArrowSchemaMarkReleased。该私有数据通过PoolAllocationMixin(bridge.cc)从默认内存池分配,便于内存记账与泄漏检测。

移动数组(Moving)

消费者可以通过位拷贝或浅成员拷贝“移动”ArrowArray结构体,然后把源结构体标记为 released(置release为 NULL)但不调用其 release 回调,从而保证任意时刻只有一个存活副本。此后 release 回调将在目标结构体上被调用。

也可以移动一个或多个子数组,但父ArrowArray必须立即释放(它指向的子数组已失效);典型场景是只保留感兴趣的若干列(子数组)而释放其余列。移动能够成立的前提是ArrowArray可平凡重定位——因此结构体内部指针成员(含private_data)不得指向结构体自身,生产者也不得在外部单独保存指向该结构体的指针,必须用private_data记录簿记信息。这正解释了 helpers.h 中ArrowArrayMove的实现:memcpyArrowArrayMarkReleased(src)

不可变性约定

生产者和消费者都应把通过buffers成员可达的导出数据视为不可变,否则一方可能在另一方变更数据时看到不一致内容。

C Data Interface 实战:手写生产者导出代码

规范文档提供了可直接复制的完整 C 生产者示例,覆盖从最简 int32 到嵌套 struct 的导出,全部基于标准malloc/free,不依赖任何 Arrow 头文件。

导出非空 int32 类型(静态数据)

类型的所有成员都指向静态分配数据,release 回调是平凡的:

static void release_int32_type(struct ArrowSchema* schema) { // Mark released schema->release = NULL; } void export_int32_type(struct ArrowSchema* schema) { *schema = (struct ArrowSchema) { .format = "i", .name = "", .metadata = NULL, .flags = 0, .n_children = 0, .children = NULL, .dictionary = NULL, .release = &release_int32_type }; }

导出 C-malloc() 的 int32 数组(所有权转移)

通过 release 回调把buffers的所有权转移给消费者:

static void release_int32_array(struct ArrowArray* array) { assert(array->n_buffers == 2); // Free the buffers and the buffers array free((void *) array->buffers[1]); free(array->buffers); // Mark released array->release = NULL; } void export_int32_array(const int32_t* data, int64_t nitems, struct ArrowArray* array) { *array = (struct ArrowArray) { .length = nitems, .offset = 0, .null_count = 0, .n_buffers = 2, .n_children = 0, .children = NULL, .dictionary = NULL, .release = &release_int32_array }; // Allocate list of buffers array->buffers = (const void**) malloc(sizeof(void*) * array->n_buffers); assert(array->buffers != NULL); array->buffers[0] = NULL; // no nulls, null bitmap can be omitted array->buffers[1] = data; }

注意:int32数组有两个缓冲区(null 位图 + 数据),无 null 时位图缓冲区可为 NULL;偏移为 0、null_count 为 0。

导出 struct<float32, utf8> 类型(C-malloc() 子类型)

+s结构类型需要递归初始化每个子类型,并用一个会递归释放的 release 回调:

static void release_malloced_type(struct ArrowSchema* schema) { int i; for (i = 0; i < schema->n_children; ++i) { struct ArrowSchema* child = schema->children[i]; if (child->release != NULL) { child->release(child); } free(child); } free(schema->children); // Mark released schema->release = NULL; } void export_float32_utf8_type(struct ArrowSchema* schema) { struct ArrowSchema* child; *schema = (struct ArrowSchema) { .format = "+s", .name = "", .metadata = NULL, .flags = 0, .n_children = 2, .dictionary = NULL, .release = &release_malloced_type }; schema->children = malloc(sizeof(struct ArrowSchema*) * schema->n_children); // child type #0: float32 child = schema->children[0] = malloc(sizeof(struct ArrowSchema)); *child = (struct ArrowSchema) { .format = "f", .name = "floats", .metadata = NULL, .flags = ARROW_FLAG_NULLABLE, .n_children = 0, .dictionary = NULL, .children = NULL, .release = &release_malloced_type }; // child type #1: utf8 child = schema->children[1] = malloc(sizeof(struct ArrowSchema)); *child = (struct ArrowSchema) { .format = "u", .name = "strings", .metadata = NULL, .flags = ARROW_FLAG_NULLABLE, .n_children = 0, .dictionary = NULL, .children = NULL, .release = &release_malloced_type }; }

对应地,规范还给出了export_float32_utf8_array的完整实现:父数组n_buffers=1(struct 无自身数据缓冲区,仅buffers[0]=NULL)、n_children=2;子数组 #0(float32)n_buffers=2(null 位图 + 数据)、null_count=-1(未计算);子数组 #1(utf8)n_buffers=3(null 位图 + offsets + data)。释放回调release_malloced_array递归释放所有子数组、全部缓冲区与指针数组。

实战要点:子数组缓冲区的布局(int32/float 2 个、utf8 3 个)与 Columnar 格式规范完全一致;null_count = -1表示尚未计算,消费者需自行扫描。

C Stream Interface:进程内流式传输

规范 指出,C stream interface 建立在 C data interface 之上,把ArrowSchema/ArrowArray组合成更高级的规范,便于同一进程内流式数据的通信:一个 C 流暴露同 schema 的数据块(chunk)流式来源,通过阻塞的拉取式迭代函数获取数据块。

ArrowArrayStream 结构体

#ifndef ARROW_C_STREAM_INTERFACE #define ARROW_C_STREAM_INTERFACE struct ArrowArrayStream { // Callbacks providing stream functionality int (*get_schema)(struct ArrowArrayStream*, struct ArrowSchema* out); int (*get_next)(struct ArrowArrayStream*, struct ArrowArray* out); const char* (*get_last_error)(struct ArrowArrayStream*); // Release callback void (*release)(struct ArrowArrayStream*); // Opaque producer-specific data void* private_data; }; #endif // ARROW_C_STREAM_INTERFACE

字段语义:

  • get_schema(必填):查询流中所有数据块共用的 schema;返回 0 表示成功,否则返回非零错误码。不能在已释放的流上调用。
  • get_next(必填):获取下一个数据块;返回 0 表示成功。成功时消费者必须检查ArrowArray是否标记为 released——若已释放则到达流末尾,否则该ArrowArray是一个有效数据块。
  • get_last_error(必填):仅在最后一次流操作返回错误后调用,返回指向 UTF-8 null 结尾字符串的指针(无详细描述时可为 NULL)。返回的指针仅在流的下一次回调调用前有效,需要长期持有必须拷贝到消费者管理的存储。
  • release(必填):释放回调,用法与 C data interface 相同。
  • private_data(可选):同前。

错误码约定

get_schemaget_next返回的非零整数应按本平台errno解释(符号常量跨平台稳定,数值平台相关)。规范建议至少识别:EINVAL(参数/输入校验错误)、ENOMEM(内存分配失败)、EIO(通用 I/O 错误)。

结果与流的生命周期

get_schemaget_next返回的数据必须独立释放,其生命周期与ArrowArrayStream无关。流的生命周期同样通过 release 回调管理。此外,流源不保证线程安全:消费者若从多线程调用get_next,必须自行串行化。

C 消费者示例:遍历查询结果

假设某数据库提供如下 C API 执行 SQL 并把结果集导出为 Arrow C 流:

void MyDB_Query(const char* query, struct ArrowArrayStream* result_set);

消费者代码如下:

static void handle_error(int errcode, struct ArrowArrayStream* stream) { // Print stream error const char* errdesc = stream->get_last_error(stream); if (errdesc != NULL) { fputs(errdesc, stderr); } else { fputs(strerror(errcode), stderr); } // Release stream and abort stream->release(stream), exit(1); } void run_query() { struct ArrowArrayStream stream; struct ArrowSchema schema; struct ArrowArray chunk; int errcode; MyDB_Query("SELECT * FROM my_table", &stream); // Query result set schema errcode = stream.get_schema(&stream, &schema); if (errcode != 0) { handle_error(errcode, &stream); } int64_t num_rows = 0; // Iterate over results: loop until error or end of stream while ((errcode = stream.get_next(&stream, &chunk) == 0) && chunk.release != NULL) { // Do something with chunk... fprintf(stderr, "Result chunk: got %lld rows\n", chunk.length); num_rows += chunk.length; // Release chunk chunk.release(&chunk); } // Was it an error? if (errcode != 0) { handle_error(errcode, &stream); } fprintf(stderr, "Result stream ended: total %lld rows\n", num_rows); // Release schema and stream schema.release(&schema); stream.release(&stream); }

这段代码完整演示了三条协议规则:用get_schema取一次 schema、循环get_next直到chunk.release == NULL(流结束)、每个 chunk、schema、stream 各自独立释放。

从源码看 C ABI 在 Arrow C++ 中的落地

导出/导入的对称 API

bridge.h 定义了与规范一一对应的Export*/Import*系列 API,均属于arrow命名空间:

  • 导出:ExportTypeExportFieldExportSchema(写ArrowSchema);ExportArrayExportRecordBatch(写ArrowArray,可选同时写ArrowSchema)。
  • 导入:ImportTypeImportFieldImportSchemaImportArrayImportRecordBatch。导入函数会按规范“移动”结构体内容——即使失败,给定的ArrowSchema也会被释放。

流接口方面(bridge.h):ExportRecordBatchReaderExportChunkedArray把 C++ 对象导出为ArrowArrayStreamImportRecordBatchReaderImportChunkedArray反向导入(后者会把流完整消费掉再返回ChunkedArray)。另有一组实验性的 Device 接口(ExportDeviceArray等)对应ArrowDeviceArray/ArrowDeviceArrayStream

从实现看,导出时会创建ExportedSchemaPrivateData(bridge.cc)持有 format/name/metadata 字符串、子结构数组与字典结构,用内存池分配以便记账;元数据则按规范编码为“键值对数量 + 逐对长度前缀”的二进制串(EncodeMetadata,bridge.cc 起,先预计算总大小再写入)。

测试验证

bridge_test.cc 用 5000 余行测试覆盖了这套接口。以流导出为例:

  • TestArrayStreamExport.Simple(bridge_test.cc):构造int32列的 RecordBatchReader,ExportRecordBatchReader导出后依次get_schema校验 schema、两次get_next分别与两个批次比较、再断言流结束(AssertStreamEnd),最后重复断言流结束以验证幂等性。
  • TestArrayStreamExport.ArrayLifetime(bridge_test.cc):导出流后,在流被释放的情况下仍能通过ImportRecordBatch导入之前取出的两个 chunk,并断言内存池占用高于导出前——直接验证了“get_next 返回的数据独立于流生命周期”的约定。
  • TestArrayExport.ExportRecordBatch(bridge_test.cc 起):验证 record batch 以 struct 数组形式导出后可用ImportRecordBatch无损还原。

这些测试是阅读规范时极佳的对照材料:每个语义约定都能在测试里找到对应的验证点。

面向其他语言的落地:PyCapsule 接口速览

PyCapsule 接口规范 是 C data/stream interface 在 Python 生态的标准化封装(官方标注为实验性)。它解决的是“Python 库之间如何暴露这些 C 结构体”的问题:用带名字与析构函数的PyCapsule包装结构体指针,避免裸指针传整数的不安全与泄漏。

  • 胶囊命名约定:ArrowSchema"arrow_schema"ArrowArray"arrow_array"ArrowArrayStream"arrow_array_stream"ArrowDeviceArray"arrow_device_array"ArrowDeviceArrayStream"arrow_device_array_stream"
  • 导出协议为三个鸭子类型方法:__arrow_c_schema____arrow_c_array__(可带requested_schema协商表示形式)、__arrow_c_stream__;设备接口另有__arrow_c_device_array____arrow_c_device_stream__
  • 胶囊的析构函数应调用结构体的 release 回调(若非空);消费者移走数据后把 release 置空,避免双重释放。

规范的稳定性承诺

规范文档明确:一旦 C data interface 进入官方 Arrow 发布,C ABI 即冻结——ArrowSchemaArrowArray的结构定义不得以任何方式改变,包括新增成员。仅允许向后兼容的变更,例如新增ArrowSchema.flags取值或扩展 format 字符串的可能性;任何不兼容变更应放入新规范(如 "Arrow C data interface v2")。这一承诺是整个生态可以放心把这三个结构体拷进自己代码的前提。设计灵感则来自 Python buffer protocol(PEP 3118)——正是那个让各类 Python 库无需互相认识即可近乎零成本交换数值数据的协议。

结语

Arrow C ABI 以极小的 API 面(三个结构体 + 三组 release 约定)实现了极大的互操作价值:它让 C++ 数据库引擎可以“不依赖 Arrow 库却输出 Arrow 格式结果”,让 Python 库通过 PyCapsule 协议互通,也让任何语言运行时只需翻译 50 行 C 定义即可接入整个 Arrow 生态。从 格式字符串 到 release 回调模板,从 结构体定义 到 导出/导入实现 与 测试验证,本文覆盖了从协议语义到实战代码的完整链路,可作为你接入 Arrow C ABI 的实现参考。

【免费下载链接】arrowApache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing项目地址: https://gitcode.com/gh_mirrors/arrow12/arrow

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询