当通过Rust通过FFI调用一个shared_ptr <t>的C ++函数时,如何使用它呢?

时间:2018-11-14 11:25:50

标签: c++ rust shared-ptr ffi

C ++

shared_ptr<Foo> create_foo();

铁锈

extern "C" {
    pub fn create_foo() -> ???;
}

Bindgen将shared_ptr变成不透明的斑点。

我不能只使用原始指针,因为C ++代码并不知道我有对Foo的引用,并且可能会调用其解构函数。

3 个答案:

答案 0 :(得分:4)

std::shared_ptr是C ++类,并且是无法从库中直接导出的非平凡类型-您需要使用目标语言对其进行定义,以使其与C ++中的语言一致。要使用FFI,您需要为您的库函数提供一个简单的C ABI(C ++ ABI不稳定,并且可能在编译器版本之间进行更改(Rust的ABI可能会有所不同)),而且我怀疑与std::shared_ptr相关的所有函数是否都这样,因此还有一个障碍。

我建议改为从您的库中返回原始C指针,并在Rust中拥有它。

即使在C ++中,要加载C ++库,您也要提供C-ABI函数(通过extern C)来访问您类型的指针,然后根据需要在C ++中使用它。

所以,几点:

  1. 从函数返回原始C指针,而无需进行名称修饰,以便我们知道其名称并可以链接到它:

    extern "C" Foo* create_foo();
    
  2. 添加一个知道如何正确释放对象的删除器:

    extern "C" void delete_foo(Foo *);
    
  3. 让库用户(Rust)决定如何拥有它,例如,通过boxing的值并通过std::sync::Arc(如std::shared_ptr将其与原子引用计数器一起使用确实):

    extern "C" {
        fn create_foo() -> *mut Foo;
        fn delete_foo(p: *mut Foo);
    }
    
    struct MyFoo {
        raw: *mut Foo,
    }
    
    impl MyFoo {
        fn new() -> MyFoo {
            unsafe { MyFoo { raw: create_foo() } }
        }
    }
    
    impl Drop for MyFoo {
        fn drop(&mut self) {
            unsafe {
                delete_foo(self.raw);
            }
        }
    }
    
    fn main() {
        use std::sync::Arc;
    
        let value = Arc::new(MyFoo::new());
        let another_value = value.clone();
        println!("Shared counter: {}", Arc::strong_count(&value));
    }
    
  4. 让C ++一方忘记拥有此指针-如果在库外部使用它,并且提供了原始指针,则不能依赖它。

如果您无权访问库源代码,则无法执行任何操作:std::shared_ptr对象将永远不会释放指针,并且我们不能使其不删除指针。

答案 1 :(得分:1)

  

我不能只使用原始指针,因为C ++代码并不知道我有对Foo的引用,并且可能称它为解构函数。

是,不是。用你的实际例子。 C ++将把shared_ptr的所有权交给一个叫create_foo的人,因此C ++知道仍然有一些东西拥有指针。

您需要添加一个get函数,该函数将为您获取值而不丢失指针的所有权,如下所示:

extern "C" {
    std::shared_ptr<Foo> create_foo() {
        // do the thing
    }
    /* or maybe this
    std::shared_ptr<Foo> &&create_foo() {
        // do the thing
    }
    */

    Foo *get_foo(std::shared_ptr<Foo> &foo) {
        foo.get();
    }

    void destroy_foo(std::shared_ptr<Foo> foo) {
    }
    /* or maybe this
    void destroy_foo(std::shared_ptr<Foo> &&foo) {
    }
    */
}

另外shared_ptr<Foo>也不是有效的C语言,所以我不知道bindgen和C ++是否接受此命令(可能是警告),但是代码中已经存在该消息。

在Rust方面,您可以这样做:

// must be generated by bindgen and this might create a lot of problems
// this need to be the same struct as the shared_ptr on the C++ side.
// if even one octet is not correct you will run into bugs
// BE SURE that bindgen don't implement Copy for this
struct shared_ptr<T>; 

struct Foo(i32);

extern "C" {
    fn create_foo() -> shared_ptr<Foo>;

    fn get_foo(foo: &shared_ptr<Foo>) -> *mut Foo;

    fn destroy_foo(foo: shared_ptr<Foo>);
}

fn main() {
    unsafe {
        let my_shared_foo = create_foo();
        let foo = get_foo(&my_shared_foo);
        (*foo).0;
        destroy_foo(my_shared_foo);
    }
}

当然,这只是一个例子,在这方面没有什么是真正安全的。而且由于我无法测试,请告诉我是否写了不起作用的内容。 bindgen应该做好这项工作。

答案 2 :(得分:-1)

您可以返回指向动态分配的std::shared_ptr

的指针

在C ++方面:

shared_ptr<Foo> create_foo();
extern "C" void *rust_create_foo()
{
    shared_ptr<Foo> foo = create_foo();
    return static_cast<void*>(new shared_ptr<Foo>(foo));        
}
extern "C" void rust_delete_foo(void *ptr)
{
    shared_ptr<Foo> *foo = static_cast<shared_ptr<Foo>*>(ptr);
    delete foo;
}

在Rust方面:

extern "C" {
    pub fn rust_create_foo() -> *const c_void;
    pub fn rust_delete_foo(foo: *const c_void);
}

如果您无法调用rust_delete_foo,则动态分配的指针将泄漏,因此该对象将永远不会被释放。

然后,当您要使用该对象时,必须编写使用该void*的包装函数,进行强制转换并调用相应的函数。