Должен ли я использовать Pin, когда C++ вызывает метод Rust через указатель?

У меня есть код C++, который вызывает код Rust с данными. Он знает, на какой объект отправлять данные. Вот пример функции Rust, которую C++ вызывает обратно:

extern "C" fn on_open_vpn_receive(
    instance: Box<OpenVpn>,
    data: *mut c_uchar,
    size: *mut size_t,
) -> u8 

Он получает указатель как Box, поэтому я создал функцию openvpn_set_rust_parent, которая устанавливает, какой объект C++ должен вызывать обратно. Этот объект является указателем на самого себя. Я использую Pin, поэтому Box не перераспределяется куда-то еще, из-за чего С++ вызывает недопустимый адрес.

impl OpenVpn {
    pub fn new() -> Pin<Box<OpenVpn>> {
        let instance = unsafe { interface::openvpn_new(profile.as_ptr()) };
        let o = OpenVpn { instance: instance };
        let p = Box::pin(o);
        unsafe {
            interface::openvpn_set_rust_parent(o.instance, p.as_ptr());
        };
        p
    }
}

Подпись:

pub fn openvpn_set_rust_parent(instance: *mut OpenVpnInstance, parent: *mut OpenVpn)

Я не знаю, как преобразовать p в *mut OpenVpn для перехода на C++. Моя идея в порядке? Я думаю, что использование Pin здесь хорошо, и я думаю, что это хороший способ вызова объекта из C++.


person Guerlando OCs    schedule 11.11.2020    source источник
comment
Важно. По крайней мере, в настоящее время вам следует избегать использования типов Box‹T› для функций, которые определены в C, но вызываются из Rust. - из документов std::boxed. Так что не пишите эту подпись в первую очередь.   -  person Sebastian Redl    schedule 11.11.2020
comment
@SebastianRedl Хотя это правда (и интересно, спасибо за ссылку), если быть предельно педантичным, этот вопрос, похоже, касается функции, определенной в Rust, которая вызывается из C (++). Однако похоже, что та же логика может применяться и в другом направлении.   -  person trentcl    schedule 11.11.2020
comment
@trentcl А, правда. Я неправильно понял эту часть поста. И нет, логика конкретно не идет другим путем.   -  person Sebastian Redl    schedule 12.11.2020
comment
Он получает указатель как Box — это крайне подозрительно, так как это означает, что функция должна быть вызвана один раз и ровно один раз. Если это вызывается ноль раз, у вас есть утечка памяти. Если он вызывается дважды, вы будете использовать память после ее освобождения. Учитывая, что вы возвращаете p из функции, это означает, что как только сработает обратный вызов, любой код Rust, обращающийся к p, вызовет неопределенное поведение. То же самое, если p отбрасывается кодом Rust до того, как произойдет обратный вызов.   -  person Shepmaster    schedule 14.11.2020
comment
Код @Shepmaster C++ создается и уничтожается OpenVpn, поэтому код C выглядит так, как будто он принадлежит OpenVpn. openvpn_set_rust_parent просто устанавливает обратный вызов внутри класса C++, чтобы он знал, какой объект (его родитель) вызывать. Вызов дважды или более просто устанавливает обратный вызов снова. Также нет никакого способа ничего не вызывать, потому что он вызывает своего родителя, который всегда живет больше, чем он. Итак, учитывая эти факты, я думаю, имеет смысл вызывать openvpn_set_rust_parent и при этом возвращать p. Что вы думаете?   -  person Guerlando OCs    schedule 14.11.2020


Ответы (1)


Это не имеет значения. Pin не является глубоко волшебным типом, который заставляет ваше значение никогда не двигаться. На самом деле, все сводится к строго сформулированной документации и некоторым направляющим рельсам, которые не позволяют вам делать плохие вещи в безопасном коде Rust. Pin можно обойти с помощью небезопасного кода, который включает в себя любой код FFI.

Наличие Pin внутри вашего кода Rust может помочь вам сохранить точность и правильность кода Rust, но в нем нет ничего полезного для целей вызова Rust из других языков.

Pin определяется как repr(transparent), что означает, что вы можете использовать его в своей подписи FFI, если внутренний тип безопасен для использования в FFI:

#[stable(feature = "pin", since = "1.33.0")]
#[lang = "pin"]
#[fundamental]
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct Pin<P> {
    pointer: P,
}

Я использую Pin, поэтому Box не перераспределяется куда-то еще, из-за чего С++ вызывает недопустимый адрес.

Pin этого не делает, Box делает это. Когда вы что-то упаковываете, вы перемещаете значение в кучу. Сам Box — это просто указатель. Адрес указателя будет перемещаться, а адрес данных в куче — нет.

Обратите внимание, что второй напечатанный адрес (0x55..30, в куче) тот же самый, даже несмотря на то, что сам Box переместился:

fn main() {
    let a = 42;

    let b = Box::new(a);
    println!("{:p}", &b);  // 0x7ffe3598ea80
    println!("{:p}", &*b); // 0x556d21528b30

    let c = b;
    println!("{:p}", &c);  // 0x7ffe3598ea88
    println!("{:p}", &*c); // 0x556d21528b30
}

Смотрите также:

person Shepmaster    schedule 13.11.2020
comment
Мне всегда было немного неясно, какие именно аспекты #[stable] элемента были стабилизированы; Я так понимаю, что repr гарантированно стабилен? - person eggyal; 14.11.2020
comment
Итак, поскольку Box<Anything> безопасно использовать в FFI, означает ли это, что Pin‹Box‹Anything›› безопасен? Я должен просто позвонить unsafe { interface::openvpn_set_rust_parent(o.instance, p); }; p? Ps: я не могу этого сделать, потому что я перемещаю p на openvpn_set_rust_parent - person Guerlando OCs; 14.11.2020
comment
@eggyal Я не знаю, были ли где-нибудь явно записаны стабильность и такие вещи, как repr, но, как и многие другие вещи, это улица с односторонним движением. Сейчас я забыл точный тип, но был тип stdlib, у которого не было атрибута repr (и, следовательно, был repr(Rust)), который изменился на repr(transparent). Это было нормально, потому что он перешел от неопределенного макета к определенному макету, но это не могло измениться назад без поломки. Мне было бы удобно предположить, что Pin навсегда останется repr(transparent), если только не будет обнаружена какая-то ошибка, связанная с небезопасностью памяти. - person Shepmaster; 14.11.2020
comment
@GuerlandoOCs, как указано в комментариях к вашему вопросу, хотя Box<T> допустимо для добавления подписи FFI, это не означает, что каждое его использование является правильным. Например, вы не можете выделить в C и передать указатель на Rust как Box<T>, потому что аллокаторы не совпадут. Pin<Box<T>> безопасно использовать в FFI в тех же местах, что и Box<T>. - person Shepmaster; 14.11.2020