Rust调用C/C++
以 string.h 为例,调用string类库的功能(实际上没必要,因为rust stdlib中已经预置了字符串操作比较丰富的API,这里只是作为一个示例,毕竟字符串类库基本是大家最常用的类库了。)
工程搭建
cargo new --lib string-sysCargo.toml配置
[package]
name = "string-sys"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
bindgen = "0.59"
使用bindgen生成Rust low-level API
我们在项目根目录中添加build.rs文件,并且编写如下代码,作为构建逻辑:
fn main() {
let bindings = bindgen::Builder::default()
.header("wrapper.h") // 要读取的头文件定义,就是根据这个头文件生成必要的Rust API
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Unable to generate bindings.");
bindings
.write_to_file("src/bindings.rs") // 将生成的binding存储的路径.
.expect("Failed to write bindings.");
}
项目根目录 中新建wrapper.h文件,里面include我们需要使用的c/c++的API头文件。比如此实例中就是string.h
#include "string.h"
执行cargo build 命令,执行成功之后,会在src目录中,生成一个bindings.rs文件,代码很长,大家可自行查看。
封装API,将底层API封装成Rust-like API
bindings.rs文件中的API都是C语言转义到Rust侧的原始接口,这种调用对于实际的调用者是不友好的。所以一般我们需要简化操作,对这些底层的API再封装一层,形成“高级”API供其他crate或者使用者调用。
以strcat和strlen的封装为例:
修改lib.rs
#![allow(unused)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(deref_nullptr)]
mod bindings;
use bindings as cc_str;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
/// 重新封装函数,入参以及返回类型,适配成Rust-Like的形式,而不是各种指针类型。
pub fn concat(dest: &str, src: &str) -> String {
let mut dest_cstring = CString::new(dest).unwrap();
let dest_ptr: *mut c_char = dest_cstring.as_ptr() as *mut c_char;
let src = CString::new(src).unwrap();
let src_ptr: *const c_char = src.as_ptr();
unsafe {
let result_ptr = cc_str::strcat(dest_ptr, src_ptr);
CStr::from_ptr(result_ptr).to_string_lossy().into_owned()
}
}
pub fn str_len(s: &str) -> u64 {
let s = CString::new(s).unwrap();
let s: *const c_char = s.as_ptr();
unsafe { cc_str::strlen(s) }
}
#[cfg(test)]
mod test_suite {
use super::*;
#[test]
fn test_strlen() {
assert_eq!(11 as u64, str_len("Scathon Lin"));
}
#[test]
fn test_concat() {
let result = concat("Hello ", "World!");
assert_eq!("Hello World!", result);
}
}
测试
bindings.rs文件中本身也包含了一些测试代码,但是我们对于自己封装的高级API也需要进行单元测试, 编写完测试例执行cargo test命令,查看测试是否通过。
注意点
有些C的头文件可能生成的bindings.rs是存在问题的,比如math.h,存量变量冲突,这种的可能需要手动调整bindings.rs文件。否则构建会失败。