2016-04-01 12 views
5

Sto scrivendo un wrapper/FFI per una libreria C che richiede una chiamata di inizializzazione globale nel thread principale e uno per la distruzione.Modo consigliato di avvolgere routine di inizializzazione/distruzione C lib.

Ecco come Attualmente sto maneggiarlo:

struct App; 

impl App { 
    fn init() -> Self { 
     unsafe { ffi::InitializeMyCLib(); } 
     App 
    } 
} 

impl Drop for App { 
    fn drop(&mut self) { 
     unsafe { ffi::DestroyMyCLib(); } 
    } 
} 

che può essere usato come:

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

Questo funziona bene, ma ci si sente come un hack, legando queste chiamate alla vita di una struttura non necessaria. Avere il distruttore in un blocco finally (Java) o at_exit (Ruby) sembra in teoria più appropriato.

Esiste un modo più aggraziato per farlo in Rust?

EDIT

Sarebbe possibile/sicuro da usare questa configurazione in questo modo (usando il lazy_static cassa), invece del mio secondo blocco di cui sopra:

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

Sarebbe questo riferimento essere garantito a essere inizializzato prima di qualsiasi altro codice e distrutto all'uscita? È una cattiva pratica usare lazy_static in una libreria?

Ciò renderebbe anche più semplice facilitare l'accesso alla FFI attraverso questa struttura, poiché non dovrei preoccuparmi di passare il riferimento alla struttura istanziata (chiamata nel mio esempio originale _init_).

Ciò renderebbe anche più sicuro in alcuni modi, poiché potrei rendere privato il costruttore di struct default App.

+1

Side-Note: A seconda della sicurezza del wrapper, è necessario rendere la funzione 'init' non sicura (poiché nessuno può chiamarla più volte) e implementare tutte le funzioni che richiedono l'inizializzazione del clib come metodi sull'oggetto 'App'. In questo modo nessuno può chiamare le funzioni senza averle inizializzate. È anche possibile implementarlo come una sorta di singolo conteggio di riferimento per renderlo sicuro da inizializzare. Questa è una grande vittoria su Java e Ruby, perché lì puoi chiamare le funzioni senza aver inizializzato la lib –

+0

* nel thread principale * - sei ** sicuro ** che deve essere il thread principale? Potrebbe essere un thread qualsiasi, purché sia ​​inizializzato prima dell'uso? – Shepmaster

+0

@ker, grazie per il commento. Non ci avevo pensato in quel modo (avendo tutti accesso attraverso la struttura "App", ma questo ha senso. Dovrò pensare se questo funzionerebbe per il mio caso. –

risposta

2

Non conosco alcun modo per far rispettare il fatto che un metodo venga chiamato nel thread principale oltre la documentazione fortemente scritta. Così, ignorando tale obbligo ... :-)

In generale, userei std::sync::Once, che sembra fondamentalmente progettato per questo caso:

Una primitiva di sincronizzazione che può essere utilizzato per eseguire una sola volta inizializzazione globale . Utile per l'inizializzazione una tantum per FFI o relativa funzionalità . Questo tipo può essere creato solo con il valore .

Nota che non è prevista alcuna pulizia; molte volte devi solo perdere tutto ciò che la biblioteca ha fatto. Di solito, se una libreria ha un percorso di pulizia dedicato, è stata anche strutturata per memorizzare tutti i dati inizializzati in un tipo che viene poi passato in funzioni successive come una sorta di contesto o ambiente. Ciò si assocerebbe piacevolmente ai tipi di ruggine.

Attenzione

Il codice attuale è non come protezione come si spera che è.Dal momento che il App è una struct vuoto, un utente finale può costruirlo senza chiamare il metodo:

let _init_ = App; 

non riesco a trovare la domanda appropriata con i dettagli al momento, ma mi piacerebbe usare per PhantomData rendilo più protetto dall'esterno del tuo modulo.

Nel complesso, mi piacerebbe usare qualcosa di simile:

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

grazie per la risposta! L'uso di 'Once' mi ha ricordato la gabbia' lazy_static'.Ho aggiornato la mia domanda con un altro modo possibile di gestirlo, ma non lo faccio sapere se è sano. –

1

Ho fatto qualche scavare intorno per vedere come altre librerie FFI gestire questa situazione. Ecco quello che sono attualmente in uso (simile a @ risposta di Shepmaster e liberamente ispirato alla routine di inizializzazione di 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(); } 
    } 
} 

Ho poi chiamare questa funzione all'interno dei costruttori pubblici per i miei le strutture pubbliche.

Problemi correlati