Rust 调用 C 使用示例

2024-06-24 language rust c/cpp

简单介绍 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
}