use core::ffi::{c_uint, c_void};
use core::mem;
use crate::raw;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum Error {
Typedef,
Abi,
}
pub type Result<T> = ::core::result::Result<T, Error>;
fn status_to_result<R>(status: raw::ffi_status, good: R) -> Result<R> {
if status == raw::ffi_status_FFI_OK {
Ok(good)
} else if status == raw::ffi_status_FFI_BAD_TYPEDEF {
Err(Error::Typedef)
}
else {
Err(Error::Abi)
}
}
#[derive(Clone, Copy, Debug, Hash)]
#[repr(C)]
pub struct CodePtr(pub *mut c_void);
impl CodePtr {
pub fn from_fun(fun: unsafe extern "C" fn()) -> Self {
CodePtr(fun as *mut c_void)
}
pub fn from_ptr(fun: *const c_void) -> Self {
CodePtr(fun as *mut c_void)
}
pub fn as_fun(&self) -> &unsafe extern "C" fn() {
unsafe { self.as_any_ref_() }
}
pub unsafe fn as_safe_fun(&self) -> &extern "C" fn() {
self.as_any_ref_()
}
pub(crate) unsafe fn as_any_ref_<T>(&self) -> &T {
&*(&self.0 as *const _ as *const T)
}
pub fn as_ptr(self) -> *const c_void {
self.0
}
pub fn as_mut_ptr(self) -> *mut c_void {
self.0
}
}
pub use raw::{
ffi_abi, ffi_abi_FFI_DEFAULT_ABI, ffi_arg, ffi_cif, ffi_closure, ffi_sarg, ffi_status, ffi_type,
};
pub mod types {
pub use crate::raw::{
ffi_type_double as double, ffi_type_float as float, ffi_type_longdouble as longdouble,
ffi_type_pointer as pointer, ffi_type_sint16 as sint16, ffi_type_sint32 as sint32,
ffi_type_sint64 as sint64, ffi_type_sint8 as sint8, ffi_type_uint16 as uint16,
ffi_type_uint32 as uint32, ffi_type_uint64 as uint64, ffi_type_uint8 as uint8,
ffi_type_void as void,
};
#[cfg(feature = "complex")]
pub use crate::raw::{
ffi_type_complex_double as complex_double, ffi_type_complex_float as complex_float,
ffi_type_complex_longdouble as complex_longdouble,
};
}
pub mod type_tag {
use crate::raw;
use core::ffi::c_ushort;
pub const STRUCT: c_ushort = raw::ffi_type_enum_STRUCT as c_ushort;
#[cfg(feature = "complex")]
pub const COMPLEX: c_ushort = raw::ffi_type_enum_COMPLEX as c_ushort;
}
pub unsafe fn prep_cif(
cif: *mut ffi_cif,
abi: ffi_abi,
nargs: usize,
rtype: *mut ffi_type,
atypes: *mut *mut ffi_type,
) -> Result<()> {
let status = raw::ffi_prep_cif(cif, abi, nargs as c_uint, rtype, atypes);
status_to_result(status, ())
}
pub unsafe fn prep_cif_var(
cif: *mut ffi_cif,
abi: ffi_abi,
nfixedargs: usize,
ntotalargs: usize,
rtype: *mut ffi_type,
atypes: *mut *mut ffi_type,
) -> Result<()> {
let status = raw::ffi_prep_cif_var(
cif,
abi,
nfixedargs as c_uint,
ntotalargs as c_uint,
rtype,
atypes,
);
status_to_result(status, ())
}
pub unsafe fn call<R>(cif: *mut ffi_cif, fun: CodePtr, args: *mut *mut c_void) -> R {
if mem::size_of::<R>() < mem::size_of::<usize>() {
let mut result = mem::MaybeUninit::<usize>::uninit();
unsafe {
raw::ffi_call(
cif,
Some(*fun.as_safe_fun()),
result.as_mut_ptr().cast::<c_void>(),
args,
);
let result = result.assume_init();
if cfg!(target_endian = "big") {
call_return_small_big_endian_result((*(*cif).rtype).type_, &result)
} else {
(&result as *const usize).cast::<R>().read()
}
}
} else {
let mut result = mem::MaybeUninit::<R>::uninit();
unsafe {
raw::ffi_call(
cif,
Some(*fun.as_safe_fun()),
result.as_mut_ptr().cast::<c_void>(),
args,
);
result.assume_init()
}
}
}
unsafe fn call_return_small_big_endian_result<R>(type_tag: u16, result: *const usize) -> R {
if type_tag == raw::FFI_TYPE_FLOAT as u16
|| type_tag == raw::FFI_TYPE_STRUCT as u16
|| type_tag == raw::FFI_TYPE_VOID as u16
{
unsafe { result.cast::<R>().read() }
} else {
unsafe {
result
.cast::<R>()
.add((mem::size_of::<usize>() / mem::size_of::<R>()) - 1)
.read()
}
}
}
pub fn closure_alloc() -> (*mut ffi_closure, CodePtr) {
unsafe {
let mut code_pointer = mem::MaybeUninit::<*mut c_void>::uninit();
let closure =
raw::ffi_closure_alloc(mem::size_of::<ffi_closure>(), code_pointer.as_mut_ptr());
(
closure as *mut ffi_closure,
CodePtr::from_ptr(code_pointer.assume_init()),
)
}
}
pub unsafe fn closure_free(closure: *mut ffi_closure) {
raw::ffi_closure_free(closure as *mut c_void);
}
pub type Callback<U, R> =
unsafe extern "C" fn(cif: &ffi_cif, result: &mut R, args: *const *const c_void, userdata: &U);
pub type CallbackMut<U, R> = unsafe extern "C" fn(
cif: &ffi_cif,
result: &mut R,
args: *const *const c_void,
userdata: &mut U,
);
pub type RawCallback = unsafe extern "C" fn(
cif: *mut ffi_cif,
result: *mut c_void,
args: *mut *mut c_void,
userdata: *mut c_void,
);
pub unsafe fn prep_closure<U, R>(
closure: *mut ffi_closure,
cif: *mut ffi_cif,
callback: Callback<U, R>,
userdata: *const U,
code: CodePtr,
) -> Result<()> {
let status = raw::ffi_prep_closure_loc(
closure,
cif,
Some(mem::transmute::<Callback<U, R>, RawCallback>(callback)),
userdata as *mut c_void,
code.as_mut_ptr(),
);
status_to_result(status, ())
}
pub unsafe fn prep_closure_mut<U, R>(
closure: *mut ffi_closure,
cif: *mut ffi_cif,
callback: CallbackMut<U, R>,
userdata: *mut U,
code: CodePtr,
) -> Result<()> {
let status = raw::ffi_prep_closure_loc(
closure,
cif,
Some(mem::transmute::<CallbackMut<U, R>, RawCallback>(callback)),
userdata as *mut c_void,
code.as_mut_ptr(),
);
status_to_result(status, ())
}
#[cfg(test)]
mod test {
use std::ptr::{addr_of_mut, null_mut};
use super::*;
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct SmallStruct(u8, u16);
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct LargeStruct(u64, u64, u64, u64);
extern "C" fn return_nothing() {}
extern "C" fn return_i8(a: i8) -> i8 {
a
}
extern "C" fn return_u8(a: u8) -> u8 {
a
}
extern "C" fn return_i16(a: i16) -> i16 {
a
}
extern "C" fn return_u16(a: u16) -> u16 {
a
}
extern "C" fn return_i32(a: i32) -> i32 {
a
}
extern "C" fn return_u32(a: u32) -> u32 {
a
}
extern "C" fn return_i64(a: i64) -> i64 {
a
}
extern "C" fn return_u64(a: u64) -> u64 {
a
}
extern "C" fn return_pointer(a: *const c_void) -> *const c_void {
a
}
extern "C" fn return_f32(a: f32) -> f32 {
a
}
extern "C" fn return_f64(a: f64) -> f64 {
a
}
extern "C" fn return_small_struct(a: SmallStruct) -> SmallStruct {
a
}
extern "C" fn return_large_struct(a: LargeStruct) -> LargeStruct {
a
}
macro_rules! test_return_value {
($ty:ty, $ffitype:expr, $val:expr, $fn:ident) => {{
let mut cif = ffi_cif::default();
let mut arg_ty_array: [*mut ffi_type; 1] = [addr_of_mut!($ffitype)];
let mut arg: $ty = $val;
let mut arg_array: [*mut c_void; 1] = [addr_of_mut!(arg).cast()];
prep_cif(
&mut cif,
ffi_abi_FFI_DEFAULT_ABI,
1,
addr_of_mut!($ffitype),
arg_ty_array.as_mut_ptr(),
)
.unwrap();
let result: $ty = call(&mut cif, CodePtr($fn as *mut _), arg_array.as_mut_ptr());
assert_eq!(result, $val);
}};
}
#[test]
fn test_return_values() {
{
let mut cif = ffi_cif::default();
unsafe {
prep_cif(
&mut cif,
ffi_abi_FFI_DEFAULT_ABI,
0,
addr_of_mut!(types::void),
null_mut(),
)
.unwrap();
call::<()>(&mut cif, CodePtr(return_nothing as *mut _), null_mut());
}
}
unsafe {
test_return_value!(i8, types::sint8, 0x55, return_i8);
test_return_value!(u8, types::uint8, 0xAA, return_u8);
test_return_value!(i16, types::sint16, 0x5555, return_i16);
test_return_value!(u16, types::uint16, 0xAAAA, return_u16);
test_return_value!(i32, types::sint32, 0x5555_5555, return_i32);
test_return_value!(u32, types::uint32, 0xAAAA_AAAA, return_u32);
test_return_value!(i64, types::sint64, 0x5555_5555_5555_5555, return_i64);
test_return_value!(u64, types::uint64, 0xAAAA_AAAA_AAAA_AAAA, return_u64);
test_return_value!(f32, types::float, core::f32::consts::E, return_f32);
test_return_value!(f64, types::double, core::f64::consts::PI, return_f64);
let mut dummy = 0;
test_return_value!(
*const c_void,
types::pointer,
addr_of_mut!(dummy).cast(),
return_pointer
);
let mut small_struct_elements = [
addr_of_mut!(types::uint8),
addr_of_mut!(types::uint16),
null_mut(),
];
let mut small_struct_type = ffi_type {
type_: type_tag::STRUCT,
elements: small_struct_elements.as_mut_ptr(),
..Default::default()
};
test_return_value!(
SmallStruct,
small_struct_type,
SmallStruct(0xAA, 0x5555),
return_small_struct
);
let mut large_struct_elements = [
addr_of_mut!(types::uint64),
addr_of_mut!(types::uint64),
addr_of_mut!(types::uint64),
addr_of_mut!(types::uint64),
null_mut(),
];
let mut large_struct_type = ffi_type {
type_: type_tag::STRUCT,
elements: large_struct_elements.as_mut_ptr(),
..Default::default()
};
test_return_value!(
LargeStruct,
large_struct_type,
LargeStruct(
0x1234_5678_9abc_def0,
0x0fed_cba9_8765_4321,
0x5555_5555_5555_5555,
0xAAAA_AAAA_AAAA_AAAA,
),
return_large_struct
);
}
}
}