use core::ffi::c_void;
use core::sync::atomic::{AtomicUsize, Ordering};
use azul_css::AzString;
use crate::refany::RefAny;
pub const AZ_HOST_HANDLE_RTTI_ID: u64 = 0xA20A_4853_5448_5F44;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct HostHandlePayload {
pub id: u64,
}
#[repr(C)]
#[derive(Debug)]
pub struct InvokerSlot {
fn_ptr: AtomicUsize,
}
impl InvokerSlot {
#[must_use]
pub const fn new() -> Self {
Self {
fn_ptr: AtomicUsize::new(0),
}
}
pub fn set(&self, ptr: usize) {
self.fn_ptr.store(ptr, Ordering::SeqCst);
}
pub fn get(&self) -> usize {
self.fn_ptr.load(Ordering::SeqCst)
}
}
impl Default for InvokerSlot {
fn default() -> Self {
Self::new()
}
}
pub static HOST_HANDLE_RELEASER: InvokerSlot = InvokerSlot::new();
pub static GENERIC_INVOKER: InvokerSlot = InvokerSlot::new();
pub type AzGenericInvoker = extern "C" fn(
handle: u64,
kind: *const core::ffi::c_char,
args: *const *const c_void,
n_args: usize,
ret: *mut c_void,
);
#[no_mangle]
pub extern "C" fn AzApp_setGenericInvoker(invoker: AzGenericInvoker) {
GENERIC_INVOKER.set(invoker as usize);
}
#[no_mangle]
pub extern "C" fn AzApp_setHostHandleReleaser(releaser: extern "C" fn(u64)) {
HOST_HANDLE_RELEASER.set(releaser as usize);
}
extern "C" fn host_handle_destructor(ptr: *mut c_void) {
if ptr.is_null() {
return;
}
let payload = unsafe { &*(ptr as *const HostHandlePayload) };
let releaser_addr = HOST_HANDLE_RELEASER.get();
if releaser_addr == 0 {
return;
}
let releaser: extern "C" fn(u64) = unsafe { core::mem::transmute(releaser_addr) };
#[cfg(feature = "std")]
{
drop(std::panic::catch_unwind(std::panic::AssertUnwindSafe(
|| releaser(payload.id),
)));
}
#[cfg(not(feature = "std"))]
{
releaser(payload.id);
}
}
pub fn host_handle_to_refany(id: u64) -> RefAny {
let payload = HostHandlePayload { id };
let type_name: AzString = "AzHostHandle".into();
RefAny::new_c(
&raw const payload as *const c_void,
size_of::<HostHandlePayload>(),
align_of::<HostHandlePayload>(),
AZ_HOST_HANDLE_RTTI_ID,
type_name,
host_handle_destructor,
0,
0,
)
}
#[must_use]
pub fn refany_to_host_handle(refany: &RefAny) -> Option<u64> {
if !refany.is_type(AZ_HOST_HANDLE_RTTI_ID) {
return None;
}
let ptr = refany.get_data_ptr() as *const HostHandlePayload;
if ptr.is_null() {
return None;
}
Some(unsafe { (*ptr).id })
}
#[no_mangle]
pub extern "C" fn AzRefAny_newHostHandle(id: u64) -> RefAny {
host_handle_to_refany(id)
}
#[no_mangle]
#[allow(clippy::not_unsafe_ptr_arg_deref)] pub extern "C" fn AzRefAny_getHostHandle(refany: *const RefAny) -> u64 {
if refany.is_null() {
return 0;
}
let r = unsafe { &*refany };
refany_to_host_handle(r).unwrap_or(0)
}
#[macro_export]
macro_rules! impl_managed_callback {
(
wrapper: $wrapper:ty,
info_ty: $info_ty:ty,
return_ty: $ret:ty,
default_ret: $default:expr,
invoker_static: $invoker_static:ident,
invoker_ty: $invoker_ty:ident,
thunk_fn: $thunk_fn:ident,
setter_fn: $setter_fn:ident,
from_handle_fn: $from_handle_fn:ident,
) => {
$crate::impl_managed_callback! {
wrapper: $wrapper,
info_ty: $info_ty,
return_ty: $ret,
default_ret: $default,
invoker_static: $invoker_static,
invoker_ty: $invoker_ty,
thunk_fn: $thunk_fn,
setter_fn: $setter_fn,
from_handle_fn: $from_handle_fn,
extra_args: [],
}
};
(
wrapper: $wrapper:ty,
info_ty: $info_ty:ty,
return_ty: $ret:ty,
default_ret: $default:expr,
invoker_static: $invoker_static:ident,
invoker_ty: $invoker_ty:ident,
thunk_fn: $thunk_fn:ident,
setter_fn: $setter_fn:ident,
from_handle_fn: $from_handle_fn:ident,
extra_args: [ $( $extra_name:ident : $extra_ty:ty ),* $(,)? ] $(,)?
) => {
pub static $invoker_static: $crate::host_invoker::InvokerSlot =
$crate::host_invoker::InvokerSlot::new();
pub type $invoker_ty = extern "C" fn(
handle: u64,
data: *const $crate::refany::RefAny,
info: *const $info_ty,
$( $extra_name : *const $extra_ty , )*
out: *mut $ret,
);
#[no_mangle]
pub extern "C" fn $setter_fn(invoker: $invoker_ty) {
$invoker_static.set(invoker as usize);
}
extern "C" fn $thunk_fn(
data: $crate::refany::RefAny,
info: $info_ty,
$( $extra_name : $extra_ty , )*
) -> $ret {
const KIND_STR: &str = concat!(stringify!($wrapper), "\0");
let body = move || -> $ret {
let ctx = info.get_ctx();
let handle = match ctx {
$crate::refany::OptionRefAny::Some(ref refany) => {
match $crate::host_invoker::refany_to_host_handle(refany) {
Some(id) => id,
None => return $default,
}
}
_ => return $default,
};
let invoker_addr = $invoker_static.get();
if invoker_addr == 0 {
let generic_addr = $crate::host_invoker::GENERIC_INVOKER.get();
if generic_addr == 0 {
return $default;
}
let generic: $crate::host_invoker::AzGenericInvoker =
unsafe { core::mem::transmute(generic_addr) };
let args = [
&raw const data as *const core::ffi::c_void,
&raw const info as *const core::ffi::c_void,
$( & $extra_name as *const _ as *const core::ffi::c_void , )*
];
let mut out: $ret = $default;
generic(
handle,
KIND_STR.as_ptr() as *const core::ffi::c_char,
args.as_ptr(),
args.len(),
&raw mut out as *mut core::ffi::c_void,
);
return out;
}
let invoker: $invoker_ty = unsafe { core::mem::transmute(invoker_addr) };
let mut out: $ret = $default;
invoker(
handle,
&raw const data,
&raw const info,
$( & $extra_name as *const $extra_ty , )*
&raw mut out,
);
out
};
#[cfg(feature = "std")]
{
std::panic::catch_unwind(std::panic::AssertUnwindSafe(body))
.unwrap_or($default)
}
#[cfg(not(feature = "std"))]
{
body()
}
}
impl $wrapper {
#[must_use] pub fn create_from_host_handle(handle: u64) -> Self {
Self {
cb: $thunk_fn,
ctx: $crate::refany::OptionRefAny::Some(
$crate::host_invoker::host_handle_to_refany(handle),
),
}
}
}
#[no_mangle]
pub extern "C" fn $from_handle_fn(handle: u64) -> $wrapper {
<$wrapper>::create_from_host_handle(handle)
}
};
}
#[cfg(all(test, feature = "std"))]
#[allow(
clippy::items_after_statements,
clippy::redundant_clone,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
trivial_casts,
clippy::borrow_as_ptr,
clippy::cast_ptr_alignment,
clippy::unused_self,
unused_qualifications,
unreachable_pub,
private_interfaces
)] mod tests {
use core::sync::atomic::{AtomicU64, Ordering as AtOrdering};
use std::sync::Mutex;
use super::*;
pub(super) static TEST_LOCK: Mutex<()> = Mutex::new(());
static LAST_RELEASED: AtomicU64 = AtomicU64::new(0);
extern "C" fn recording_releaser(id: u64) {
LAST_RELEASED.store(id, AtOrdering::SeqCst);
}
#[test]
fn destructor_transmutes_and_invokes_releaser() {
let _g = TEST_LOCK.lock().unwrap();
LAST_RELEASED.store(0, AtOrdering::SeqCst);
AzApp_setHostHandleReleaser(recording_releaser);
let mut payload = HostHandlePayload { id: 0xABCD_1234 };
host_handle_destructor((&raw mut payload).cast::<c_void>());
assert_eq!(LAST_RELEASED.load(AtOrdering::SeqCst), 0xABCD_1234);
HOST_HANDLE_RELEASER.set(0);
}
#[test]
fn destructor_null_ptr_is_noop() {
host_handle_destructor(core::ptr::null_mut());
}
#[test]
fn host_handle_roundtrips_through_refany() {
let _g = TEST_LOCK.lock().unwrap();
HOST_HANDLE_RELEASER.set(0);
let refany = host_handle_to_refany(0x55);
assert_eq!(refany_to_host_handle(&refany), Some(0x55));
}
#[derive(PartialEq, Debug)]
struct FakeRet(u32);
struct FakeInfo;
impl FakeInfo {
fn get_ctx(&self) -> crate::refany::OptionRefAny {
panic!("boom from get_ctx");
}
}
struct FakeWrapper {
#[allow(dead_code)]
cb: extern "C" fn(crate::refany::RefAny, FakeInfo) -> FakeRet,
#[allow(dead_code)]
ctx: crate::refany::OptionRefAny,
}
crate::impl_managed_callback! {
wrapper: FakeWrapper,
info_ty: FakeInfo,
return_ty: FakeRet,
default_ret: FakeRet(99),
invoker_static: AZ_TEST_FAKE_INVOKER,
invoker_ty: AzTestFakeInvoker,
thunk_fn: az_test_fake_thunk,
setter_fn: az_test_fake_set_invoker,
from_handle_fn: az_test_fake_from_handle,
}
#[test]
fn thunk_contains_panic_and_returns_default() {
let _g = TEST_LOCK.lock().unwrap();
HOST_HANDLE_RELEASER.set(0);
let data = host_handle_to_refany(1);
let out = az_test_fake_thunk(data, FakeInfo);
assert_eq!(out, FakeRet(99));
}
}
#[cfg(test)]
#[path = "host_invoker_test.rs"]
mod host_invoker_test;