2016-04-01 3 views
5

Ich schreibe einen Wrapper/FFI für eine C-Bibliothek, die einen globalen Initialisierungsaufruf im Hauptthread sowie einen für die Zerstörung erfordert.Empfohlene Methode zum Umschließen von C lib-Initialisierungs-/Zerstörungsroutine

Hier ist, wie ich bin derzeit Umgang es:

struct App; 

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

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

, die wie verwendet werden kann:

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

Dies funktioniert gut, aber es fühlt sich an wie ein Hack, um diese Anrufe auf die Lebenszeit binden einer unnötigen Struktur. Die Verwendung des Destruktors in einem finally (Java) oder at_exit (Ruby) Block scheint theoretisch geeigneter zu sein.

Gibt es in Rust etwas eleganteres?

EDIT

Wäre es möglich/sicher sein, diese Einrichtung zu verwenden, wie so (mit der lazy_static Kiste), statt meinem zweiten Block oben:

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

Würde dieses Verweis auf garantiert werden vor jedem anderen Code initialisiert und beim Beenden zerstört werden? Ist es eine schlechte Übung, in einer Bibliothek zu verwenden?

Dies würde es auch erleichtern, den Zugriff auf das FFI durch diese eine Struktur zu erleichtern, da ich nicht die Referenz auf die instantiierte Struktur weitergeben müsste (in meinem ursprünglichen Beispiel _init_ genannt).

Dies würde es auch in gewisser Weise sicherer, da ich die App struct Standardkonstruktors privat machen könnte.

+1

Side-Note: Abhängig davon, wie sicher Ihr Wrapper sein soll, sollten Sie die 'init'-Funktion unsicher machen (weil niemand sie mehrfach aufrufen darf), und alle Funktionen implementieren, die den clib initialisieren müssen auf dem 'App' Objekt. Auf diese Weise kann niemand die Funktionen aufrufen, ohne sie initialisiert zu haben. Sie können es auch als eine Art Referenzzähler implementieren, um die Initialisierung zu erleichtern. Das ist ein großer Gewinn gegenüber Java und Ruby, weil man dort die Funktionen aufrufen kann, ohne die Lib –

+0

* im Hauptthread zu initialisieren * - bist du sicher, dass es der Hauptthread sein muss? Kann es ein beliebiger Thread sein, solange er vor der Verwendung initialisiert wird? – Shepmaster

+0

@ker, danke für den Kommentar. Ich hatte nicht so darüber nachgedacht (Zugang über die 'App'-Struktur zu haben, aber das macht Sinn. Ich muss darüber nachdenken, ob das für meinen Fall funktionieren würde.) –

Antwort

2

Ich weiß von keiner Möglichkeit zu erzwingen, dass eine Methode im Hauptthread jenseits der stark formulierten Dokumentation aufgerufen wird. Also, diese Anforderung zu ignorieren ... :-)

Im Allgemeinen würde ich verwenden std::sync::Once, die im Grunde für diesen Fall ausgelegt scheinen:

Ein Synchronisations primitiver, die verwendet werden können, einen einmaligen laufen global Initialisierung. Nützlich für die einmalige Initialisierung für FFI oder verwandte Funktionalität. Dieser Typ kann nur mit dem Wert ONCE_INIT konstruiert werden.

Beachten Sie, dass keine Bereinigung vorgesehen ist; oft haben Sie nur noch auslaufen, was die Bibliothek getan hat. Normalerweise, wenn eine Bibliothek einen eigenen Bereinigungspfad hat, hat es auch strukturiert alle zu speichern, die Daten in einer Art initialisiert, die dann in nachfolgende Funktionen als eine Art Kontext oder Umwelt geführt wird. Dies wäre schön zu Rust Typen zuordnen.

Warnung

Ihre aktuellen Code ist nicht als Schutz, wie Sie es wünschen.Da Ihr App eine leere Struktur ist, kann ein Endbenutzer es konstruieren ohne Ihre Methode aufrufen:

let _init_ = App; 

Ich kann nicht die entsprechende Frage mit Details zur Zeit finden, aber ich PhantomData verwenden würde Machen Sie es besser geschützt von außerhalb Ihres Moduls.

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

danke für die Antwort! Ihre Verwendung von' Once' erinnerte mich an die 'lazy_static' Kiste. Ich habe meine Frage mit einer anderen möglichen Art und Weise aktualisiert, dies zu handhaben, aber ich tue es nicht weiß, ob es gesund ist. –

1

Ich habe einige graben, um zu sehen, wie andere FFI Libs behandeln diese Situation:

Insgesamt ich so etwas wie diese verwenden würde. Hier ist, was ich bin derzeit mit (ähnlich @ Shepmaster Antwort und basiert lose auf der Initialisierungsroutine 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(); } 
    } 
} 

ich diese Funktion rufen Sie dann in den öffentlichen Konstruktoren für meine öffentlichen Strukturen.

Verwandte Themen