专栏首页Rust语言学习交流Rust FFI 编程 - Rust导出共享库01

Rust FFI 编程 - Rust导出共享库01

从前面的章节,我们可以看到,C与Rust/Rust与C的交互,核心就是指针的操作。两边的代码使用的是同一个程序栈,栈上的指针能放心地传递,而不用担心被错误释放的问题(栈上内存被调用规则自动管理,C和Rust中都是如此)。两边的代码可能使用不同的堆分配器,因此,堆上的指针的传递需要严格注意,需要各自管理各自的资源,谁创建谁释放。指针传递过程中,需要分析所有权问题。有了这种基本思维模型后,我们用 Rust 进行 FFI 编程,就会心中有数,知道什么时候该做什么,不再是一团浆糊了。

从本篇开始,我们进入新的领域:在 C 代码中调用 Rust 的功能。

我们先来看最简单的例子:C 中向 Rust 函数中,传入两个数,相加,并打印。

调用加法函数,并打印

Rust 代码:

// 在 Cargo.toml 中,加入如下两行

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

要让 Rust 导出动态共享库,需要在 Cargo.toml 中这样设置的,必须。

// src/lib.rs

#[no_mangle]
pub extern "C" fn addtwo0(a: u32, b: u32) {
    let c = a + b;
    println!("print in rust, sum is: {}", c);
}

执行 cargo build 编译。会在 target/debug/ 下生成 lib{cratename}.so (我们这里为 librustffi3.so)这个动态链接库文件。

接下来看 C 代码:

#include <stdio.h>
#include <stdint.h>

extern void addtwo0(uint32_t, uint32_t);

int main(void) {
  addtwo0(1, 2);
}

编译:

gcc -o ./ccode01  ./csrc/ccode01.c -L ./  -lrustffi3

会在当前目录下生成 ccode01 二进制文件(我已把 librustffi3.so 文件拷贝至当前目录)。运行

LD_LIBRARY_PATH=. ./ccode01

输出下面结果:

print in rust, sum is: 3

在 C 中处理返回值

上个示例,Rust 中计算的值,并没有返回给 C 这边。我们看看怎么返回回来。修改上述示例如下。

Rust代码:

// src/lib.rs

#[no_mangle]
pub extern "C" fn addtwo1(a: u32, b: u32) -> u32 {
    let c = a + b;
    println!("print in rust, sum is: {}", c);
    c
}

C 代码:

#include <stdio.h>
#include <stdint.h>

extern uint32_t addtwo1(uint32_t, uint32_t);

int main(void) {
  uint32_t sum = addtwo1(10, 20);
  printf("print in c, sum is: %d\n", sum);
}

运行生成结果如下:

print in rust, sum is: 30
print in c, sum is: 30

可以看到,直接给 Rust 函数和 C 导入的函数签名添加返回值类型就可以了,两边的类型要保持一致。

C 向 Rust 传入一个数组计算元素的和并返回

前面两个例子是最简单的整型类型的参数传递,能说明 Rust 导出共享库的基本样板操作。但在函数参数这块儿,能说明的问题有限。下面,我们设计一个新的例子:C 向 Rust 传入一个数组计算元素的和并返回。

先来看 C 代码:

// csrc/ccode03.c

#include <stdio.h>
#include <stdint.h>

extern uint32_t sum_of_array(const uint32_t *numbers, size_t length);

int main(void) {
  uint32_t numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  unsigned int length = sizeof(numbers) / sizeof(*numbers);
  uint32_t sum = sum_of_array(numbers, length);
  printf("print in c, sum is: %d\n", sum);
}

配套的 Rust 代码:

// src/lib.rs

use std::slice;

#[no_mangle]
pub extern "C" fn sum_of_array(array: *const u32, len: usize) -> u32 {
    let array = unsafe {
        assert!(!array.is_null());

        slice::from_raw_parts(array, len)
    };

    array.iter().sum()
}

编译和运行代码:

编译 rust so:cargo build
编译 c binary:gcc -o ./ccode03 ./ccode03.c -L ./ -lrustffi3
运行:LD_LIBRARY_PATH=. ./ccode03

输出:

print in c, sum is: 55

分析代码后,可以看到。数组的传递,实际是剖分成两个要素传递:

  1. 数组的地址,或首元素指针(这两个本质是一样的),数组的指针的类型就是指向数组首元素的指针的类型;
  2. 数组长度。数组的长度不是数组所占字节的长度,而是元素个数。

可以看到,这个例子中,C 中的数组是分配在栈上的,并且在分配时直接初始化了。

Rust 代码中,参数中的 *const u32 就对应 C 中的 const uint32_t *

对于外界传入 Rust 的指针,Rust 这边,总是要先检查一下指针有效性的(确保不为空):

assert!(!array.is_null());

Rust 拿到 C 传递过来的指针后,标准的规范是:

  1. 尽早转换为 Rust 的安全类型进行操作。也就是说,保证不安全(unsafe块中的)的代码尽量少,并且直接使用这个指针的代码尽可能的少,转换成 Rust 中的标准类型再用。
  2. 尽量保证 zero cost。避免不必要的内存 copy 操作,影响性能。

为满足第一条规则,在转换前,我们的代码没有任何业务代码。

为满足第二条规则,这里使用了 slice 类型,而不是 Vec 类型:

let array = unsafe {slice::from_raw_parts(array, len)};

注意 from_raw_parts() 操作是 unsafe 的,因此需要包在 unsafe {} 中执行。

总结

本篇,我们研究了 Rust 导出动态链接库给 C 用的基本形式和规范。下一篇,我们会探讨字符串作为函数参数和返回值传递的细节。

本文所有代码在这里找到:https://github.com/daogangtang/learn-rust/tree/master/10rustffi3

本文分享自微信公众号 - Rust语言学习交流(rust-china),作者:Mike Tang

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【Rust日报】2019-11-21 主要使用 Rust 开发的 MesaTEE 正式进入 Apache 孵化器

    MesaTEE 是一个通用的安全计算框架,用于为安全关键场景提供通用计算服务。它结合了先进的混合内存安全(HMS)模型和可信计算技术(如 TPM )的能力,以及...

    MikeLoveRust
  • 【Rust日报】2020-01-05 推荐阅读——面向大众的Rust和WebAssembly教程

    sourmesh 是一款超快、轻量级核酸搜索和比对软件,此版本与sourmash 2.0兼容,包括sourmash Python API、命令行、签名和数据库文...

    MikeLoveRust
  • 【Rust日报】 2019-05-12:Snip开源神经网络推理引擎Tract

    今天有个Reddit讨论贴,有人指出每个发布到crates.io的crate都应该加上Readme说明和Repository地址(GitHub、GitLab等)...

    MikeLoveRust
  • 整理代码,将一些曾经用过的功能整合进一个spring-boot

    由于本人的码云太多太乱了,于是决定一个一个的整合到一个springboot项目里面。

    ydymz
  • 生动描述三次tcp握手

    TCP EDITOR 怎样生动描述 TCP 的「三次握手」? 「你瞅啥?」 「瞅你咋地?」 「来咱俩唠唠。」 然后就唠上了。 加上一些语气助词。 {爱我去,你瞅...

    java思维导图
  • Marathon/Mesos 集群排错记录

    部署某个镜像到Mesos集群的某个Agent一直停留在Waiting,但是在Mesos UI上发现这个Agent的资源是够的(4CPU/14G mem,只使用了...

    陆道峰
  • Can't run app with devtools and java 9

    https://github.com/spring-projects/spring-boot/issues/7565

    一个会写诗的程序员
  • Spring Boot切换为APR模式

    Spring Boot内置了tomcat容器,直接运行Application就可以启动web服务器。

    用户1154259
  • 记录一次 Spring boot 应用排错过程

    一个spring boot开发的项目,spring boot版本是1.5.7,携带的spring版本是4.1.3。开发反馈,突然在本地启动不起来了,表象特征就是...

    搜云库技术团队
  • 测试环境bug,dubbo调不到dubbo服务

    测试环境,cloud服务调dubbo服务再调dubbo,其他服务与本服务公用一个zk,其中一个总是注册不上,每次调用dubbo发现查不到数据,服务启动失败...

    疯狂的KK

扫码关注云+社区

领取腾讯云代金券