包装 C lib 初始化/销毁例程的推荐方法 [英] Recommended way to wrap C lib initialization/destruction routine

查看:36
本文介绍了包装 C lib 初始化/销毁例程的推荐方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am writing a wrapper/FFI for a C library that requires a global initialization call in the main thread as well as one for destruction.

这是我目前的处理方式:

Here is how I am currently handling it:

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) 或 at_exit (Ruby) 块中使用析构函数在理论上似乎更合适.

This works fine, but it feels like a hack, tying these calls to the lifetime of an unnecessary struct. Having the destructor in a finally (Java) or at_exit (Ruby) block seems theoretically more appropriate.

在 Rust 中有没有更优雅的方法来做到这一点?

Is there some more graceful way to do this in Rust?

编辑

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

Would it be possible/safe to use this setup like so (using the lazy_static crate), instead of my second block above:

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

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

Would this reference be guaranteed to be initialized before any other code and destroyed on exit? Is it bad practice to use lazy_static in a library?

这也将使通过这个结构更容易访问 FFI,因为我不必费心传递对实例化结构的引用(在我的原始示例中称为 _init_).

This would also make it easier to facilitate access to the FFI through this one struct, since I wouldn't have to bother passing around the reference to the instantiated struct (called _init_ in my original example).

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

This would also make it safer in some ways, since I could make the App struct default constructor private.

推荐答案

我不知道除了措辞强硬的文档之外,没有办法强制在主线程中调用方法.因此,忽略该要求... :-)

I know of no way of enforcing that a method be called in the main thread beyond strongly-worded documentation. So, ignoring that requirement... :-)

通常,我会使用 std::sync::Once,这似乎基本上是为这种情况设计的:

Generally, I'd use std::sync::Once, which seems basically designed for this case:

一种同步原语,可用于运行一次性全局初始化.用于 FFI 或相关的一次性初始化功能.这种类型只能用 ONCE_INIT 构造价值.

A synchronization primitive which can be used to run a one-time global initialization. Useful for one-time initialization for FFI or related functionality. This type can only be constructed with the ONCE_INIT value.

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

Note that there's no provision for any cleanup; many times you just have to leak whatever the library has done. Usually if a library has a dedicated cleanup path, it has also been structured to store all that initialized data in a type that is then passed into subsequent functions as some kind of context or environment. This would map nicely to Rust types.

警告

您当前的代码没有像您希望的那样具有保护性.由于您的 App 是一个空结构,最终用户可以在不调用您的方法的情况下构建它:

Your current code is not as protective as you hope it is. Since your App is an empty struct, an end-user can construct it without calling your method:

let _init_ = App;

我们将使用零大小的参数来防止这种情况.另请参阅定义指向 C 不透明指针的字段的 Rust 习语是什么? 了解为以下对象构造不透明类型的正确方法外国金融机构.

We will use a zero-sized argument to prevent this. See also What's the Rust idiom to define a field pointing to a C opaque pointer? for the proper way to construct opaque types for FFI.

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

Altogether, I'd use something like this:

use std::sync::Once;

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

static C_LIB_INITIALIZED: Once = Once::new();

#[derive(Copy, Clone)]
struct TheLibrary(());

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

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

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

这篇关于包装 C lib 初始化/销毁例程的推荐方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆