I have C++ code that calls Rust code with data. It knows which object to send the data to. Here's an example of the Rust function that C++ calls back:
extern "C" fn on_open_vpn_receive(
instance: Box<OpenVpn>,
data: *mut c_uchar,
size: *mut size_t,
) -> u8
It receives the pointer as a Box
, so I created a function openvpn_set_rust_parent
that sets which object the C++ must call back. This object is a pointer to itself. I'm using Pin
so the Box
is not reallocated to somewhere else, making C++ call an invalid address.
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
}
}
Signature:
pub fn openvpn_set_rust_parent(instance: *mut OpenVpnInstance, parent: *mut OpenVpn)
I don't know how to transform p
into *mut OpenVpn
to pass to C++. Is my idea ok? I think the usage of Pin
is good here, and I think this is a good way of calling the object from C++.
It doesn't matter. Pin
isn't a deeply magical type that forces your value to never move. Really, it boils down to strongly-worded documentation and some guide rails that prevents you from doing bad things within safe Rust code. Pin
can be circumvented by unsafe code, which includes any FFI code.
Having the Pin
inside your Rust code might help you keep the Rust code accurate and valid, but it has nothing useful to add for the purposes of calling Rust from other languages.
Pin
is defined as repr(transparent)
, which means that you can use it in your FFI signature as long as the inner type is safe to use in FFI:
#[stable(feature = "pin", since = "1.33.0")]
#[lang = "pin"]
#[fundamental]
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct Pin<P> {
pointer: P,
}
I'm using
Pin
so theBox
is not reallocated to somewhere else, making C++ call an invalid address.
Pin
doesn't do this, Box
does this. When you box something, you move the value to the heap. The Box
itself is just a pointer. The address of the pointer will move around, but the address of the data in the heap will not.
Note that the second address (0x55..30
, on the heap) printed is the same, even though the Box
itself has moved:
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
}
See also: