Skip to content

Commit

Permalink
Supports stack allocated object
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon committed Oct 6, 2024
1 parent baba340 commit 700365d
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 20 deletions.
53 changes: 51 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,62 @@

This is a Rust crate to generate binding to C++ functions and methods with zero overhead. It work by generating a FFI function with a symbol matching with the C++ side. Thus, allows you to call those C++ functions and methods directly from Rust without any overhead the same as C++ call themself.

This crate does not provides safe wrappers for C++ functions. It was designed for people who know how to use C++.
The goal of this crate is to allows efficient integration between Rust and C++, which mean most of the generated code will be unsafe. You need to know how the C++ code you are going to use is working otherwise you will be hit by undefined behaviors.

## Limitations

- Rust can only create a C++ object on the heap.
- Rust cannot access an instance variable directly. You need to create a getter/setter for each variable you want to access.

## Usage

Copy `cppbind.hpp` on the root of this repository to your crate and create C++ files for the code you want to use on the Rust side. You still need some C++ files here even if the code you want to use living somewhere else to define type metadata required by `cppbind`. You need the following line for each C++ class you want to use on Rust side:

```cpp
#include "cppbind.hpp"

CPPBIND_CLASS(class1);
```
`class1` must be a complete type before `CPPBIND_CLASS` line. The next step is setup `build.rs` to build the C++ files you just created. The following example use [cc](https://crates.io/crates/cc) to build those C++ files:
```rust
use std::path::{Path, PathBuf};
fn main() {
// Build C++ sources.
let root = Path::new("src");
let mut b = cc::Build::new();
let files = ["main.cpp"];
for f in files.into_iter().map(|f| root.join(f)) {
b.file(&f);
println!("cargo::rerun-if-changed={}", f.to_str().unwrap());
}
b.cpp(true).compile("example");
// Set variables required by cppbind. The CPPBIND_METADATA variable need to be a path to a
// static library that defines class metadata with CPPBIND_CLASS you want to use on Rust side.
// This library can also contains other C++ code. In this example this library was built on the
// above using cc crate.
let mut lib = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
if cfg!(windows) {
lib.push("example.lib");
} else {
lib.push("libexample.a");
}
println!(
"cargo::rustc-env=CPPBIND_METADATA={}",
lib.to_str().unwrap()
);
}
```

See [Build Scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html) for more details about `build.rs`.

## License

This project is licensed under either of
Expand Down
3 changes: 3 additions & 0 deletions cppbind.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

#include <stddef.h>

#define CPPBIND_CLASS(n) \
template<> const size_t cppbind::type_info<n>::size = sizeof(n)

namespace cppbind {
template<typename T>
struct type_info {
Expand Down
20 changes: 19 additions & 1 deletion example/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::path::Path;
use std::path::{Path, PathBuf};

fn main() {
// Build C++ sources.
let root = Path::new("src");
let mut b = cc::Build::new();
let files = ["main.cpp"];
Expand All @@ -12,4 +13,21 @@ fn main() {
}

b.cpp(true).compile("example");

// Set variables required by cppbind. The CPPBIND_METADATA variable need to be a path to a
// static library that defines class metadata with CPPBIND_CLASS you want to use on Rust side.
// This library can also contains other C++ code. In this example this library was built on the
// above using cc crate.
let mut lib = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());

if cfg!(windows) {
lib.push("example.lib");
} else {
lib.push("libexample.a");
}

println!(
"cargo::rustc-env=CPPBIND_METADATA={}",
lib.to_str().unwrap()
);
}
3 changes: 1 addition & 2 deletions example/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,4 @@ class class1 {
std::string v1;
};

template<>
const size_t cppbind::type_info<class1>::size = sizeof(class1);
CPPBIND_CLASS(class1);
1 change: 1 addition & 0 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ proc-macro = true
proc-macro2 = "1.0.86"
quote = "1.0.37"
syn = "2.0.79"
thiserror = "1.0.64"
23 changes: 14 additions & 9 deletions macros/src/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,32 @@ pub fn render(items: Declarations) -> syn::Result<TokenStream> {

fn render_class(item: Class) -> syn::Result<TokenStream> {
// Render constructors.
let mut ctors = TokenStream::new();
let class = item.name;
let mut impls = TokenStream::new();

for (i, ctor) in item.ctors.into_iter().enumerate() {
let name = format_ident!("new{}", i + 1, span = ctor.span);

ctors.extend(quote! {
pub unsafe fn #name() -> ::cppbind::Ptr<Self> {
todo!();
impls.extend(quote! {
pub unsafe fn #name(this: T) -> Self {
Self {
mem: this,
phantom: ::std::marker::PhantomData,
}
}
});
}

// Compose.
let name = item.name;

Ok(quote! {
#[allow(non_camel_case_types)]
pub struct #name(std::marker::PhantomData<std::rc::Rc<()>>);
pub struct #class<T> {
mem: T,
phantom: ::std::marker::PhantomData<::std::rc::Rc<()>>,
}

impl #name {
#ctors
impl<T: ::cppbind::Memory<Class = Self>> #class<T> {
#impls
}
})
}
Expand Down
7 changes: 7 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use self::meta::Metadata;
use proc_macro::TokenStream;
use std::sync::LazyLock;
use syn::{parse_macro_input, Error};

mod cpp;
mod meta;

/// Generate binding to C++ functions and methods.
#[proc_macro]
Expand All @@ -12,3 +15,7 @@ pub fn cpp(body: TokenStream) -> TokenStream {
.unwrap_or_else(Error::into_compile_error)
.into()
}

static META: LazyLock<Metadata> = LazyLock::new(|| {
Metadata::from_static_lib(std::env::var_os("CPPBIND_METADATA").unwrap()).unwrap()
});
15 changes: 15 additions & 0 deletions macros/src/meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use std::path::Path;
use thiserror::Error;

/// Contains C++ metadata loaded from a static library.
pub struct Metadata {}

impl Metadata {
pub fn from_static_lib(path: impl AsRef<Path>) -> Result<Self, MetadataError> {
Ok(Self {})
}
}

/// Represents an error when [`Metadata`] fails to load.
#[derive(Debug, Error)]
pub enum MetadataError {}
18 changes: 12 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
pub use cppbind_macros::*;

/// RAII struct to free a C++ object on the heap.
pub struct Ptr<T>(*mut T);
/// Memory of a C++ class that live on a heap.
pub struct Heap<T>(*mut T);

impl<T> Drop for Ptr<T> {
fn drop(&mut self) {
unsafe { std::ptr::drop_in_place(self.0) };
}
impl<T: Memory> Memory for Heap<T> {
type Class = T::Class;
}

unsafe impl<T: Send> Send for Heap<T> {}
unsafe impl<T: Sync> Sync for Heap<T> {}

/// Memory of a C++ class.
pub trait Memory {
type Class;
}

0 comments on commit 700365d

Please sign in to comment.