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:
Colin Walters 2021-02-13 22:00:58 +00:00 committed by OpenShift Merge Robot
parent 8d9e113e8f
commit 8a62ad9f3d
4 changed files with 73 additions and 3 deletions

View File

@ -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(())
}
}

View File

@ -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++" {

View File

@ -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

View File

@ -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