< 返回版块

Mike Tang 发表于 2020-08-20 22:08

Tags:rust,ffi,

本章,我们继续来玩耍跨Rust 和 C FFI边界的指针。

设计

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

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

本章示例的所有代码,皆可在:https://github.com/daogangtang/learn-rust/tree/master/11rustffi4 找到。

评论区

写评论

还没有评论

1 共 0 条评论, 1 页