我正在为一个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默认构造函数设为私有。
答案 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(); }
}
}
然后我在公共构造函数中为我的公共结构调用此函数。