Recommended Wrap Init / Destroy Procedure C lib

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

This is how I process it now:

struct App; impl App { fn init() -> Self { unsafe { ffi::InitializeMyCLib(); } App } } impl Drop for App { fn drop(&mut self) { unsafe { ffi::DestroyMyCLib(); } } } 

which can be used as:

 fn main() { let _init_ = App::init(); // ... } 

This works fine, but it seems like a hack, tying these challenges to a life of an unnecessary structure. Having a destructor in a finally (Java) or at_exit (Ruby) block seems to be theoretically more appropriate.

Is there an even more elegant way to do this in Rust?

EDIT

Is it possible / safe to use this setting like this (using the lazy_static box) instead of the second block above:

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

Is it possible to guarantee that this link will be initialized before any other code and destroyed upon exit? Is it wrong to use the lazy_static library in the library?

It would also facilitate facilitating access to FFI through this single structure, since I would not have to go past the reference to the instance structure (called _init_ in my original example).

This will also make it safer since I could make the App constru default default constructor.

+5
source share
2 answers

I do not know how to force the use of a method that is called in the main thread for heavily documented documentation. So, ignoring this requirement ... :-)

Typically, I would use std::sync::Once , which seems to be mainly intended for this case:

A synchronization primitive that can be used to trigger one-time global initialization. Useful for one-time initialization for FFI or related functionality. This type can only be built using the ONCE_INIT value.

Please note that there are no conditions for any cleaning; many times you just need to leak everything that the library has done. Usually, if the library has a dedicated cleaning path, it was also structured to store all initialized data in a type, which is then passed to the subsequent functions as some kind of context or environment. This will conveniently display the types of Rust.

Attention

Your current code is not as secure as you hope. Since your App is an empty structure, the end user can build it without calling your method:

 let _init_ = App; 

I cannot find the corresponding question with details at the moment, but I would use PhantomData to make it more secure from outside your module.

In general, I would use something like this:

 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); } 
+2
source

I did a bit of work to see how other FFI libraries handle this situation. Here is what I am currently using (similar to @Shepmaster's answer and weakly based on curl-rust initialization):

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

Then I call this function inside the common constructors for my open structures.

+1
source

Source: https://habr.com/ru/post/1246245/


All Articles