本章,我们继续来玩耍跨Rust 和 C FFI边界的指针。
设计
本节,我们设计这样一个示例:
示例 1
- Rust 导出一个 so 库
- main 函数在 C 这边,链接 Rust 的 so 库
- C 中分配栈内存,交由 Rust 端填充
- Rust 端打印
- C 端打印
示例 2
- 同样的示例,C 中分配堆内存,交由 Rust 端填充,并且两边分别打印。
下面我们直接看示例1的代码。
示例 1 代码
Rust 端。
// src/lib.rs
use std::os::raw::c_int;
use std::slice;
#[repr(C)]
#[derive(Debug)]
pub struct Student {
pub num: c_int,
pub total: c_int,
}
#[no_mangle]
pub extern "C" fn fill_students(p_stu: *mut Student, n: c_int) {
assert!(!p_stu.is_null());
let s: &mut [Student] = unsafe { slice::from_raw_parts_mut(p_stu, n as usize) };
for elem in s.iter_mut() {
// fill any valid values
elem.num = 1 as c_int;
elem.total = 100 as c_int;
}
}
#[no_mangle]
pub extern "C" fn print_students(p_stu: *mut Student, n: c_int) {
assert!(!p_stu.is_null());
let s: &[Student] = unsafe { slice::from_raw_parts(p_stu, n as usize) };
for elem in s.iter() {
println!("print in rust side: {:?}", elem);
}
}
记得 Cargo.toml 加上:
[lib]
crate-type = ["cdylib"]
C 端
// csrc/cfoo1.c
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
typedef struct Students {
int num; // serial number
int total; // total score
} Student;
extern void fill_students(Student *stu, int);
extern void print_students(Student *stu, int);
void print_students_c(Student *stu, int n) {
int i;
for (i=0; i<n; i++) {
printf("C side print: %d %d\n", stu[i].num, stu[i].total);
}
}
void main() {
int len = 10;
Student students[len];
// call rust fill and print functions
fill_students(students, len);
print_students(students, len);
// call c print function
print_students_c(students, len);
}
C 端代码这样编译:
gcc -o ./cfoo1 ./cfoo1.c -L ./ -lrustffi4
(注意,我已经将 cargo build 生成的 librustffi4.so 文件从 target/debug/ 目录拷贝至 C 代码所在目录)
C 端二进制运行:
LD_LIBRARY_PATH=. ./cfoo1
结果如下:
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
可以看到,C的栈空间上分配的结构体数组,已经被Rust这边成功填充了。
感觉已经没什么可讲的了。看过本教程之前内容的同学,应该会秒懂。
接着来看示例2的代码。
示例2的代码
示例2的代码,Rust 这边没有变化。下面直接看 C 这边的代码:
// csrc/cfoo2.c
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
typedef struct Students {
int num; // serial number
int total; // total score
} Student;
extern void fill_students(Student *stu, int);
extern void print_students(Student *stu, int);
Student* create_students(int n) {
if (n <= 0) return NULL;
Student *stu = NULL;
stu = (Student*) malloc(sizeof(Student)*n);
return stu;
}
void release_students(Student *stu) {
if (stu != NULL)
free(stu);
}
void print_students_c(Student *stu, int n) {
int i;
for (i=0; i<n; i++) {
printf("C side print: %d %d\n", stu[i].num, stu[i].total);
}
}
void main() {
int len = 10;
Student* students = create_students(len);
// call rust fill and print functions
fill_students(students, len);
print_students(students, len);
// call c print function
print_students_c(students, len);
release_students(students);
}
C 端代码这样编译:
gcc -o ./cfoo2 ./cfoo2.c -L ./ -lrustffi4
(注意,我已经将 cargo build 生成的 librustffi4.so 文件从 target/debug/ 目录拷贝至 C 代码所在目录)
C 端二进制运行:
LD_LIBRARY_PATH=. ./cfoo2
结果如下:
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
print in rust side: Student { num: 1, total: 100 }
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
C side print: 1 100
可以看到,两个示例打印结果完全一致。
示例2的C语言这边是在堆上 malloc 了一块内存,所以程序结束的时候,要记得 free 掉。
我们从两个示例的对比可以看到,C 这边栈和堆的指针,都可以用相同的 Rust 的代码。也就是说,Rust 这边,它就认 C 的指针,而不管这个指针是从哪里来,栈也好,堆也好,甚至其它地址的指针也好,对 Rust 来说,其实都一样(本质上都是内存指针)。
结论
本章通过构造两个示例,演示了 Rust导出共享库 的一个操作场景,例子清晰明了,可细品。
本章示例的所有代码,皆可在:https://github.com/daogangtang/learn-rust/tree/master/11rustffi4 找到。
评论区
写评论还没有评论