1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
//! Useful when `extern "C" fn()` delegate types give compile errors.
//!
//! # Example
//!
//! If you want to accept user-provided callbacks or "delegates":
//!
//!```
//! use interoptopus::{ffi_function, callback};
//! use interoptopus::patterns::slice::FFISlice;
//!
//! callback!(CallbackSlice(x: FFISlice<u8>) -> u8);
//!
//! #[ffi_function]
//! pub extern "C" fn my_function(callback: CallbackSlice) {
//! callback.call(FFISlice::empty());
//! }
//!
//! ```
//! Backends supporting this pattern might generate the equivalent to the following pseudo-code:
//!
//! ```csharp
//! public delegate uint CallbackSlice(Sliceu8 x0);
//!
//! void my_function(CallbackSlice callback);
//! ```
//!
//! Backends not supporting this pattern, and C FFI, will see the equivalent of the following C code:
//! ```c
//! typedef void (*fptr_fn_Sliceu8)(my_library_slicemutu8 x0);
//!
//! void my_function(fptr_fn_Sliceu8 callback);
//! ```
//!
//!
//! # Code Generation
//!
//! The macro [**`callback`**](crate::callback) enables two use cases:
//!
//! - On the **Rust** side it will generate a new function-pointer type with better compatibility
//! with respect to lifetimes in signatures, and accepting an unlimited number of args.
//!- On the **FFI side** a _properly named_ callback (delegate, function pointer ...) type can be
//! produced (e.g., `MyCallback`), instead of one where it's name is just a generic concatenation
//! of all used parameters (e.g., `InteropDelegate_fn_i32_i32`).
//!
//!
//! # Background
//!
//! Due to how we generate FFI metadata and how Rust traits work there are some types which
//! don't work nicely with Interoptopus: function pointers. Practically speaking, the following code _should_ work:
//!
//! ```ignore
//! use interoptopus::ffi_function;
//! use interoptopus::patterns::slice::FFISlice;
//!
//! pub type CallbackSlice = extern "C" fn(FFISlice<u8>);
//!
//! #[ffi_function]
//! pub extern "C" fn my_function(callback: CallbackSlice) {
//! callback(FFISlice::empty());
//! }
//!
//! ```
//!
//! The intention is to provide a FFI function `my_function`, accepting
//! a callback, which in turn accepts an `FFISlice<'a, u8>`.
//! Although this is valid FFI code to write, a compile error is returned, which may look like this:
//!
//! ```text
//! error: implementation of `CTypeInfo` is not general enough
//! [...]
//! = note: ...`CTypeInfo` would have to be implemented for the type `for<'r> extern "C" fn(FFISlice<'r, u8>)`
//! = note: ...but `CTypeInfo` is actually implemented for the type `extern "C" fn(FFISlice<'0, u8>)`, for some specific lifetime `'0`
//! = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
//! ```
//!
//! The reasons for this are somewhat technical, but it boils down to us being unable to generally
//! implement [`CTypeInfo`](crate::lang::rust::CTypeInfo) for _all_ types you may want to use;
//! [`FFISlice`](crate::patterns::slice::FFISlice) here being one of them.
//! To fix this, you can replace `pub type CallbackSlice = ...` with a `callback!` call
//! which should generate a helper type that works.
//!
use crate::lang::c::FnPointerType;
/// Internal helper naming a generated callback type wrapper.
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct NamedCallback {
name: String,
fnpointer: FnPointerType,
}
impl NamedCallback {
/// Creates a new named callback.
pub fn new(name: String, callback: FnPointerType) -> Self {
Self { name, fnpointer: callback }
}
/// Gets the type name of this callback.
pub fn name(&self) -> &str {
&self.name
}
/// Returns the function pointer type.
pub fn fnpointer(&self) -> &FnPointerType {
&self.fnpointer
}
}
/// Defines a callback type, akin to a `fn f(T) -> R` wrapped in an [Option](std::option).
///
/// A named delegate will be emitted in languages supporting them, otherwise a regular
/// function pointer. For details, please see the [**callbacks module**](crate::patterns::callbacks).
///
/// # Example
///
/// This defines a type `MyCallback` with a parameter `slice` returning an `u8`.
///
/// ```
/// use interoptopus::callback;
/// use interoptopus::patterns::slice::FFISlice;
///
/// callback!(MyCallback(slice: FFISlice<u8>) -> u8);
/// ```
///
/// The generated type definition similar to:
///
/// ```
/// # use interoptopus::patterns::slice::FFISlice;
/// #[repr(transparent)]
/// pub struct MyCallback(Option<extern "C" fn(FFISlice<u8>) -> u8>);
/// ```
#[macro_export]
macro_rules! callback {
($name:ident($($param:ident: $ty:ty),*) $(-> $rval:ty)?) => {
#[derive(Default, Clone)]
#[repr(transparent)]
pub struct $name(Option<extern "C" fn($($ty),*) $(-> $rval)?>);
impl $name {
/// Will call function if it exists, panic otherwise.
pub fn call(&self, $($param: $ty),*) $(-> $rval)? {
self.0.expect("Assumed function would exist but it didn't.")($($param),*)
}
}
impl From<extern "C" fn($($ty),*) $(-> $rval)?> for $name {
fn from(x: extern "C" fn($($ty),*) $(-> $rval)?) -> Self {
Self(Some(x))
}
}
unsafe impl interoptopus::lang::rust::CTypeInfo for $name {
fn type_info() -> interoptopus::lang::c::CType {
use interoptopus::lang::rust::CTypeInfo;
let rval = < () as CTypeInfo >::type_info();
$(
let rval = < $rval as CTypeInfo >::type_info();
)?
let params = vec![
$(
interoptopus::lang::c::Parameter::new(stringify!($param).to_string(), < $ty as CTypeInfo >::type_info()),
)*
];
let sig = interoptopus::lang::c::FunctionSignature::new(params, rval);
let fn_pointer = interoptopus::lang::c::FnPointerType::new(sig);
let named_callback = interoptopus::patterns::callbacks::NamedCallback::new(stringify!($name).to_string(), fn_pointer);
interoptopus::lang::c::CType::Pattern(interoptopus::patterns::TypePattern::NamedCallback(named_callback))
}
}
};
}