Support calling Rust methods from C++
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: &R) -> usize;
fn set(self: &mut R, n: usize);
This syntax requires Rust 1.43.
diff --git a/demo-cxx/demo.cc b/demo-cxx/demo.cc
index 6fc2465..bc0d976 100644
--- a/demo-cxx/demo.cc
+++ b/demo-cxx/demo.cc
@@ -18,7 +18,10 @@
return std::unique_ptr<ThingC>(new ThingC(std::string(appname)));
}
-void do_thing(SharedThing state) { print_r(*state.y); }
+void do_thing(SharedThing state) {
+ print_r(*state.y);
+ state.y->print();
+}
} // namespace example
} // namespace org
diff --git a/demo-rs/src/main.rs b/demo-rs/src/main.rs
index 759bbe1..713a1d1 100644
--- a/demo-rs/src/main.rs
+++ b/demo-rs/src/main.rs
@@ -19,6 +19,7 @@
extern "Rust" {
type ThingR;
fn print_r(r: &ThingR);
+ fn print(self: &ThingR);
}
}
@@ -28,6 +29,12 @@
println!("called back with r={}", r.0);
}
+impl ThingR {
+ fn print(&self) {
+ println!("method called back with r={}", self.0);
+ }
+}
+
fn main() {
let x = ffi::make_demo("demo of cxx::bridge");
println!("this is a {}", x.as_ref().unwrap().get_name());
diff --git a/gen/write.rs b/gen/write.rs
index 41b5b37..7e1caac 100644
--- a/gen/write.rs
+++ b/gen/write.rs
@@ -2,7 +2,7 @@
use crate::gen::out::OutFile;
use crate::gen::{include, Opt};
use crate::syntax::atom::Atom::{self, *};
-use crate::syntax::{Api, ExternFn, Receiver, Signature, Struct, Type, Types, Var};
+use crate::syntax::{Api, ExternFn, ExternType, Receiver, Signature, Struct, Type, Types, Var};
use proc_macro2::Ident;
pub(super) fn gen(
@@ -45,9 +45,25 @@
}
for api in apis {
- if let Api::Struct(strct) = api {
- out.next_section();
- write_struct(out, strct);
+ match api {
+ Api::Struct(strct) => {
+ out.next_section();
+ write_struct(out, strct);
+ }
+ Api::RustType(ety) => {
+ let methods = apis.iter().filter_map(|api| match api {
+ Api::RustFunction(efn) => match &efn.sig.receiver {
+ Some(rcvr) if rcvr.ident == ety.ident => Some(efn),
+ _ => None,
+ },
+ _ => None,
+ }).collect::<Vec<_>>();
+ if !methods.is_empty() {
+ out.next_section();
+ write_struct_with_methods(out, ety, methods);
+ }
+ }
+ _ => {}
}
}
@@ -300,6 +316,21 @@
writeln!(out, "using {} = {};", ident, ident);
}
+fn write_struct_with_methods(out: &mut OutFile, ety: &ExternType, methods: Vec<&ExternFn>) {
+ for line in ety.doc.to_string().lines() {
+ writeln!(out, "//{}", line);
+ }
+ writeln!(out, "struct {} final {{", ety.ident);
+ for method in &methods {
+ write!(out, " ");
+ let sig = &method.sig;
+ let local_name = method.ident.to_string();
+ write_rust_function_shim_decl(out, &local_name, sig, None, false);
+ writeln!(out, ";");
+ }
+ writeln!(out, "}};");
+}
+
fn write_exception_glue(out: &mut OutFile, apis: &[Api]) {
let mut has_cxx_throws = false;
for api in apis {
@@ -326,7 +357,11 @@
} else {
write_extern_return_type_space(out, &efn.ret, types);
}
- write!(out, "{}cxxbridge02${}(", out.namespace, efn.ident);
+ let receiver_type = match &efn.receiver {
+ Some(base) => base.ident.to_string(),
+ None => "_".to_string(),
+ };
+ write!(out, "{}cxxbridge02${}${}(", out.namespace, receiver_type, efn.ident);
if let Some(base) = &efn.receiver {
write!(out, "{} *__receiver$", base.ident);
}
@@ -471,7 +506,11 @@
}
fn write_rust_function_decl(out: &mut OutFile, efn: &ExternFn, types: &Types) {
- let link_name = format!("{}cxxbridge02${}", out.namespace, efn.ident);
+ let receiver_type = match &efn.receiver {
+ Some(base) => base.ident.to_string(),
+ None => "_".to_string(),
+ };
+ let link_name = format!("{}cxxbridge02${}${}", out.namespace, receiver_type, efn.ident);
let indirect_call = false;
write_rust_function_decl_impl(out, &link_name, efn, types, indirect_call);
}
@@ -490,6 +529,10 @@
}
write!(out, "{}(", link_name);
let mut needs_comma = false;
+ if let Some(base) = &sig.receiver {
+ write!(out, "{} &__receiver$", base.ident);
+ needs_comma = true;
+ }
for arg in &sig.args {
if needs_comma {
write!(out, ", ");
@@ -519,20 +562,26 @@
writeln!(out, "//{}", line);
}
let local_name = efn.ident.to_string();
- let invoke = format!("{}cxxbridge02${}", out.namespace, efn.ident);
+ let receiver_type = match &efn.receiver {
+ Some(base) => base.ident.to_string(),
+ None => "_".to_string(),
+ };
+ let invoke = format!("{}cxxbridge02${}${}", out.namespace, receiver_type, efn.ident);
let indirect_call = false;
write_rust_function_shim_impl(out, &local_name, efn, types, &invoke, indirect_call);
}
-fn write_rust_function_shim_impl(
+fn write_rust_function_shim_decl(
out: &mut OutFile,
local_name: &str,
sig: &Signature,
- types: &Types,
- invoke: &str,
+ receiver: Option<&Receiver>,
indirect_call: bool,
) {
write_return_type(out, &sig.ret);
+ if let Some(base) = receiver {
+ write!(out, "{}::", base.ident);
+ }
write!(out, "{}(", local_name);
for (i, arg) in sig.args.iter().enumerate() {
if i > 0 {
@@ -551,6 +600,21 @@
if !sig.throws {
write!(out, " noexcept");
}
+}
+
+fn write_rust_function_shim_impl(
+ out: &mut OutFile,
+ local_name: &str,
+ sig: &Signature,
+ types: &Types,
+ invoke: &str,
+ indirect_call: bool,
+) {
+ if out.header && sig.receiver.is_some() {
+ // We've already defined this inside the struct.
+ return;
+ }
+ write_rust_function_shim_decl(out, local_name, sig, sig.receiver.as_ref(), indirect_call);
if out.header {
writeln!(out, ";");
} else {
@@ -589,8 +653,11 @@
write!(out, "::rust::Str::Repr error$ = ");
}
write!(out, "{}(", invoke);
+ if let Some(_) = &sig.receiver {
+ write!(out, "*this");
+ }
for (i, arg) in sig.args.iter().enumerate() {
- if i > 0 {
+ if i > 0 || sig.receiver.is_some() {
write!(out, ", ");
}
match &arg.ty {
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 998b47e..2d3166a 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -154,7 +154,11 @@
let ret = expand_extern_type(efn.ret.as_ref().unwrap());
outparam = Some(quote!(__return: *mut #ret));
}
- let link_name = format!("{}cxxbridge02${}", namespace, ident);
+ let receiver_type = match &efn.receiver {
+ Some(base) => base.ident.to_string(),
+ None => "_".to_string(),
+ };
+ let link_name = format!("{}cxxbridge02${}${}", namespace, receiver_type, ident);
let local_name = format_ident!("__{}", ident);
quote! {
#[link_name = #link_name]
@@ -366,7 +370,11 @@
fn expand_rust_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
let ident = &efn.ident;
- let link_name = format!("{}cxxbridge02${}", namespace, ident);
+ let receiver_type = match &efn.receiver {
+ Some(base) => base.ident.to_string(),
+ None => "_".to_string(),
+ };
+ let link_name = format!("{}cxxbridge02${}${}", namespace, receiver_type, ident);
let local_name = format_ident!("__{}", ident);
let catch_unwind_label = format!("::{}", ident);
let invoke = Some(ident);
@@ -388,6 +396,13 @@
catch_unwind_label: String,
invoke: Option<&Ident>,
) -> TokenStream {
+ let receiver = sig.receiver.iter().map(|base| {
+ let ident = &base.ident;
+ match base.mutability {
+ None => quote!(__receiver: &#ident),
+ Some(_) => quote!(__receiver: &mut #ident),
+ }
+ });
let args = sig.args.iter().map(|arg| {
let ident = &arg.ident;
let ty = expand_extern_type(&arg.ty);
@@ -397,6 +412,7 @@
quote!(#ident: #ty)
}
});
+ let all_args = receiver.chain(args);
let vars = sig.args.iter().map(|arg| {
let ident = &arg.ident;
@@ -418,7 +434,10 @@
});
let mut call = match invoke {
- Some(ident) => quote!(super::#ident),
+ Some(ident) => match sig.receiver {
+ None => quote!(super::#ident),
+ Some(_) => quote!(__receiver.#ident),
+ },
None => quote!(__extern),
};
call.extend(quote! { (#(#vars),*) });
@@ -476,7 +495,7 @@
quote! {
#[doc(hidden)]
#[export_name = #link_name]
- unsafe extern "C" fn #local_name(#(#args,)* #outparam #pointer) #ret {
+ unsafe extern "C" fn #local_name(#(#all_args,)* #outparam #pointer) #ret {
let __fn = concat!(module_path!(), #catch_unwind_label);
#expr
}
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index a720a80..4da5740 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -56,6 +56,7 @@
extern "Rust" {
type R;
+ type R2;
fn r_return_primitive() -> usize;
fn r_return_shared() -> Shared;
@@ -80,11 +81,28 @@
fn r_try_return_void() -> Result<()>;
fn r_try_return_primitive() -> Result<usize>;
fn r_fail_return_primitive() -> Result<usize>;
+
+ fn r_return_r2(n: usize) -> Box<R2>;
+ fn get(self: &R2) -> usize;
+ fn set(self: &mut R2, n: usize) -> usize;
}
}
pub type R = usize;
+pub struct R2(usize);
+
+impl R2 {
+ fn get(&self) -> usize {
+ self.0
+ }
+
+ fn set(&mut self, n: usize) -> usize {
+ self.0 = n;
+ n
+ }
+}
+
#[derive(Debug)]
struct Error;
@@ -187,3 +205,7 @@
fn r_fail_return_primitive() -> Result<usize, Error> {
Err(Error)
}
+
+fn r_return_r2(n: usize) -> Box<R2> {
+ Box::new(R2(n))
+}
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index 2daae53..8893ee8 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -181,6 +181,13 @@
ASSERT(std::strcmp(e.what(), "rust error") == 0);
}
+ auto r2 = r_return_r2(2020);
+ ASSERT(r2->get() == 2020);
+ ASSERT(r2->set(2021) == 2021);
+ ASSERT(r2->get() == 2021);
+ ASSERT(r2->set(2020) == 2020);
+ ASSERT(r2->get() == 2020);
+
cxx_test_suite_set_correct();
return nullptr;
}