简单介绍 Rust 调用 C 常用的示例代码。
简介
使用的时候可以在 Rust 工程中,或者是独立的 C 库提供了 Rust 的封装。如下通过简单示例介绍,提供一个 libsysx
的系统库,通过 C 语言对底层的系统调用进行封装,然后由 Rust 调用。
目录结构如下。
├── Cargo.toml
├── README.md
├── libsysx/
│ ├── CMakeLists.txt
│ ├── include/
│ │ └── math.h
│ └── src/
│ └── math.c
├── build.rs
└── src/
└── main.rs
手动编译
对应内容为。
// math.h
#ifndef __LIBSYSX_H__
#define __LIBSYSX_H__
int add(int left, int right);
#endif
// math.c
int add(int left, int right) {
return left + right;
}
然后手动生成动态库和静态库,并复制到 target/{debug,release}/deps
目录下。
$ gcc -shared -fPIC -o libsysx.so -I include src/math.c
$ gcc -c -o math.o -I include src/math.c
$ ar rc libsysx.a math.o
对应的 Rust 代码如下,使用静态库链接,如果是动态库需要将路径添加到 LD_LIBRARY_PATH
环境变量中,然后通过 cargo run/build
运行即可。
//#[link(name = "math", kind = "dylib")]
#[link(name = "math", kind = "static")]
extern "C" {
fn add(left: isize, right: isize) -> isize;
}
fn main() {
println!("{}", unsafe { add(10, 20) });
}
CMake
可以通过 Makefile
管理,不过当前大部分的 C/CPP 工程会使用 CMake
管理,这里同时会生成动态和静态库,更多内容可以参考 CMake 使用简介 。
CMAKE_MINIMUM_REQUIRED(VERSION 3.9)
PROJECT(sysx)
SET(SRC_LIST src/math.c)
SET(LIB_STATIC_NAME ${PROJECT_NAME}-static)
SET(LIB_SHARED_NAME ${PROJECT_NAME}-shared)
ADD_LIBRARY(${LIB_STATIC_NAME} STATIC ${SRC_LIST})
SET_TARGET_PROPERTIES(${LIB_STATIC_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
ADD_LIBRARY(${LIB_SHARED_NAME} SHARED ${SRC_LIST})
SET_TARGET_PROPERTIES(${LIB_SHARED_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
INSTALL(TARGETS ${LIB_STATIC_NAME} DESTINATION .)
INSTALL(TARGETS ${LIB_SHARED_NAME} DESTINATION .)
此时常规的可以通过如下命令生成。
cd libsysx && make build && cd build
cmake ..
make
不过建议通过如下命令在 target/libsysx
目录下生成,可以直接在工程目录下执行如下命令。
cmake -S libsysx -B target/libsysx
cmake --build target/libsysx
上述命令也是后续 build.rs
中使用。
build.rs
这里直接使用命令行进行编译,对应代码如下。
use std::process::Command;
fn main() {
let libs = "libsysx";
let target = "target/libsysx";
std::fs::create_dir(target).unwrap();
Command::new("cmake")
.args(&["-S", libs, "-B", target])
.status()
.unwrap();
Command::new("cmake")
.args(&["--build", target])
.status()
.unwrap();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-link-search=native={}", target);
println!("cargo:rustc-link-lib=static=sysx");
}
使用示例
数组参数
通过指针+整数方式传递,指针指定的真实数据,而整数则是数组的大小,这里仅列出相关的代码。
// math.c
int sum01(const int *arr, int length) {
int total = 0;
for (int i = 0; i < length; i++) {
total += arr[i];
}
return total;
}
int sum02(const int arr[5]) {
int total = 0;
for (int i = 0; i < 5; i++) {
total += arr[i];
}
return total;
}
// main.rs
use std::os::raw::c_int;
//#[link(name = "sysx", kind = "dylib")]
#[link(name = "sysx", kind = "static")]
extern "C" {
fn sum01(arr: *const c_int, length: c_int) -> c_int;
fn sum02(arr: &[c_int; 5]) -> c_int;
}
fn main() {
let numbers: [c_int; 5] = [1, 2, 3, 4, 5];
unsafe {
let total = sum01(numbers.as_ptr(), numbers.len() as c_int);
println!("sum(1, 2, 3, 4, 5) = {}", total);
let total = sum02(&numbers);
println!("sum(1, 2, 3, 4, 5) = {}", total);
}
}
如果是可修改的,那么可以通过 as_mut_ptr()
替换原 as_ptr()
函数。还有一种场景是已经明确知道数组的长度,就可以通过 [c_int; 5]
类似的方式传入。
其它
注意事项
不要使用 bool
在 stdbool.h
头文件中包含了定义,在 clang-16
内容如下。
#define bool _Bool
#define true 1
#define false 0
而 _Bool
是 C99 引入的 关键字,但是,Rust 中的 std::os::raw
只定义了常规的类型,包含了如下类型。
alias_core_ffi! {
c_char c_schar c_uchar
c_short c_ushort
c_int c_uint
c_long c_ulong
c_longlong c_ulonglong
c_float
c_double
c_void
}