Pass pointer to itself so C++ can call

I've defined a way to pass my Rust object to C++ so it can call this Rust object back:

extern "C" {
     pub fn zlmedia_set_parent(zl_media: *mut ZLInstance, parent: Box<ZLMedia>);
}

pub fn new_boxed(url: &str) -> Box<ZLMedia> {
    let c_url = CString::new(url).expect("CString::new failed");
    let p = Box::new(ZLMedia {
        zl_media: unsafe { zlmedia_new(c_url.into_raw()) },
        url: url.to_string(),
    });
    unsafe{zlmedia_set_parent(p.as_ref().zl_media, p);}
    p
}

The problem is that I can't pass the Box to C++ because I move it here:

unsafe{zlmedia_set_parent(p.as_ref().zl_media, p);}

So, how can I pass a Box to itself to C++ and still return it in the new? I dont really need to move it, because it goes to a C++ function.

A Box must have exclusive ownership of the value inside, so it is unsound to both give a pointer to C and to keep using it in Rust. You will have to do something more like this:

extern "C" {
     pub fn zlmedia_set_parent(zl_media: *mut ZLInstance, parent: *mut ZLMedia);
}

// A custom Box type
struct ZLMediaBox {
    ptr: *mut ZLMedia,
}
impl Drop for ZLMediaBox {
    fn drop(&mut self) {
        // optionally tell C to stop using the ZLMedia ptr first
        unsafe { drop(Box::from_raw(self.ptr)); }
    }
}

pub fn new_boxed(url: &str) -> ZLMediaBox {
    let c_url = CString::new(url).expect("CString::new failed");
    let p = Box::new(ZLMedia {
        zl_media: unsafe { zlmedia_new(c_url.into_raw()) },
        url: url.to_string(),
    });
    let ptr = Box::into_raw(p);
    unsafe{zlmedia_set_parent(p.as_ref().zl_media, ptr);}
    ZLMediaBox {
        ptr,
    }
}
1 Like

the problem with this approach is that then ZLMedia has to be a C-friendly, which I was avoiding

What type does zlmedia_set_parent() accept, what are its semantics? Couldn't you just pass &*p, i.e. a non-owning temporary pointer, instead?

Also note that it looks like you are trying to create a self-referential struct, which is not currently possible in Rust. What are you trying to accomplish? I have googled zlmedia and the two function names found in your code, but I couldn't find anything. Is this a proprietary library? Could you provide more context, type signatures, expected semantics, etc.?

this is the C++ side of the code:

void zlmedia_set_parent(ZLMediaInstance *zLMediaInstance, ZLMedia *zl_media)
    {
        ((ZLMedia *)zLMediaInstance)->set_parent(zl_media);
    }

What I'm trying to achieve is a way for ZLMedia own an object that calls itself back. Except that this object is a C++ object. I want to be able for this ZLMedia's object call a ZLMedia method. Example:

let zl_media = ZLMedia::new(...);

zl_media internally will instantiate a C++ object that will be able to call zl_media.something()

I've researched many ways of doing this. One of them is as suggested by @alice but the problem is that then ZLMedia on the Rust side has to be C-friendly, because we pass a raw pointer to it to C++. It even works if ZLMedia is not C-friendly (FFI-safe) but the compiler gives a warning which means it'll give undefined behaviour in some cases.

I then found that with Box<ZLMedia> I don't have to make Rust's ZLMedia C-friendly, and a Box<ZLMedia> on C++ is just ZLMedia *zl_media, where ZLMedia is an opaque struct, which is perfect for me. So when I call C++ code from Rust using Box<ZLMedia> it arrives in C++ as ZLMedia*. Then C++ can use this to call back the Rust instance (its parent).

I'm really lost in doing C++ FFI with Rust. There are very few tutorials that teach how to call simple functions, but not complex that for example Rust object set C++ callbacks that calls a Rust instance back.

In this case, it really does look like you want the callback object to be owned, therefore passing the box by value (or by explicitly doing Box::into_raw()) is probably the right thing to do. If you simultaneously need to pass single ownership and retain the passed value, then either you want to clone it, or re-architect your code so that you can indeed relinquish ownership of the callback.

I don't get it. Isn't passing by value what I was trying to do? I get the problem that

unsafe{zlmedia_set_parent(p.as_ref().zl_media, p);}
    p

I cannot pass it by value and return, in the new_boxed function. And passing with Box::into_raw, would force zlmedia_set_parent to accept parent: *mut ZLMedia instead of parent: Box<ZLMedia>, which would force ZLMedia to be C-frindly, which I can't do

No, it doesn't. Passing a Box<T> or an "owning" *mut T through FFI does exactly the same thing. If your type is not #[repr(C)], then you can't dereference it from C code, regardless of whether you directly passed it as Box or via Box::into_raw(). If it is, then you can dereference it, again, regardless of the type.

Passing a Box versus a raw pointer only makes a difference if you want to retain a copy of the pointer. You can't do that with Box because Box enforces its own invariant for the sake of safety, that is, you can only have a single owner and a single mutable pointer to the allocated object. If you use Box::into_raw(), then you can violate this rule, but then you are on your own, since using two copies of the same pointer for mutating the data is still Undefined Behavior.

Actually, once the unfortunate pull request stuffing allocators into Box lands, Box will cease to be guaranteed to have the same layout as *mut T, and the only remaining reliable way to pass boxed values through FFI will be into_raw().

1 Like

I dont want to deference the pointer in the C side, I just wand C to store it and then call a function on Rust with the pointer as an argument + the other args, so this function can then call the args on the pointer, therefore calling an argument of the pointer.

By your first paragraph you're saying that this:

extern "C" {
     pub fn zlmedia_set_parent(zl_media: *mut ZLInstance, parent: Box<ZLMedia>);
}

//...
unsafe{zlmedia_set_parent(p.as_ref().zl_media, p.into_raw());}

is valid? I don't think so I guess you are suggesting me to do this:

extern "C" {
     pub fn zlmedia_set_parent(zl_media: *mut ZLInstance, parent: *mut ZLMedia);
}

//...
unsafe{zlmedia_set_parent(p.as_ref().zl_media, p.into_raw());}

right?

But this is where I get problems. Compiler complains on the line

extern "C" {
     pub fn zlmedia_set_parent(zl_media: *mut ZLInstance, parent: *mut ZLMedia);
}

saying in *mut ZLMedia that ZLMedia is not FFI-safe. That's why I insisted on using Box.

Yes, it is, or more precisely, it ought to be valid. (Except that you have a type error down in the call, because if the function is declared to take Box<T>, then you should pass the box directly instead of calling into_raw()). Box<T> does NOT change the FFI-safety of its contained value type. It neither removes nor adds FFI-safety.

Link to the relevant URLO thread, and to the docs that explicitly state:

So long as T: Sized , a Box<T> is guaranteed to be represented as a single pointer and is also ABI-compatible with C pointers (i.e. the C type T*)

And to the announcement of Rust 1.41 which also explicitly says:

Starting with Rust 1.41.0, we have declared that a Box<T> , where T: Sized is now ABI compatible with the C language's pointer ( T* ) types. So if you have an extern "C" Rust function, called from C, your Rust function can now use Box<T> , for some specific T , while using T* in C for the corresponding function.

Except that the other direction (Rust calling into C) comes with a warning, whereby a bug in LLVM may still cause UB in valid code, so they do indeed advise programmers to mirror the C types as precisely as possible (using raw pointers in this case) as a best practice. But unless you hit this LLVM bug, passing a Box<T> should work in theory, it should be equivalent with transferring ownership and passing a raw pointer, and once the LLVM bug is fixed, it will also be reliable in practice.


As to why you are getting the warning: it's an unavoidable false positive, no matter which approach you choose, the Box or the raw pointer. The compiler can't possibly know whether you will actually dereference the pointer on the C side, since it doesn't, it can't, analyze your C code (and for all intents and purposes, you might as well be linking to an opaque library without source).

So it's merely being conservative, emitting the warning anyway, because it's better to get a false positive warning (that you can suppress explicitly if you promise you won't dereference the pointer from C) than it is to get a false negative and silently cause UB if you simply forgot to add the #[repr(C)] on your struct that you do however intend to dereference from C.

The safest way to solve this issue is, however, to use an opaque pointer instead. Take void * on the C side, cast to *mut c_void on the Rust side, and this guarantees (quasi, modulo arcane C hacks) that you won't be able to dereference the pointer on the C side, and this makes the warning go away without needing to slap an #[allow(improper_ctypes)] on your struct declaration.

(Note that this still doesn't allow you to violate the "single ownership" and "no mutable aliasing" rules, so you will have to enforce those on your own, paying special attention to the C code and the interaction between the C and the Rust parts of the code.)

you mean:

extern "C" {
     pub fn zlmedia_set_parent(zl_media: *mut c_void, parent: *mut c_void);
}

pub fn new_boxed(url: &str) -> Box<ZLMedia> {
    let c_url = CString::new(url).expect("CString::new failed");
    let p = Box::new(ZLMedia {
        zl_media: unsafe { zlmedia_new(c_url.into_raw()) },
        url: url.to_string(),
    });
    //What here? simply p.zl_media and p would move the pointer and thus cannot return p
    unsafe{zlmedia_set_parent(p.as_ref().zl_media, p);}
    p
}

remember

pub fn zlmedia_new(uri: *const c_char) -> *mut ZLInstance;

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.