Rust编程从入门到实战 如何在从C 调用Rust库,或者从 Rust 调用C库
Rust 有一个非常好的特性,就是它支持与C 语言的ABI 兼容。什么是ABI 呢?维基百 科是这么解释的:
In computer software,an application binary interface(ABI)is an interface between two program modules;often,one of these modules is a library or operating system facility,and the other is a program that is being run by a user.
An ABI defines how data structures or computational routines are accessed in machine code, which is a low-level,hardware-dependent format.
所以,我们可以用Rust写一个库,然后直接把它当成C 写的库来使用。或者反过来, 用C 写的库,可以直接在Rust 中被调用。而且这个过程是没有额外性能损失的。 C 语言的 ABI 是这个世界上最通用的ABI, 大部分编程语言都支持与C 的 ABI 兼容。这也意味着, Rust与其他语言之间的交互是没问题的,比如用Rust为 Python /Node.js/Ruby 写一个模 块等。
本章主要讲解 Rust 如何与其他语言进行交互,为了让示例尽可能地简洁、清晰,本章主 要关注的是Rust 如何与C 语言进行交互。至于其他语言,搞清楚它们如何与C 语言交互就 完全可以掌握它们与Rust 的交互办法,所以无须一个个地分别讲解。
什么是FFI
所谓的FFI 指的是:
A foreign function interface(FFI)is a mechanism by which a program programming language can call routines or make use of services written in another. written in one
Rust 不支持源码级别与其他语言的交互,因为这么做代价很大、限制太多,所以需要用 库的方式来互相调用。这就要求我们有能力用Rust 生成与C 的 ABI 兼容的库。通过 rustc -h 命令我们可以看到,Rust 编译器支持生成这样一些种类的库:
--crate - type[bin | lib | rlib | dylib | cdylib | staticlib | proc - macro] Comma separated list of types of crates
for the compiler to emit
其中,cdylib 和 staticlib 就是与C 的 ABI 兼容的。分别代表动态链接库和静态链 接库。在编译的时候,我们需要指定这样的选项才能生成合适的目标文件。指定目标文件类 型有两种方式:
- 在编译命令行中指定,如rustc --crate-type=staticlib test.rs;
- 在源代码入口中指定,如#![crate_type =“staticlib”]。
另外,我们还需要注意,C 的 ABI 以及运行时库也不是完全统一的。此事是由rustup 工具 管理的。执行rustup show 可以看到当前使用的工具链是什么,比如笔者当前的工具链是:
Default host:x86_64-unknown-linux-gnu
这意味着用这套工具链生成的C 库是和gcc 工具链的ABI 兼容的。如果读者需要生成与 MSVC 的 ABI 兼容的库,那么需要使用:
rustup target add x86_64-pc-windows-msvc
我们还可以用rustup 来下载Android 系统的工具链,实现交叉编译,等等。关于工具链 以及C 运行时库的链接方式的问题,读者可以参考rustup的官方网站。
除了指定目标文件、工具链之外,更重要的是需要注意接口的设计。不是所有的Rust 的 语言特性都适合放到交互接口中的。比如,Rust 中有泛型,C 语言里面没有,所以泛型这种 东西是不可能暴露出来给C 语言使用的,这就不是C 语言的ABI 的一部分。只有符合C 语 言的调用方式的函数,才能作为FFI 的接口。这样的函数有以下基本要求:
- 使 用extern"C" 修饰,在Rust中 extern fn默认等同于extern"C"fn;
- 使 用 #[no_mangle] 修饰函数,避免名字重整;
- 函数参数、返回值中使用的类型,必须在Rust和 C 里面具备同样的内存布局。
下面我们用示例来说明如何实现FFI。
从C 调用Rust库
假设我们要在Rust 中实现一个把字符串从小写变大写的函数,然后由C 语言调用这个函数。实现代码如下:
# [no_mangle] pub extern "C"fn rust_capitalize(s: *mut c_char) {
unsafe {
let mut p = s as * mut u8;
while * p != 0 {
let ch = char: :from( * p);
if ch.is_ascii() {
let upper = ch.to_ascii_uppercase(); * p = upper as u8;
}
p = p.offset(1);
}
}
}
我 们 在Rust 中实现这个函数,考虑到C 语言调用的时候传递的是char * 类型,所 以 在Rust 中我们对应的参数类型是*mutstd::0s::raw::c_char。这样两边就对应起 来了。
这个函数是要被外部的C 代码调用的,所以 一 定要用extern"C" 修饰。用#[no_ mangle] 修饰主要是为了保证导出的函数名字和源码中的一致。这个并不是必须的,我们 还可以使用# [export_name =“my_whatever_name”] 来指定导出名字。在某些时候, 我们需要导出的函数名恰好在Rust 中跟某个关键字发生了冲突,就可以用这种方式来规避。
使用如下编译命令,可以生成一个与C 的 ABI 兼容的静态库。
rustc --crate-type=staticlib capitalize.rs
下面我们再写一个调用这个函数的C 程序:
#include < stdlib.h > #include < stdio.h >
//declare
extern void rust_capitalize(char * );
int main() {
char str[] = "hello world";
rust_capitalize(str);
printf("8s\n", str);
return 0;
}
使用如下命令编译链接:
gcc -o main main.c -L.-1:libcapitalize.a -W1,--gc-sections -1pthread -1dl
可以正确生成可执行程序。执行代码,可见该程序完成了预期中的功能。—
从 Rust 调用C 库
这个例子我们反过来,从Rust中调用C 写的库。C 的实现如下所示:
int add_square(int a, int b) {
return a * a + b * b;
}
使用如下命令可以生成对应的静态库:
gcc - c - Wall - Werror - fpic simple_math.c ar rcs libsimple_math.a simple_math.o
现在我们到Rust 中调用这个静态库:
use std: :OS: :raw: :c_int;
# [link(name = "simple_math")] extern "C" {
fn add_square(a: c_int, b: c_int) - >c_int;
}
fn main() {
let r
= unsafe {
add_square(2, 2)
};
println ! ("{}", r);
}
使用如下命令编译链接:
rustc -L .call_math.rs
参 数 -L 可以指定依赖库的查找路径,具体的名字可以通过#[link(name = “library_name”)] 来指定。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)