news 2026/6/12 4:18:25

【Rust】19-FFI、ABI 与跨语言边界设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Rust】19-FFI、ABI 与跨语言边界设计

FFI、ABI 与跨语言边界设计

研究目标

  • 理解 FFI 不只是语法互调,还包含 ABI、所有权和错误边界。
  • 掌握 Rust 与 C 交互时的基础表示和安全约束。
  • 学会设计清晰的跨语言 API 边界。

FFI 与 ABI

FFI 是 foreign function interface,指不同语言之间互相调用。ABI 是 application binary interface,描述函数调用约定、参数传递、返回值、符号命名、类型布局等底层规则。

Rust 与 C 交互时,常使用外部块声明 C 函数。下面示例使用 Rust 2024 兼容写法:

unsafeextern"C"{fnabs(input:i32)->i32;}

extern "C"表示使用 C ABI,unsafe extern表示声明者必须保证外部函数签名正确。调用外部函数是不安全的,因为 Rust 编译器无法验证外部代码是否满足声明。

fnmain(){letvalue=unsafe{abs(-3)};println!("{value}");}

导出 Rust 函数给 C

Rust 函数默认符号名会被改编。要导出稳定符号,可以使用:

#[unsafe(no_mangle)]pubextern"C"fnadd(a:i32,b:i32)->i32{a+b}

#[unsafe(no_mangle)]保留函数名,extern "C"使用 C 调用约定。在 Rust 2024 中,no_mangle属于 unsafe attribute,因为导出未改编符号可能和其他符号冲突。

通常还需要在Cargo.toml配置库类型:

[lib] crate-type = ["cdylib", "staticlib"]

cdylib用于生成动态库,staticlib用于静态链接。

FFI 安全类型

跨 FFI 边界应使用 C 兼容类型:

  • i32u32usize等基础整数需注意平台宽度。
  • #[repr(C)]结构体。
  • 裸指针*const T*mut T
  • C 字符串指针*const c_char

不要直接在 C ABI 中暴露StringVec<T>、trait object、闭包、普通 Rust enum 等 Rust 专有布局类型。

#[repr(C)]pubstructPoint{pubx:i32,puby:i32,}#[unsafe(no_mangle)]pubextern"C"fndistance_squared(point:Point)->i32{point.x*point.x+point.y*point.y}

字符串边界

C 字符串通常是以\0结尾的字节序列。Rust 的String是 UTF-8、拥有所有权且包含长度和容量。两者不能直接等同。

从 C 接收字符串:

usestd::ffi::CStr;usestd::os::raw::c_char;pubunsafefnread_name(ptr:*constc_char)->Option<String>{ifptr.is_null(){returnNone;}letc_str=unsafe{CStr::from_ptr(ptr)};Some(c_str.to_string_lossy().into_owned())}

这里 unsafe 前提包括:指针非空、指向有效 NUL 结尾字符串、内存在调用期间有效。

把 Rust 字符串传给 C 时可使用CString,它保证内部没有 NUL 字节并添加结尾 NUL。

所有权边界

跨语言边界最容易出错的是谁分配、谁释放。

一种常见设计是成对提供创建和释放函数:

usestd::ffi::CString;usestd::os::raw::c_char;#[unsafe(no_mangle)]pubextern"C"fnmake_message()->*mutc_char{CString::new("hello").unwrap().into_raw()}#[unsafe(no_mangle)]pubunsafeextern"C"fnfree_message(ptr:*mutc_char){if!ptr.is_null(){let_=unsafe{CString::from_raw(ptr)};}}

into_raw把所有权交给调用方,调用方必须把指针传回free_message。不能用 C 的free释放 Rust 分配的内存。

不要跨 FFI unwind

Rust panic 不应跨越不支持 unwind 的 FFI 边界。C++ 异常也不应随意穿过 Rust 栈帧。跨语言边界应把错误转成显式返回值。

常见 C 风格 API:

#[repr(C)]pubenumStatus{Ok=0,InvalidInput=1,InternalError=2,}#[unsafe(no_mangle)]pubextern"C"fndo_work(input:i32,output:*muti32)->Status{ifoutput.is_null(){returnStatus::InvalidInput;}unsafe{*output=input*2;}Status::Ok}

通过状态码和输出参数表达失败,调用者不需要理解 Rust 的Result

opaque pointer 模式

复杂 Rust 类型可以通过不透明指针暴露:

pubstructEngine{count:usize,}#[unsafe(no_mangle)]pubextern"C"fnengine_new()->*mutEngine{Box::into_raw(Box::new(Engine{count:0}))}#[unsafe(no_mangle)]pubunsafeextern"C"fnengine_increment(engine:*mutEngine){ifletSome(engine)=unsafe{engine.as_mut()}{engine.count+=1;}}#[unsafe(no_mangle)]pubunsafeextern"C"fnengine_free(engine:*mutEngine){if!engine.is_null(){let_=unsafe{Box::from_raw(engine)};}}

C 侧只持有Engine*,不知道内部布局。Rust 保留实现细节和内存管理能力。

bindgen 与 cbindgen

工具可以减少手写绑定:

  • bindgen:根据 C 头文件生成 Rust FFI 绑定。
  • cbindgen:根据 Rust 代码生成 C/C++ 头文件。

工具能减少机械错误,但不能自动解决所有权、线程安全、错误模型和生命周期设计。边界设计仍然需要人工审查。

跨语言 API 设计建议

  • 使用小而稳定的 C ABI 表面。
  • 不暴露 Rust 专有类型。
  • 明确每个指针是否可空、是否拥有所有权、是否可变。
  • 提供成对释放函数。
  • 不让 panic 或异常跨边界。
  • 把错误转成状态码或显式错误对象。
  • 对 unsafe 函数写清 safety contract。

常见误解

  • extern "C"只解决调用约定,unsafe extern也不自动保证内存安全。
  • #[repr(C)]只影响布局,不验证字段语义安全。
  • 指针非空不代表有效,也不代表对齐正确。
  • 跨语言边界不要共享隐含所有权协议。

继续研究

  • Rustonomicon:FFI、repr、ownership across FFI。
  • Rust Reference:external blocks、ABI、type layout。
  • bindgen 和 cbindgen 文档。
  • C ABI、平台 calling convention 和动态链接文档。

后记

2026年6月11日15点30分于上海。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/12 4:18:06

Anthropic新架构:显式提示工程层为何正在归零

1. 项目概述&#xff1a;这不是一次普通更新&#xff0c;而是一次架构级“蒸发”“Anthropic Just Shipped the Layer That’s Already Going to Zero”——这个标题一出来&#xff0c;我正在调试一个Claude调用链的终端前停了三秒。不是因为震惊&#xff0c;而是因为熟悉&…

作者头像 李华
网站建设 2026/6/12 4:15:56

如何5分钟部署Keep:开源AIOps告警管理平台的一站式解决方案

如何5分钟部署Keep&#xff1a;开源AIOps告警管理平台的一站式解决方案 【免费下载链接】keep The open-source AIOps and alert management platform 项目地址: https://gitcode.com/GitHub_Trending/kee/keep 在现代云原生环境中&#xff0c;运维团队每天需要面对海量…

作者头像 李华
网站建设 2026/6/12 4:15:53

告别抓瞎!用C#和网络调试助手一步步“拆解”三菱PLC的A-1E协议报文

从字节流到业务逻辑&#xff1a;C#实战解析三菱PLC A-1E协议通信全流程当我们需要让工业控制系统与上位机进行数据交互时&#xff0c;协议通信往往是第一个需要攻克的难关。三菱PLC的A-1E协议作为FX系列设备的主流通信标准&#xff0c;其二进制报文格式对初学者来说就像一本没有…

作者头像 李华
网站建设 2026/6/12 4:14:54

别再只调库了!深入理解STM32定时器在激光测距中的高精度时间测量原理

深入解析STM32定时器在激光测距中的高精度时间测量技术 激光测距技术凭借其高精度和快速响应的特性&#xff0c;已成为工业测量、机器人导航等领域的关键技术。然而&#xff0c;许多开发者在使用STM32进行激光测距时&#xff0c;往往停留在简单的库函数调用层面&#xff0c;未能…

作者头像 李华
网站建设 2026/6/12 4:12:51

Docker Compose 与多服务编排:从单容器到本地开发环境

Docker Compose 与多服务编排&#xff1a;从单容器到本地开发环境一、本地开发的"环境地狱"&#xff1a;依赖太多&#xff0c;启动太复杂 微服务架构下&#xff0c;本地开发一个功能可能需要启动多个服务&#xff1a;API 网关、用户服务、订单服务、Redis 缓存、MySQ…

作者头像 李华
网站建设 2026/6/12 4:10:52

Qt开源报表引擎limereport实战:从编译到数据绑定的完整指南

1. 环境准备与源码编译 第一次接触limereport这个Qt开源报表引擎时&#xff0c;我完全理解为什么网上资料这么少——它就像藏在深山里的武林秘籍&#xff0c;功能强大但入门门槛不低。不过别担心&#xff0c;跟着我的步骤走&#xff0c;保证你能顺利跨过第一个坎&#xff1a;编…

作者头像 李华