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”)] 来指定。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐