首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

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

#include

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

#include

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

#include

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

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

数组的地址,或首元素指针(这两个本质是一样的),数组的指针的类型就是指向数组首元素的指针的类型;

数组长度。数组的长度不是数组所占字节的长度,而是元素个数。

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

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

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

assert!(!array.is_null());

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

尽早转换为 Rust 的安全类型进行操作。也就是说,保证不安全(unsafe块中的)的代码尽量少,并且直接使用这个指针的代码尽可能的少,转换成 Rust 中的标准类型再用。

尽量保证 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

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200716A0YSXI00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券