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

时间:2016-04-01 14:47:41

标签: rust

我正在为一个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();
    // ...
}

这很好用,但感觉就像是一个黑客,将这些调用绑定到不必要的struct的生命周期。在finally(Java)或at_exit(Ruby)块中使用析构函数似乎在理论上更合适。

在Rust中有更优雅的方法吗?

修改

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

lazy_static! {
    static ref APP: App = App::new();
}

是否可以保证在任何其他代码之前初始化此引用并在退出时销毁?在库中使用lazy_static是不好的做法吗?

这也可以通过这个结构更方便地访问FFI,因为我不必费心地传递对实例化结构的引用(在我的原始示例中称为_init_)。 / p>

这也会使它在某些方面更安全,因为我可以将App struct默认构造函数设为私有。

2 个答案:

答案 0 :(得分:2)

我知道没有办法强制在强主语文档之外的主线程中调用一个方法。所以,忽略那个要求......: - )

一般来说,我使用std::sync::Once,这似乎基本上是针对这种情况设计的:

  

可用于运行一次性全局的同步原语   初始化。对FFI或相关的一次性初始化很有用   功能。此类型只能使用ONCE_INIT构建   值。

请注意,没有任何清理条款;很多时候你只需泄漏图书馆所做的任何事情。通常,如果库具有专用的清理路径,则它也被构造为将所有初始化的数据存储在一个类型中,然后作为某种上下文或环境传递给后续函数。这将很好地映射到Rust类型。

警告

您当前的代码 并不像您希望的那样具有保护性。由于您的App是一个空结构,最终用户可以构建它而无需调用您的方法

let _init_ = App;

目前我无法通过详细信息找到相应的问题,但我会使用PhantomData使其免受模块外部的影响。

总而言之,我会使用这样的东西:

use std::sync::{Once, ONCE_INIT};
use std::marker::PhantomData;

mod ffi {
    extern {
        pub fn InitializeMyCLib();
        pub fn CoolMethod(arg: u8);
    }
}

static C_LIB_INITIALIZED: Once = ONCE_INIT;

#[derive(Copy, Clone)]
struct TheLibrary {
    marker: PhantomData<()>,
}

impl TheLibrary {
    fn new() -> Self {
        C_LIB_INITIALIZED.call_once(|| {
            unsafe {
                ffi::InitializeMyCLib();
            }
        });
        TheLibrary {
            marker: PhantomData,
        }
    }

    fn cool_method(&self, arg: u8) {
        unsafe { ffi::CoolMethod(arg) }
    }
}

fn main() {
    let lib = TheLibrary::new();
    lib.cool_method(42);
}

答案 1 :(得分:1)

我做了一些挖掘,看看其他FFI库如何处理这种情况。这是我目前正在使用的内容(类似于@ Shepmaster的答案,基于curl-rust的初始化例程):

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(); }
    }
}

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