Rust 语言主要在关键字和标准库两个方面对 FFI 提供了支持,具体如下:
关键字 extern
属性 #[no_mangle]
外部块 ExternBlock 及其属性 link 和 link_name
标准库
std:os:raw 模块:例如c_char。
std:ffi 模块:传递 UTF-8 字符串时,CString和CStr很有用。
你可以使用 libc::foo 这种形式访问这个库中的任何导出内容。
在Rust里,只能创建子线程,如果想创建子进程,就需要用到libc库
fn main() {
unsafe {
let pid = libc::fork(); if pid > 0 {println!("Hello, I am parent thread: {}", libc::getpid());}
else if pid == 0 {println!("Hello, I am child thread: {}", libc::getpid());println!("My parent thread: {}", libc::getppid()); }
else {println!("Fork creation failed!");}}}
1.libc 的所有函数调用,都必须放进 unsafe 块中。因为它的所有调用都是 unsafe 的;
2.std 的线程操作封装,好用,形象。libc 的进程操作,与 C 语言系统编程一样,完全是另外一套思路和编程风格;
3.std 的线程操作虽然简洁,但是也缺少更细颗粒度的控制。而 libc 可以对进程的操作(及后面对子进程的功能扩充,父进程中的信号管理等),做到完全的控制,更加灵活,功能强大;
4.std 本身无法实现进程 fork 的功能。
因为我 Rust 的封装是 zero cost (零成本)的。零成本抽象赋予了 Rust 系统编程的能力。
libc 与 std::os:😗::raw,这里面有的用法是一样的,没有任何问题。简单的和C交互可以用os:raw里面的,而一旦产生了系统调用或者 Unix 环境编程,那么就得引入 libc 库来操作。
这个工具就是将写好的Rust代码配置一下,然后会自动生成接口代码头文件等等。其实,FFI封装、转换,熟悉了之后,知识点就那些,模式也比较固定,如果接口量很大,那就需要大量重复的 coding。量一大,人手动绑定出错的机率也大。所以这种辅助工具的意义就显露出来了。基于辅助工具生成的代码,如不完美,再适当手动修一修,几下就能搞定,大大提高生产效率。
在Rust中,存在三种类型的指针:
&T:它是对类型T的不可变引用
&mut T:它是对类型T的可变引用
众所周知,Rust语言的指针是一种安全的指针,它会遵循一定的规则,比如ownership规则,会确保不出现悬挂指针。但是当我们需要写一些底层框架的时候,往往需要绕过这些规则,自由的控制指针,这时候我们就可以使用原始指针。
*const T:表示指向类型T的不可变原始指针。它是Copy类型。这类似于&T,只是它可以为空值。
*mut T:一个指向T的可变原始指针,它不支持Copy特征(non-Copy)。
以下可以定义Rust的原始指针:
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
}
管理原始指针非常不安全,开发者在使用它们时需要注意很多细节。不恰当地使用它们可能会以非常隐蔽的方式导致诸如内存泄漏、引用挂起,以及大型代码库中的双重释放等问题。为了解决这些问题,我们可以使用C++中广泛采用的智能指针。
智能指针的两个特性:Drop和Deref
Drop:这是多次提及的特征,它可以自动释放相关值超出作用域后占用的资源。Drop特征类似于你在其他语言中遇到的被称为对象析构函数的东西。它包含一个drop方法,当对象超出作用域时就会被调用。该方法将&mut self作为参数。使用drop释放值是以LIFO的方式进行的。也就是说,无论最后构建的是什么,都首先会被销毁。drop方法是你为自己的结构体放置清理代码的理想场所。例如使用引用计数值或GC时,它尤其方便。当我们实例化任何Drop实现值时(任意堆分配类型),Rust编译器会在编译后的代码中每个作用域结束的位置插入drop方法调用。因此,我们不需要在这些实例上手动调用drop方法。
Deref:为了提供与普通指针类似的行为,也就是说,为了能够解引用被指向类型的调用方法,智能指针类型通常会实现Deref特征,这允许用户对这些类型使用解引用运算符*。虽然Deref只为你提供了只读权限,但是还有DerefMut,它可以为你提供对底层类型的可变引用。
智能指针的种类:
标准库中的智能指针有如下几种。
Box:它提供了最简单的堆资源分配方式。Box类型拥有其中的值,并且可用于保存结构体中的值,或者从函数返回它们。
Rc:它用于引用计数。每当获取新引用时,计数器会执行递增操作,并在用户释放引用时对计数器执行递减操作。当计数器的值为零时,该值将被移除。
Arc:它用于原子引用计数。这与之前的类型类似,但具有原子性以保证多线程的安全性。
Cell:它为我们提供实现了Copy特征的类型的内部可变性。换句话说,我们有可能获得多个可变引用。
RefCell:它为我们提供了类型的内部可变性,并且不需要实现Copy特征。它用于运行时的锁定以确保安全性。
引用计数指针:
所有权规则只允许某个给定作用域中存在一个所有者。但是,在某些情况下你需要与多个变量共享类型。例如在GUI库中,每个子窗体小部件都需要具有对其父容器窗口小部件的引用,以便基于用户的resize事件来调整子窗口的布局。虽然有时生命周期允许你将父节点存储为&'a Parent,但是它通常受到’a值生命周期的限制,一旦作用域结束,你的引用将失效。在这种情况下,我们需要更灵活的方法,并且需要使用引用计数类型。程序中的这些智能指针类型会提供值的共享所有权。
引用计数类型支持某个粒度级别的垃圾回收。在这种方法中,智能指针类型允许用户对包装值进行多次引用。在内部,智能指针使用引用计数器(这里是refcount)来统计已发放的并且活动的引用数量,不过它只是一个整数值。当引用包装的智能指针值的变量超出作用域时,refcount的值就会递减。一旦该对象的所有引用都消失,refcount的值也会变成0,之后该值会被销毁。这就是引用计数指针的常见工作模式。
Rust为我们提供了两种引用计数指针类型。
Rc:这主要用于单线程环境。
Arc:这主要用于多线程环境。
1.pub extern “C” fn sum_of_array(array: *const u32, len: usize) -> u32
slice::from_raw_parts(array,len)
C端传来的数组(指针类型),进到Rust这边进行强制类型转换,变成非可变原始指针类型。函数slice::from_raw_parts(array,len)就是对原始指针进行转换为Rust切片类型,切片就是一个指针+一个长度即可。
2.CStr::from_ptr(raw_string):CStr就是C端产生数据,Rust端使用,
只是借用,常用于打印。raw_string是直接从C接过来的可变原始指针。
使用std::ffi::CStr提供的from_ptr方法包装 C 的字符串指针,它基于空字符’\0’来计算字符串的长度,并可以通过它将外部 C 字符串转换为 Rust 的 &str和String
use std::ffi::CStr;
use libc::c_char;
extern {
fn char_func() -> *mut c_char;
}
fn get_string() -> String {
unsafe {
let raw_string: *mut c_char = char_func();
let cstr = CStr::from_ptr(raw_string);
cstr.to_string_lossy().into_owned()
}
}
3.CStr::from_ptr(s).to_string_lossy().into_owned():注意to_string_lossy()的使用:因为在rust中一切字符都是采用utf8表示的而c不是,
因此如果要将c的字符串转换到rust字符串的话,需要检查是否都为有效utf-8字节。
4.CString::new(“Hello, world!”).as_ptr():Cstring是Rust端产生数据,C端进行使用。
as_ptr()就是将RustCString指针类型转化为C的原始指针类型。
5.CString::new(“Hello world!”).into_raw()
使用std::ffi::CString提供的一对方法into_raw和from_raw可以进行原始指针转换,由于将字符串的所有权转移给了调用者,所以调用者必须将字符串返回给 Rust,以便正确地释放内存。
into_raw()和.as_ptr()的作用类似,都是变成原始指针传给C端。
6.CString::from_raw(s)
一般在释放内存的时候使用,C端用完需要Rust端来释放。
7.Box::into_raw(Box::new(new_stu)):其实这里是智能指针和两端堆栈申请有关,into_raw()就是将Rust智能指针变成原始指针。
8.Box::from_raw(p_stu):from_raw():就是将C端传来的p_stu变成Rust智能指针。
C代码:
uint32_t sum = sum_of_array(numbers, length);
Rust代码:
pub extern "C" fn sum_of_array(array: *const u32, len: usize) -> u32 {
let array = unsafe {
assert!(!array.is_null());
slice::from_raw_parts(array, len)
};
array.iter().sum()
}
这里的参数传递一目了然,array一开始是C过来的指针类型,通过slice::from_raw_parts(array,len)之后,变成一个Rust切片类型,后面用iter进行求和。切片类型就是一个指针和一组数据合在一起组成。
对于C语言来说,字符串有两种,一种是共享的只读字符串 char * ,不能修改。另一种是动态分配的可变字符串 char [],可以修改。
而在Rust里面,字符串是由字符的 UTF-8 编码组成的字节序列。表示的类型有很多种。
字符串则比较复杂,Rust 中的字符串,是一组u8组成的 UTF-8 编码的字节序列,字符串内部允许NULL字节;但在 C 中,字符串只是指向一个char的指针,用一个NULL字节作为终止。
我们需要做一些特殊的转换,在 Rust FFI 中使用std::ffi::CStr,它表示一个NULL字节作为终止的字节数组,可以通过 UTF-8 验证转换成 Rust 中的&str。
CStr:表示以空字符终止的 C 字符串或字节数组的借用,属于引用类型。一般用于和 C 语言交互,由 C 分配并被 Rust 借用的字符串。
CString:表示拥有所有权的,中间没有空字节,以空字符终止的字符串类型。一般用于和 C 语言交互时,由 Rust 分配并传递给 C 的字符串。
下面这段代码,在这里get_string使用CStr::from_ptr从C的char*获取一个字符串,并且转化成了一个String。
fn get_string() -> String {
unsafe {
let raw_string: *mut c_char = char_func();
let cstr = CStr::from_ptr(raw_string);
cstr.to_string_lossy().into_owned()
}
}
和CStr表示从C中来,rust不拥有归属权的字符串相反,CString表示由rust分配,Rust拥有所有权,可以进行修改,用以传给C程序的字符串。
use std::ffi::CString;
use std::os::raw::c_char;
extern {
fn my_printer(s: *const c_char);
}
let c_to_print = CString::new("Hello, world!").unwrap();
unsafe {
my_printer(c_to_print.as_ptr()); // 使用 as_ptr 将CString转化成char指针传给c函数
}