Support calling C++ methods from Rust

These methods can be declared in the bridge by naming the first
argument self and making it a reference to the containing class, e.g.,
  fn get(self: &C) -> usize;
  fn set(self: &mut C, n: usize);
This syntax requires Rust 1.43.

Note that the implementation also changes the internal naming of shim
functions so that they also contain the name of the owning class, if
any.  This allows defining multiple methods with the same name on
different objects.
This commit is contained in:
Joel Galenson 2020-04-07 15:54:05 -07:00
parent 4272d98530
commit 3d4f612b34
10 changed files with 105 additions and 23 deletions

View File

@ -9,12 +9,15 @@ ThingC::ThingC(std::string appname) : appname(std::move(appname)) {}
ThingC::~ThingC() { std::cout << "done with ThingC" << std::endl; }
const std::string &ThingC::get_name() const {
std::cout << "I'm a C++ method!" << std::endl;
return this->appname;
}
std::unique_ptr<ThingC> make_demo(rust::Str appname) {
return std::unique_ptr<ThingC>(new ThingC(std::string(appname)));
}
const std::string &get_name(const ThingC &thing) { return thing.appname; }
void do_thing(SharedThing state) { print_r(*state.y); }
} // namespace example

View File

@ -12,12 +12,13 @@ public:
~ThingC();
std::string appname;
const std::string &get_name() const;
};
struct SharedThing;
std::unique_ptr<ThingC> make_demo(rust::Str appname);
const std::string &get_name(const ThingC &thing);
void do_thing(SharedThing state);
} // namespace example

View File

@ -11,8 +11,9 @@ mod ffi {
type ThingC;
fn make_demo(appname: &str) -> UniquePtr<ThingC>;
fn get_name(thing: &ThingC) -> &CxxString;
fn get_name(self: &ThingC) -> &CxxString;
fn do_thing(state: SharedThing);
}
extern "Rust" {
@ -29,7 +30,7 @@ fn print_r(r: &ThingR) {
fn main() {
let x = ffi::make_demo("demo of cxx::bridge");
println!("this is a {}", ffi::get_name(x.as_ref().unwrap()));
println!("this is a {}", x.as_ref().unwrap().get_name());
ffi::do_thing(ffi::SharedThing {
z: 222,

View File

@ -2,7 +2,7 @@ use crate::gen::namespace::Namespace;
use crate::gen::out::OutFile;
use crate::gen::{include, Opt};
use crate::syntax::atom::Atom::{self, *};
use crate::syntax::{Api, ExternFn, Signature, Struct, Type, Types, Var};
use crate::syntax::{Api, ExternFn, Receiver, Signature, Struct, Type, Types, Var};
use proc_macro2::Ident;
pub(super) fn gen(
@ -327,8 +327,11 @@ fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
write_extern_return_type_space(out, &efn.ret, types);
}
write!(out, "{}cxxbridge02${}(", out.namespace, efn.ident);
if let Some(base) = &efn.receiver {
write!(out, "{} *__receiver$", base.ident);
}
for (i, arg) in efn.args.iter().enumerate() {
if i > 0 {
if i > 0 || efn.receiver.is_some() {
write!(out, ", ");
}
if arg.ty == RustString {
@ -347,14 +350,27 @@ fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
writeln!(out, ") noexcept {{");
write!(out, " ");
write_return_type(out, &efn.ret);
write!(out, "(*{}$)(", efn.ident);
match &efn.receiver {
None => write!(out, "(*{}$)(", efn.ident),
Some(base) => write!(out, "({}::*{}$)(", base.ident, efn.ident),
}
for (i, arg) in efn.args.iter().enumerate() {
if i > 0 {
write!(out, ", ");
}
write_type(out, &arg.ty);
}
writeln!(out, ") = {};", efn.ident);
write!(out, ")");
match &efn.receiver {
Some(Receiver { mutability: None, ident: _ }) => write!(out, " const"),
_ => {},
}
write!(out, " = ");
match &efn.receiver {
None => write!(out, "{}", efn.ident),
Some(base) => write!(out, "&{}::{}", base.ident, efn.ident),
}
writeln!(out, ";");
write!(out, " ");
if efn.throws {
writeln!(out, "::rust::Str::Repr throw$;");
@ -377,7 +393,10 @@ fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
}
_ => {}
}
write!(out, "{}$(", efn.ident);
match &efn.receiver {
None => write!(out, "{}$(", efn.ident),
Some(_) => write!(out, "(__receiver$->*{}$)(", efn.ident),
}
for (i, arg) in efn.args.iter().enumerate() {
if i > 0 {
write!(out, ", ");

View File

@ -123,6 +123,13 @@ fn expand_cxx_type(ety: &ExternType) -> TokenStream {
fn expand_cxx_function_decl(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
let ident = &efn.ident;
let receiver = efn.receiver.iter().map(|base| {
let ident = &base.ident;
match base.mutability {
None => quote!(_: &#ident),
Some(_) => quote!(_: &mut #ident),
}
});
let args = efn.args.iter().map(|arg| {
let ident = &arg.ident;
let ty = expand_extern_type(&arg.ty);
@ -136,6 +143,7 @@ fn expand_cxx_function_decl(namespace: &Namespace, efn: &ExternFn, types: &Types
quote!(#ident: #ty)
}
});
let all_args = receiver.chain(args);
let ret = if efn.throws {
quote!(-> ::cxx::private::Result)
} else {
@ -150,7 +158,7 @@ fn expand_cxx_function_decl(namespace: &Namespace, efn: &ExternFn, types: &Types
let local_name = format_ident!("__{}", ident);
quote! {
#[link_name = #link_name]
fn #local_name(#(#args,)* #outparam) #ret;
fn #local_name(#(#all_args,)* #outparam) #ret;
}
}
@ -158,7 +166,12 @@ fn expand_cxx_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types
let ident = &efn.ident;
let doc = &efn.doc;
let decl = expand_cxx_function_decl(namespace, efn, types);
let args = &efn.args;
let receiver = efn.receiver.iter().map(|base| match base.mutability {
None => quote!(&self),
Some(_) => quote!(&mut self),
});
let args = efn.args.iter().map(|arg| quote!(#arg));
let all_args = receiver.chain(args);
let ret = if efn.throws {
let ok = match &efn.ret {
Some(ret) => quote!(#ret),
@ -169,7 +182,8 @@ fn expand_cxx_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types
expand_return_type(&efn.ret)
};
let indirect_return = indirect_return(efn, types);
let vars = efn.args.iter().map(|arg| {
let receiver_var = efn.receiver.iter().map(|_| quote!(self));
let arg_vars = efn.args.iter().map(|arg| {
let var = &arg.ident;
match &arg.ty {
Type::Ident(ident) if ident == RustString => {
@ -189,6 +203,7 @@ fn expand_cxx_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types
_ => quote!(#var),
}
});
let vars = receiver_var.chain(arg_vars);
let trampolines = efn
.args
.iter()
@ -274,18 +289,36 @@ fn expand_cxx_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types
})
}
.unwrap_or(call);
quote! {
#doc
pub fn #ident(#args) #ret {
extern "C" {
#decl
let receiver_ident = efn.receiver.as_ref().map(|base| &base.ident);
match receiver_ident {
None => quote! {
#doc
pub fn #ident(#(#all_args,)*) #ret {
extern "C" {
#decl
}
#trampolines
unsafe {
#setup
#expr
}
}
#trampolines
unsafe {
#setup
#expr
},
Some(base_ident) => quote! {
#doc
impl #base_ident {
pub fn #ident(#(#all_args,)*) #ret {
extern "C" {
#decl
}
#trampolines
unsafe {
#setup
#expr
}
}
}
}
},
}
}

View File

@ -195,6 +195,10 @@ fn check_multiple_arg_lifetimes(cx: &mut Check, efn: &ExternFn) {
}
}
if efn.receiver.is_some() {
reference_args += 1;
}
if reference_args != 1 {
cx.error(
efn,

View File

@ -49,6 +49,9 @@ pub mod ffi {
fn c_try_return_sliceu8(s: &[u8]) -> Result<&[u8]>;
fn c_try_return_rust_string() -> Result<String>;
fn c_try_return_unique_ptr_string() -> Result<UniquePtr<CxxString>>;
fn get(self: &C) -> usize;
fn set(self: &mut C, n: usize) -> usize;
}
extern "Rust" {

View File

@ -15,6 +15,11 @@ C::C(size_t n) : n(n) {}
size_t C::get() const { return this->n; }
size_t C::set(size_t n) {
this->n = n;
return this->n;
}
size_t c_return_primitive() { return 2020; }
Shared c_return_shared() { return Shared{2020}; }

View File

@ -12,6 +12,7 @@ class C {
public:
C(size_t n);
size_t get() const;
size_t set(size_t n);
private:
size_t n;

View File

@ -107,6 +107,18 @@ fn test_c_call_r() {
check!(cxx_run_test());
}
#[test]
fn test_c_method_calls() {
let mut unique_ptr = ffi::c_return_unique_ptr();
let old_value = unique_ptr.as_ref().unwrap().get();
assert_eq!(2020, old_value);
assert_eq!(2021, unique_ptr.as_mut().unwrap().set(2021));
assert_eq!(2021, unique_ptr.as_ref().unwrap().get());
assert_eq!(old_value, unique_ptr.as_mut().unwrap().set(old_value));
assert_eq!(old_value, unique_ptr.as_ref().unwrap().get())
}
#[no_mangle]
extern "C" fn cxx_test_suite_get_box() -> *mut cxx_test_suite::R {
Box::into_raw(Box::new(2020usize))