整理 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.so
和 libhello.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
然后进行修改。