2016-07-10 10 views
1

Ich bin ein C-API-Wrapping, die den Anrufer ermöglicht, einen beliebigen Zeiger über Funktionsaufrufe zu setzen/erhalten. Auf diese Weise ermöglicht die C-API einem Aufrufer, beliebige Daten mit einem der C-API-Objekte zu verknüpfen. Diese Daten werden in keinem Callback verwendet, es ist nur ein Zeiger, auf den ein Benutzer zugreifen und später zugreifen kann.Lassen Sie einen Rost Void Zeiger in einem FFI gespeichert

Meine Wrapper-Struktur implementiert die Drop-Eigenschaft für das C-Objekt, das diesen Zeiger enthält. Was ich gerne tun könnte, bin mir aber nicht sicher, ob es möglich ist, dass die Daten korrekt fallen gelassen werden, wenn der Zeiger nicht null ist, wenn die Wrapper-Struktur abfällt. Ich bin mir nicht sicher, wie ich den korrekten Typ zwar von einem rohen c_void Zeiger wiederherstellen würde.

Zwei Alternativen Ich denke an sind

  1. in der Wrapper das Verhalten dieser beiden Anrufe implementieren. Rufen Sie die C-API nicht auf.
  2. Versuchen Sie nicht, irgendeine sichere Schnittstelle zu diesen Funktionen anzubieten. Dokumentieren Sie, dass der Zeiger vom Aufrufer des Wrappers verwaltet werden muss.

Was möchte ich tun? Wenn nicht, gibt es eine allgemein akzeptierte Praxis für diese Art von Situationen?

Antwort

2

Ein naiver + vollautomatische Ansatz aus den folgenden Gründen nicht möglich ist:

  • befreien Speicher nicht Drop/deconstructors rufen/...: kann der C-API von Sprachen verwendet werden, wo Objekte haben, die richtig dekonstruiert werden sollten, z C++ oder Rust selbst. Wenn Sie also nur einen Speicherzeiger speichern, wissen Sie nicht, dass Sie die richtige Funktion aufrufen sollen (Sie wissen nicht, welche Funktion und nicht wie die Aufrufkonventionen aussehen).

  • Welche Speicherzuweisung ?: Speicherzuweisung und Freigabe ist keine triviale Sache. Ihr Programm muss Speicher vom Betriebssystem anfordern und diese Ressourcen dann intelligent verwalten, um effizient und korrekt zu sein. Dies wird normalerweise von einer Bibliothek durchgeführt. Im Falle von Rust wird jemalloc verwendet (aber can be changed). Selbst wenn Sie den API-Aufrufer bitten, nur Plain Old Data zu übergeben (was leichter zu vernichten sein sollte), wissen Sie immer noch nicht, welche Bibliotheksfunktion aufgerufen werden soll, um Speicher freizugeben. Nur mit libc::free wird nicht funktionieren (es kann aber schrecklich scheitern).

Lösungen:

  • dealloc Rückruf: Sie die API Benutzer fragen kann einen zusätzlichen Zeiger auf Set eine void destruct(void* ptr) Funktion, lassen Sie sagen. Wenn dieser nicht NULL ist, rufen Sie diese Funktion während des Ablegens auf. Sie könnten auch int als Rückgabetyp verwenden, um zu signalisieren, wenn die Zerstörung fehlgeschlagen ist. In diesem Fall könnten Sie zum Beispiel panic!.

  • globaler Rückruf: Nehmen wir an, Sie haben Ihren Benutzer aufgefordert, nur POD (Plain Old Data) zu übergeben. Um zu wissen, welche free Funktion des Speicherzuweisers aufrufen soll, können Sie den Benutzer auffordern, einen globalen Zeiger void (*free)(void* ptr) zu registrieren, der während des Ablegens aufgerufen wird. Du könntest das auch optional machen.

+0

ich tatsächlich die dealloc Rückruf Lösung etwa eine Stunde gedacht hatte, bevor ich die Frage gestellt, und irgendwie vergessen darüber. Ich werde damit herumspielen und sehen, wie weit ich komme. –

1

Obwohl ich war in der Lage, dem Rat in diesem Thread zu folgen, war ich mit meinen Ergebnissen nicht ganz zufrieden, so fragte ich die Frage an den Rust-Foren und fand die answer ich wirklich suchte. (play)

use std::any::Any; 

static mut foreign_ptr: *mut() = 0 as *mut(); 

unsafe fn api_set_fp(ptr: *mut()) { 
    foreign_ptr = ptr; 
} 

unsafe fn api_get_fp() -> *mut() { 
    foreign_ptr 
} 

struct ApiWrapper {} 

impl ApiWrapper { 
    fn set_foreign<T: Any>(&mut self, value: Box<T>) { 
     self.free_foreign(); 
     unsafe { 
      let raw = Box::into_raw(Box::new(value as Box<Any>)); 
      api_set_fp(raw as *mut()); 
     } 
    } 

    fn get_foreign_ref<T: Any>(&self) -> Option<&T> { 
     unsafe { 
      let raw = api_get_fp() as *const Box<Any>; 
      if !raw.is_null() { 
       let b: &Box<Any> = &*raw; 
       b.downcast_ref() 
      } else { 
       None 
      } 
     } 
    } 

    fn get_foreign_mut<T: Any>(&mut self) -> Option<&mut T> { 
     unsafe { 
      let raw = api_get_fp() as *mut Box<Any>; 
      if !raw.is_null() { 
       let b: &mut Box<Any> = &mut *raw; 
       b.downcast_mut() 
      } else { 
       None 
      } 
     } 
    } 

    fn free_foreign(&mut self) { 
     unsafe { 
      let raw = api_get_fp() as *mut Box<Any>; 
      if !raw.is_null() { 
       Box::from_raw(raw); 
      } 
     } 
    } 
} 

impl Drop for ApiWrapper { 
    fn drop(&mut self) { 
     self.free_foreign(); 
    } 
} 

struct MyData { 
    i: i32, 
} 

impl Drop for MyData { 
    fn drop(&mut self) { 
     println!("Dropping MyData with value {}", self.i); 
    } 
} 

fn main() { 
    let p1 = Box::new(MyData {i: 1}); 
    let mut api = ApiWrapper{}; 
    api.set_foreign(p1); 
    { 
     let p2 = api.get_foreign_ref::<MyData>().unwrap(); 
     println!("i is {}", p2.i); 
    } 
    api.set_foreign(Box::new("Hello!")); 
    { 
     let p3 = api.get_foreign_ref::<&'static str>().unwrap(); 
     println!("payload is {}", p3); 
    } 
}