代码之家  ›  专栏  ›  技术社区  ›  Kornel

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

  •  12
  • Kornel  · 技术社区  · 10 年前

    我正在用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 回复  |  直到 7 年前
        1
  •  14
  •   Kornel    3 年前

    要将Rust对象发送到C:

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

    或利用 Box 只要ABI相同,Rust函数定义不必与C头完全匹配:

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

    (返回 Option<Box<Foo>> 也很好。 Result 不是。)

    向C借款(而非免费):

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

    或利用参考文献和 Option FFI安全:

    #[no_mangle]
    pub extern "C" fn peek_at(foo: Option<&mut Foo>) {
        let foo = foo.unwrap();
    }
    

    要接收/销毁先前给C的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
    }
    

    或者利用以下事实 Option<Box> FFI安全,内存由Rust管理:

    #[no_mangle]
    pub unsafe extern "C" fn free_foo(foo: Option<Box<Foo>>) {
       // dropped implicitly
    }
    
        2
  •  10
  •   trent oli_obk    5 年前

    事实上,你 没有 设法将一个对象泄漏给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()));
    
        // into_raw turns the Box into a *mut Dramatic, which the borrow checker
        // ignores, without calling its destructor.
        Box::into_raw(obj)
    }
    
    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. from_raw is the
        // inverse of into_raw: it turns the *mut Dramatic back into a
        // Box<Dramatic>. You must only call from_raw once per pointer.
        let obj: Box<Dramatic> = unsafe { Box::from_raw(*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.
        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);
    }