Как вы работаете с функцией C ++, которая возвращает shared_ptr <T>при вызове ее из Rust через FFI? - PullRequest
0 голосов
/ 14 ноября 2018

C ++

shared_ptr<Foo> create_foo();

Ржавчина

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

Биндген превращает shared_ptr в непрозрачный шарик.

Я не могу просто взять необработанный указатель, потому что тогда код C ++ не знает, что у меня есть ссылка на Foo и может вызвать его деконструктор.

Ответы [ 3 ]

0 голосов
/ 14 ноября 2018

Я не могу просто взять необработанный указатель, потому что тогда код 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 должен сделать работу.

0 голосов
/ 14 ноября 2018

Вы можете вернуть указатель на динамически распределяемый 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;
}

А на стороне Ржавчины:

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

Если вам не удастся вызвать rust_delete_foo, динамически выделенный указатель будет просачиваться, и поэтому объект никогда не будет освобожден.

Затем, когда вы захотите использовать объект, вам нужно будет написать функции-обертки, которые принимают void*, выполнить приведение и вызвать соответствующую функцию.

0 голосов
/ 14 ноября 2018

std::shared_ptr - это класс C ++ и нетривиальный тип, который не может быть экспортирован как есть из библиотеки - его определение на вашем целевом языке должно соответствовать определению в C ++. Чтобы использовать FFI, вы должны предоставить своим библиотечным функциям простой C ABI (C ++ ABI нестабилен и может меняться между версиями компилятора (как, например, ABI Rust)), и я сомневаюсь, что все функции, связанные с 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 никогда не освободит указатель, и мы не можем заставить его не удалять указатель.

...