【问题标题】:Recommended way to wrap C lib initialization/destruction routine包装 C lib 初始化/销毁例程的推荐方法
【发布时间】:2016-04-01 14:47:40
【问题描述】:

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

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

在 Rust 中是否有更优雅的方式来做到这一点?

编辑

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

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

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

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

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

【问题讨论】:

  • 旁注:根据您希望包装器的安全程度,您应该使 init 函数不安全(因为没有人可能会多次调用它),并实现所有需要 clib 的函数被初始化为App 对象上的方法。这样,没有人可以在没有初始化的情况下调用这些函数。您还可以将其实现为某种引用计数单例,以使其安全初始化。这是对 Java 和 Ruby 的一大胜利,因为您可以在没有初始化 lib 的情况下调用函数
  • 在主线程中——你确定它必须是主线程吗?它可以是任何线程,只要它在使用前被初始化?
  • @ker,感谢您的评论。我没有这样想过(通过App 结构进行所有访问,但这是有道理的。我将不得不考虑这是否适用于我的情况。
  • @Shepmaster,感谢您的评论。 C 库是 GraphicsMagick,我只是参考文档:This function should be invoked in the primary (original) thread of the application's process。我是一名主要来自 Ruby 的 Rust 初学者,所以我并不真正了解线程使用方面的“必须”。

标签: rust


【解决方案1】:

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

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

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

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

警告

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

let _init_ = App;

我们将使用一个大小为零的参数来防止这种情况。另请参阅What's the Rust idiom to define a field pointing to a C opaque pointer?,了解为 FFI 构造不透明类型的正确方法。

总的来说,我会使用这样的东西:

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

【讨论】:

  • 感谢您的回答!你对Once 的使用让我想起了lazy_static crate。我已经用另一种可能的处理方式更新了我的问题,但我不知道它是否合理。
【解决方案2】:

我做了一些挖掘,看看其他 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(); }
    }
}

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-24
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-18
    相关资源
    最近更新 更多