Add gobj_rewrap() API to pass glib-rs objects back to C++
When we started using cxxrs, most of the glib-rs objects like `OstreeRepo`/`OstreeSysroot` were owned by C++ and passed down into Rust. That motivated the addition of the special bridging infrastructure to re-create a glib-rs wrapper type from what cxxrs wants (a `Pin<&mut T>`). But now that we're adding more in Rust, we have the need to pass these objects back into C++. In fact this will hopefully soon because the default case as more of the binary entrypoint becomes Rust. Add another trait with a method `gobj_rewrap()` that converts in the other direction. This implementation took me a number of tries before I finally settled on simply using `mem::transmute()`. There are a *lot* of caveats listed on the docs for that function, but I think it really is what we want here. See the link for pending work on a Rust RFC to enable safe transmutes for some cases, and I believe that would cover this use case: https://internals.rust-lang.org/t/pre-rfc-v2-safe-transmute/11431 I've verified this works in a separate patch, but this commit also adds a simple test case - this goes all the way from: Rust glib-rs `ostree::Repo` (holding strong ref) -> Rust `Pin<&mut ostree_sys::OstreeRepo>` -> (internal cxx-rs C bridge) -> C++ `OstreeRepo&` reference -> C `OstreeRepo*` pointer Which is quite the dance if you think about it!
This commit is contained in:
parent
8d9e113e8f
commit
8a62ad9f3d
@ -9,7 +9,9 @@
|
||||
//! In the future though hopefully cxx.rs improves this situation.
|
||||
|
||||
use cxx::{type_id, ExternType};
|
||||
use glib::translate::ToGlibPtr;
|
||||
use paste::paste;
|
||||
use std::pin::Pin;
|
||||
|
||||
/// Map an empty string to a `None`.
|
||||
pub(crate) fn opt_string(input: &str) -> Option<&str> {
|
||||
@ -22,19 +24,52 @@ pub(crate) fn opt_string(input: &str) -> Option<&str> {
|
||||
pub trait FFIGObjectWrapper {
|
||||
type Wrapper;
|
||||
|
||||
/// Use this function in Rust code that accepts glib-rs
|
||||
/// objects passed via cxx-rs to synthesize the expected glib-rs
|
||||
/// wrapper type.
|
||||
fn gobj_wrap(&mut self) -> Self::Wrapper;
|
||||
}
|
||||
|
||||
pub trait FFIGObjectReWrap<'a> {
|
||||
type ReWrapped;
|
||||
|
||||
/// Convert a glib-rs wrapper object into a Pin pointer
|
||||
/// to our FFI newtype. This is necessary to call
|
||||
/// cxx-rs wrapped functions from Rust.
|
||||
fn gobj_rewrap(&'a self) -> Pin<&'a mut Self::ReWrapped>;
|
||||
}
|
||||
|
||||
/// Implement FFIGObjectWrapper given a pair of wrapper type
|
||||
/// and sys type.
|
||||
macro_rules! impl_wrap {
|
||||
($w:ident, $bound:path) => {
|
||||
($w:ident, $bound:path, $sys:path) => {
|
||||
impl FFIGObjectWrapper for $w {
|
||||
type Wrapper = $bound;
|
||||
fn gobj_wrap(&mut self) -> Self::Wrapper {
|
||||
unsafe { glib::translate::from_glib_none(&mut self.0 as *mut _) }
|
||||
}
|
||||
}
|
||||
impl<'a> FFIGObjectReWrap<'a> for $bound {
|
||||
type ReWrapped = $w;
|
||||
fn gobj_rewrap(&'a self) -> Pin<&'a mut Self::ReWrapped> {
|
||||
// Access the underlying raw pointer behind the glib-rs
|
||||
// newtype wrapper, e.g. `ostree_sys::OstreeRepo`.
|
||||
let p: *mut $sys = self.to_glib_none().0;
|
||||
// Safety: Pin<T> is a #[repr(transparent)] newtype wrapper
|
||||
// around our #[repr(transparent)] FFI newtype wrapper which
|
||||
// for the glib-rs newtype wrapper, which finally holds the real
|
||||
// raw pointer. Phew!
|
||||
// In other words: Pin(FFINewType(GlibRs(RawPointer)))
|
||||
// Here we're just powering through those layers of wrappers to
|
||||
// convert the raw pointer. See also https://internals.rust-lang.org/t/pre-rfc-v2-safe-transmute/11431
|
||||
//
|
||||
// However, since what we're handing out is a raw pointer,
|
||||
// we ensure that the lifetime of our return value is tied to
|
||||
// that of the glib-rs wrapper (which holds a GObject strong reference),
|
||||
// which ensures the value isn't freed.
|
||||
unsafe { std::mem::transmute(p) }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -49,7 +84,7 @@ macro_rules! bind_ostree_obj {
|
||||
type Id = type_id!(rpmostreecxx::[<Ostree $w>]);
|
||||
type Kind = cxx::kind::Trivial;
|
||||
}
|
||||
impl_wrap!([<FFIOstree $w>], ostree::$w);
|
||||
impl_wrap!([<FFIOstree $w>], ostree::$w, ostree_sys::[<Ostree $w>]);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -69,7 +104,7 @@ unsafe impl ExternType for FFIGCancellable {
|
||||
type Id = type_id!(rpmostreecxx::GCancellable);
|
||||
type Kind = cxx::kind::Trivial;
|
||||
}
|
||||
impl_wrap!(FFIGCancellable, gio::Cancellable);
|
||||
impl_wrap!(FFIGCancellable, gio::Cancellable, gio_sys::GCancellable);
|
||||
|
||||
// An error type helper; separate from the GObject bridging
|
||||
mod err {
|
||||
@ -150,3 +185,24 @@ mod err {
|
||||
}
|
||||
}
|
||||
pub(crate) use err::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn passthrough() -> Result<()> {
|
||||
let cancellable = gio::NONE_CANCELLABLE;
|
||||
let td = tempfile::tempdir()?;
|
||||
let p = td.path().join("repo");
|
||||
let r = ostree::Repo::new_for_path(&p);
|
||||
r.create(ostree::RepoMode::Archive, cancellable)?;
|
||||
let fd = r.get_dfd();
|
||||
assert_eq!(
|
||||
fd,
|
||||
crate::ffi::testutil_validate_cxxrs_passthrough(r.gobj_rewrap())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -250,6 +250,7 @@ pub mod ffi {
|
||||
version_suffix: &str,
|
||||
last_version: &str,
|
||||
) -> Result<String>;
|
||||
fn testutil_validate_cxxrs_passthrough(repo: Pin<&mut OstreeRepo>) -> i32;
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
|
@ -289,6 +289,16 @@ util_next_version (rust::Str auto_version_prefix,
|
||||
g_autofree char *v = increment_version (version_suffix, last_version, next_version->str, date_tag_given ? "0" : NULL);
|
||||
return rust::String(v);
|
||||
}
|
||||
|
||||
// A test function to validate that we can pass glib-rs types
|
||||
// from Rust back through cxx-rs to C++.
|
||||
int
|
||||
testutil_validate_cxxrs_passthrough(OstreeRepo &repo) noexcept
|
||||
{
|
||||
return ostree_repo_get_dfd(&repo);
|
||||
}
|
||||
|
||||
|
||||
} /* namespace */
|
||||
#undef VERSION_TAG_REGEX
|
||||
|
||||
|
@ -99,6 +99,9 @@ util_next_version (rust::Str auto_version_prefix,
|
||||
rust::Str version_suffix,
|
||||
rust::Str last_version);
|
||||
|
||||
int
|
||||
testutil_validate_cxxrs_passthrough(OstreeRepo &repo) noexcept;
|
||||
|
||||
}
|
||||
|
||||
// Below here is C code
|
||||
|
Loading…
Reference in New Issue
Block a user