C 调用 Rust 使用示例

2024-06-20 language rust c/cpp

整理 C 调用 Rust 时需要注意的事项。

简介

最简单通过 C/CPP 调用 Rust 生成函数。

----- 新建Rust库,默认会添加add函数
cargo new hello --lib
----- 将函数修改为如下
#[no_mangle]
pub extern "C" fn hello() {
    println!("Hello World");
}
----- 同时修改Cargo.toml,增加如下内容,生成静态库和动态库
[lib]
crate-type = ["staticlib", "cdylib"]

修改后的函数增加了 no_mangle 这样函数名不会修改,可以直接通过符号调用;另外,通过 extern "C" 声明函数调用方式,核心就是调用函数时的出栈入栈方式。

然后通过 cargo build 编译即可,此时会在 src/target 目录下生成 libhello.solibhello.a 两个库。

对应的 C 实现 main.c 如下,如果是 CPP 则需要在声明时添加 extern "C" {...} 包含,否则会因为 CPP 的 mangle 导致连接时出现 undefined reference 的报错。

void hello();

int main(void)
{
	hello();
	return 0;
}

然后通过如下命令编译运行即可。

----- 使用动态库连接,运行时会同时要求动态库位置
gcc main.c -o test -L./hello/target/debug -lhello
LD_LIBRARY_PATH=./hello/target/release ./test
----- 或者使用静态库,此时可以直接运行
gcc main.c -o test ./hello/target/debug/libhello.a
./test

使用示例

基础类型参数

最常见的是基础类型,例如 int double 等,只需要注意两种不同类型语言之间的差异即可。

#[no_mangle]
pub extern "C" fn add(left: u64, right: u64) -> u64 {
    left + right
}
#include <stdio.h>
#include <stdint.h>

uint64_t add(uint64_t, uint64_t);

int main(void) {
	printf("%ld\n", add(10, 20));
	return 0;
}

另外,在 Rust 的 std::os::raw 中包含很多 c_xxx 的类型声明,其对应了 C 中的类型,例如 c_int 就是 C 语言中的 int 类型。

数组参数

这里只展示 Rust 的代码。

#[no_mangle]
pub extern fn sum(arr: *const c_int, length: c_int) -> c_int {
    let nums = unsafe {
        assert!(!arr.is_null());
        slice::from_raw_parts(arr, length as usize)
    }
    let sum = nums.iter().fold(0, |acc, v| acc + v);
    sum as c_int
}

结构体参数

在 Rust 需要指定与 C 相同的布局方式,或者 #[repr(C, packed)] 不对齐。

#[repr(C)]
pub struct Rectangle {
    width: u64,
    height: u64,
}

#[no_mangle]
pub extern "C" fn area(rect: Rectangle) -> u64 {
    rect.width * rect.height
}
#include <stdint.h>
#include <stdio.h>

struct Rectangle {
    uint64_t width, height;
};

uint64_t area(struct Rectangle);

int main(void) {
    struct Rectangle rect = {10, 20};
    printf("%ld\n", area(rect));
    return 0;
}

另外,如果使用的是引用,而 C 语言中不存在,可以通过 Rust 中的原始指针,包括了 *const T *mut T 两种,分别对应了 &T&mut T 两种类型。在 Rust 中的引用会进行一系列的检查,而 raw pointer 则会忽略,使用时需要 unsafe 关键字。

示例如下。

#[repr(C)]
pub struct Rectangle {
    width: u64,
    height: u64,
}

#[no_mangle]
pub extern "C" fn area(rect: *const Rectangle) -> u64 {
    if rect.is_null() {
        return 0;
    }
    //unsafe { (*rect).width * (*rect).height }

    let rec: &Rectangle = unsafe { &(*rect) };
    rec.height * rec.width
}
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

struct Rectangle {
    uint64_t width, height;
};

uint64_t area(struct Rectangle *);

int main(void) {
    struct Rectangle *rect = malloc(sizeof(struct Rectangle));
    rect->width = 10;
    rect->height = 20;
    printf("%ld\n", area(rect));
    free(rect);
    return 0;
}

在上述的 Rust 代码中,可以直接将 *const 修改为 *mut 然后进行修改。