Rust 包装C库初始化/销毁例程的推荐方法
我正在为一个C库编写一个包装器/FFI,它需要在主线程中进行一个全局初始化调用,也需要一个用于销毁的调用 以下是我目前的处理方式: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
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(); }
}
}