如何在任意生命周期内将Rust对象借给C代码?

时间:2015-02-02 12:54:19

标签: c rust ffi

我在Rust中编写了一个具有C接口的库。 C方必须能够创建和销毁Rust对象(C方拥有它们并控制它们的生命周期)。

我设法"泄漏" C的对象,但我不确定如何正确释放它:

pub extern "C" fn create() -> *mut Foo {
   let obj = Foo; // oops, a bug
   let ptr = std::mem::transmute(&mut obj); // bad 
   std::mem::forget(obj); // not needed
   return ptr;
}

pub extern "C" fn destroy(handle: *mut Foo) {
   // get Foo back and Drop it??? 
}

我不确定如何将指针转回Rust会调用Drop的对象。简单地解除引用*handle不会编译。

2 个答案:

答案 0 :(得分:6)

实际上,你没有设法将对象泄漏给C;你已设法泄漏对(不久)不存在的堆栈帧的引用。 :d

这是一个应该正常工作的完整示例。我试图用它来评论它,以解释我在做什么以及为什么。

pub struct Dramatic(String);

// Implement a destructor just so we can see when the object is destroyed.
impl Drop for Dramatic {
    fn drop(&mut self) {
        println!("And lo, I, {}, meet a most terrible fate!", self.0);
    }
}

pub extern "C" fn create() -> *mut Dramatic {
    // We **must** heap-allocate the object!  Returning a reference to a local
    // will **almost certainly** break your program!
    let mut obj = Box::new(Dramatic("Roger".to_string()));

    // * derefs the Box into a Dramatic, the &mut re-borrows it into a regular
    // reference.  The constraint ensures we coerce the &mut Dramatic into
    // a *mut Dramatic, which "hides" the reference from the borrow checker.
    let ptr: *mut _ = &mut *obj;

    // Forget discards its argument (passed by-move), without trigger its
    // destructor, if it has one.
    ::std::mem::forget(obj);

    ptr
}

pub extern "C" fn destroy(ptr: &mut *mut Dramatic) {
    // First, we **must** check to see if the pointer is null.
    if ptr.is_null() {
        // Do nothing.
        return;
    }

    // Now, we know the pointer is non-null, we can continue.
    let obj: Box<Dramatic> = unsafe { ::std::mem::transmute(*ptr) };

    // We don't *have* to do anything else; once obj goes out of scope, it will
    // be dropped.  I'm going to drop it explicitly, however, for clarity.
    ::std::mem::drop(obj);

    // I am, however, going to null out the `ptr` we were passed just so the
    // calling code is less likely to accidentally re-use the pointer.
    *ptr = ::std::ptr::null_mut();
}

fn main() {
    let mut ptr = create();
    println!("ptr = {:?}", ptr);
    destroy(&mut ptr);
    println!("ptr = {:?}", ptr);
}

答案 1 :(得分:5)

将Rust对象发送到C:

#[no_mangle]
pub extern "C" fn create_foo() -> *mut Foo {
    Box::into_raw(Box::new(Foo))
}

从C:借用(而不是免费)

#[no_mangle]
pub unsafe extern "C" fn do(foo: *mut Foo) -> *mut Foo {
    let foo = foo.as_ref().unwrap(); // That's ptr::as_ref
}

接管/销毁先前赋予C的<:p>的Rust对象

#[no_mangle]
pub unsafe extern "C" fn free_foo(foo: *mut Foo) {
    assert!(!foo.is_null());
    Box::from_raw(foo); // Rust auto-drops it
}