#错误处理 #外部 #外国 #FFI

ffi_helpers

一个帮助简化FFI工作的crate

3个版本 (破坏性更新)

0.3.0 2021年10月25日
0.2.0 2020年4月7日
0.1.0 2018年4月21日

#371 in Rust模式

Download history 47632/week @ 2024-03-14 46619/week @ 2024-03-21 54724/week @ 2024-03-28 64297/week @ 2024-04-04 69808/week @ 2024-04-11 70948/week @ 2024-04-18 66944/week @ 2024-04-25 79008/week @ 2024-05-02 76728/week @ 2024-05-09 97785/week @ 2024-05-16 100729/week @ 2024-05-23 114693/week @ 2024-05-30 113218/week @ 2024-06-06 113501/week @ 2024-06-13 96180/week @ 2024-06-20 102778/week @ 2024-06-27

454,936 每月下载量
2 crate 中使用

MIT 许可证

48KB
646

FFI Helper

License Crates.io Documentation

一个使FFI代码更易于使用的crate。

这是我们工作中使用的工具crate的开源版本。最初目的是让Rust模块(DLL)更容易与我们的主GUI应用程序集成。我们发现它使用起来特别优雅和稳健,所以认为与全世界分享是一件好事。

特性

这试图为你提供一套抽象,可以在此基础上构建安全的API。它试图解决编写FFI代码时遇到的一些常见问题。

错误处理

错误处理通过一个私有的线程局部变量 LAST_ERROR 完成,它允许你使用类似于 errno 的机制来指示错误。

如果Rust函数返回一个 Result::Err(_),它将错误传递给 LAST_ERROR 并返回一个 明显错误 的值(例如,null0)。然后调用者检查这个返回值,并可以检查 LAST_ERROR 以获取更多信息。

提供了一个宏,允许你从C中检查 LAST_ERROR

空指针

null_pointer_check!() 宏将检查某些 可空 的东西是否为空,如果是,它将使用错误返回值(对于返回指针的函数是 null,对于整数是 0)并设置 LAST_ERROR 来指示遇到了空指针。

我们使用一个 Nullable trait 来表示具有某种 "明显无效" 值的任何东西(例如,null 指针,0)。

pub trait Nullable {
    const NULL: Self;

    fn is_null(&self) -> bool;
}

null_pointer_check!() 允许您检查某个特定的事物是否无效,设置 LAST_ERROR,并使用 Nullable::NULL 早期从当前函数返回。

实际上,这使得处理无效输入的可能性变得相当方便。

struct Foo {
  data: Vec<u8>,
}

#[no_mangle]
unsafe extern "C" fn foo_get_data(foo: *const Foo) -> *const u8 {
    null_pointer_check!(foo);

    let foo = &*foo;
    foo.data.as_ptr()
}

异常安全性

当一段Rust代码崩溃并尝试跨越FFI边界回溯时,异常安全性成为一个问题。目前这会导致程序中止,虽然不再是直接的 未定义行为,但这仍然是一个非常麻烦的问题。

存在一个 catch_panic() 函数,它允许您执行一些代码并将捕获任何回溯,适当更新 LAST_ERROR。宏 catch_panic!() 使这变得更容易一些,并且与 Nullable 特性一起工作,因此您可以从函数中退出,返回错误(Nullable::NULL)。

将闭包拆分为数据和代码

对于与回调一起工作的FFI函数来说,接受一个额外的 void *user_data 参数,该参数指向用户可能想要使用的任何额外状态,这是相当常见的。这并不允许程序员使用

可以使用 split_closure() 函数将闭包指针“拆分”为其数据的指针和一个 unsafe extern "C" fn(),它可以用作回调。

Rust闭包通过生成一个自定义类型来包含任何捕获的状态,并为 FnMut()(或 Fn(),或 FnOnce())实现。此函数通过将闭包指针强制转换为 void *(这是我们的数据)来工作(这是我们的数据),并定义一个trampoline函数,该函数将数据转换回并调用闭包。

这实际上是这个的泛化

fn split<C>(closure: &mut C) -> (*mut c_void, unsafe extern "C" fn(*mut c_void))
where C: FnMut()
{
    unsafe extern "C" fn trampoline<T>(user_data: *mut c_void) {
        let closure: &mut T = &mut *(user_data as *mut T);

        closure();
    }

    (closure as *mut C as *mut c_void, trampoline::<T>)
}

异步任务

任务API 帮助处理在后台线程上运行作业时遇到的复杂并发问题,同时保持内存和线程安全。

Task 特性本身非常简单

pub trait Task: Send + Sync + Clone {
    type Output: Send + Sync;
    fn run(&self, cancel_tok: &CancellationToken) -> Result<Self::Output, Error>;
}

然后您可以通过 export_task!() 宏生成绑定。这将声明各种 extern "C" 函数,用于在后台线程上创建 Task,定期检查是否完成,允许您取消任务,然后检索结果并在之后适当地清理一切。

这可能是该库的 杀手特性,因为它允许您轻松地在后台运行Rust任务,允许您将其集成到更大的应用程序/GUI中。

强烈建议访问 task 模块的文档,以获取更详细的说明。

依赖项

~0.4–1MB
~21K SLoC