Rust 包装C库初始化/销毁例程的推荐方法

Rust 包装C库初始化/销毁例程的推荐方法,rust,Rust,我正在为一个C库编写一个包装器/FFI,它需要在主线程中进行一个全局初始化调用,也需要一个用于销毁的调用 以下是我目前的处理方式: struct App; impl App { fn init() -> Self { unsafe { ffi::InitializeMyCLib(); } App } } impl Drop for App { fn drop(&mut self) { unsafe { ff

我正在为一个C库编写一个包装器/FFI,它需要在主线程中进行一个全局初始化调用,也需要一个用于销毁的调用

以下是我目前的处理方式:

struct App;

impl App {
    fn init() -> Self {
        unsafe { ffi::InitializeMyCLib(); }
        App
    }
}

impl Drop for App {
    fn drop(&mut self) {
        unsafe { ffi::DestroyMyCLib(); }
    }
}
可以像这样使用:

fn main() {
    let _init_ = App::init();
    // ...
}
这很好,但感觉像是一个黑客,将这些调用绑定到一个不必要的结构的生命周期。将析构函数放在
finally
(Java)或
的_exit
(Ruby)块中从理论上讲似乎更合适

有没有更优雅的方法可以在铁锈中做到这一点

编辑

是否可以/安全地这样使用此设置(使用
lazy_static
板条箱),而不是上面的第二个块:

lazy_static! {
    static ref APP: App = App::new();
}
该引用是否保证在任何其他代码之前初始化并在退出时销毁?在库中使用
lazy\u static
是一种不好的做法吗

这也使得通过这一结构访问FFI变得更容易,因为我不必费心传递对实例化结构的引用(在我最初的示例中称为
\u init\u


这在某些方面也会使它更安全,因为我可以将
应用程序
结构默认构造函数设置为私有的。

我知道除了措辞强硬的文档之外,没有其他方法可以强制在主线程中调用方法。所以,忽略这个要求…:-)

一般来说,我会使用,这似乎基本上是为这种情况设计的:

一种同步原语,可用于运行一次性全局同步 初始化。用于FFI或相关金融机构的一次性初始化 功能。此类型只能用
一次\u INIT构建
价值观

请注意,没有任何清理规定;很多时候,你不得不泄露图书馆做过的任何事情。通常,如果一个库有一个专用的清理路径,那么它的结构也会将所有初始化的数据存储在一个类型中,然后作为某种上下文或环境传递给后续函数。这将很好地映射到锈蚀类型

警告

您当前的代码并不像您希望的那样具有保护性。由于您的
应用程序
是一个空结构,最终用户无需调用您的方法即可构造它:

我们将使用一个零大小的参数来防止这种情况。有关为FFI构造不透明类型的正确方法,请参见

总的来说,我会用这样的方式:

使用std::sync::一次;
mod ffi{
外部“C”{
pub fn InitializeMyCLib();
pub-fn-CoolMethod(arg:u8);
}
}
静态C_LIB_INITIALIZED:Once=Once::new();
#[衍生(复制、克隆)]
结构图书馆(());
实施图书馆{
fn new()->Self{
C|u LIB|u已初始化。调用|u一次(| |不安全{
ffi::InitializeMyCLib();
});
图书馆(())
}
fn冷却法(&self,参数:u8){
不安全{ffi::CoolMethod(arg)}
}
}
fn main(){
让lib=TheLibrary::new();
lib.cool_法(42);
}

我仔细研究了一下其他FFI LIB如何处理这种情况。以下是我目前使用的内容(类似于@Shepmaster的答案,大致基于的初始化例程):


然后,我在公共结构的公共构造函数中调用此函数。

旁注:根据您希望包装器的安全程度,您应该使
init
函数不安全(因为没有人可以多次调用它),并实现所有需要将clib初始化为
App
对象上的方法的函数。这样,没有人可以在没有初始化的情况下调用函数。您还可以将其实现为某种引用计数的单例,以确保初始化的安全性。这是Java和Ruby的一大胜利,因为在那里你可以调用函数,而不用在主线程中初始化lib——你确定它必须是主线程吗?它可以是任何线程,只要它在使用前已初始化?@ker,谢谢您的评论。我没有这样想过(通过
App
struct拥有所有访问权限,但这是有道理的。我必须考虑这是否适用于我的案例。@Shepmaster,谢谢你的评论。C库是GraphicsMagick,我只是浏览文档:
这个函数应该在主(原始)中调用应用程序进程的线程
。我是一个Rust初学者,主要来自Ruby,所以我不太了解“must”在线程使用方面。谢谢你的回答!你使用
一次
让我想起了
惰性静态
板条箱。我已经用另一种可能的方法更新了我的问题,但我不知道它是否合理。
let _init_ = App;
fn initialize() {
    static INIT: Once = ONCE_INIT;
    INIT.call_once(|| unsafe {
        ffi::InitializeMyCLib();
        assert_eq!(libc::atexit(cleanup), 0);
    });

    extern fn cleanup() {
        unsafe { ffi::DestroyMyCLib(); }
    }
}