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

设计

本节,我们设计这样一个示例:

示例 1

  1. Rust 导出一个 so 库
  2. main 函数在 C 这边,链接 Rust 的 so 库
  3. C 中分配栈内存,交由 Rust 端填充
  4. Rust 端打印
  5. C 端打印

示例 2

  1. 同样的示例,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导出共享库 的一个操作场景,例子清晰明了,可细品。


上一篇:Rust FFI 编程 - Rust导出共享库06


下一篇:linux-找不到FFI错误模块