use std::ffi::c_void;
use std::fmt;
pub type DestroyFn = unsafe extern "C" fn(*mut c_void);
pub struct OpaqueData {
ptr: *mut c_void,
destroy_fn: Option<DestroyFn>,
size: usize,
type_id: &'static str,
}
unsafe impl Send for OpaqueData {}
unsafe impl Sync for OpaqueData {}
impl OpaqueData {
pub unsafe fn new(
ptr: *mut c_void,
destroy_fn: DestroyFn,
size: usize,
type_id: &'static str,
) -> Self {
Self { ptr, destroy_fn: Some(destroy_fn), size, type_id }
}
pub unsafe fn from_raw(ptr: *mut c_void, size: usize, type_id: &'static str) -> Self {
Self { ptr, destroy_fn: None, size, type_id }
}
pub fn as_ptr(&self) -> *const c_void {
self.ptr
}
pub fn as_mut_ptr(&mut self) -> *mut c_void {
self.ptr
}
pub fn size(&self) -> usize {
self.size
}
pub fn type_id(&self) -> &'static str {
self.type_id
}
pub fn is_null(&self) -> bool {
self.ptr.is_null()
}
pub unsafe fn cast_ref<T>(&self) -> &T {
unsafe { &*(self.ptr as *const T) }
}
pub unsafe fn cast_mut<T>(&mut self) -> &mut T {
unsafe { &mut *(self.ptr as *mut T) }
}
}
impl Drop for OpaqueData {
fn drop(&mut self) {
if !self.ptr.is_null()
&& let Some(destroy) = self.destroy_fn
{
unsafe {
destroy(self.ptr);
}
}
}
}
impl fmt::Debug for OpaqueData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OpaqueData")
.field("ptr", &self.ptr)
.field("size", &self.size)
.field("type_id", &self.type_id)
.field("has_destroy", &self.destroy_fn.is_some())
.finish()
}
}
pub struct TypedOpaque<T> {
inner: OpaqueData,
_marker: std::marker::PhantomData<T>,
}
impl<T> TypedOpaque<T> {
pub fn from_opaque(data: OpaqueData) -> Self {
Self { inner: data, _marker: std::marker::PhantomData }
}
pub fn into_inner(self) -> OpaqueData {
self.inner
}
pub fn inner(&self) -> &OpaqueData {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut OpaqueData {
&mut self.inner
}
}
impl<T> fmt::Debug for TypedOpaque<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn roplat_opaque_create(
ptr: *mut c_void,
destroy_fn: DestroyFn,
size: usize,
type_id: *const std::ffi::c_char,
) -> *mut OpaqueData {
let type_id_str = if type_id.is_null() {
"unknown"
} else {
let c_str = unsafe { std::ffi::CStr::from_ptr(type_id) };
let owned = c_str.to_string_lossy().into_owned();
Box::leak(owned.into_boxed_str())
};
let opaque = unsafe { OpaqueData::new(ptr, destroy_fn, size, type_id_str) };
Box::into_raw(Box::new(opaque))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn roplat_opaque_destroy(opaque: *mut OpaqueData) {
if !opaque.is_null() {
unsafe {
drop(Box::from_raw(opaque));
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn roplat_opaque_get_ptr(opaque: *const OpaqueData) -> *mut c_void {
if opaque.is_null() {
std::ptr::null_mut()
} else {
unsafe { (*opaque).ptr }
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn roplat_opaque_get_size(opaque: *const OpaqueData) -> usize {
if opaque.is_null() {
0
} else {
unsafe { (*opaque).size }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_opaque_data_lifecycle() {
use std::sync::atomic::{AtomicBool, Ordering};
static DESTROYED: AtomicBool = AtomicBool::new(false);
unsafe extern "C" fn my_destroy(ptr: *mut c_void) {
unsafe {
let _ = Box::from_raw(ptr as *mut i32);
}
DESTROYED.store(true, Ordering::SeqCst);
}
{
let data = Box::into_raw(Box::new(42i32));
let opaque = unsafe { OpaqueData::new(data as *mut c_void, my_destroy, 4, "test_i32") };
assert_eq!(opaque.type_id(), "test_i32");
assert_eq!(opaque.size(), 4);
assert!(!opaque.is_null());
unsafe {
assert_eq!(*opaque.cast_ref::<i32>(), 42);
}
}
assert!(DESTROYED.load(Ordering::SeqCst));
}
#[test]
fn test_typed_opaque() {
struct MyMarker;
let data = Box::into_raw(Box::new(vec![1, 2, 3]));
unsafe extern "C" fn destroy_vec(ptr: *mut c_void) {
unsafe {
let _ = Box::from_raw(ptr as *mut Vec<i32>);
}
}
let opaque = unsafe {
OpaqueData::new(
data as *mut c_void,
destroy_vec,
std::mem::size_of::<Vec<i32>>(),
"Vec<i32>",
)
};
let typed = TypedOpaque::<MyMarker>::from_opaque(opaque);
assert_eq!(typed.inner().type_id(), "Vec<i32>");
unsafe {
let v = typed.inner().cast_ref::<Vec<i32>>();
assert_eq!(v.as_slice(), &[1, 2, 3]);
}
}
#[test]
fn test_ffi_interface() {
use std::sync::atomic::{AtomicBool, Ordering};
static FFI_DESTROYED: AtomicBool = AtomicBool::new(false);
unsafe extern "C" fn ffi_destroy(ptr: *mut c_void) {
unsafe {
let _ = Box::from_raw(ptr as *mut f64);
}
FFI_DESTROYED.store(true, Ordering::SeqCst);
}
let value = Box::into_raw(Box::new(std::f64::consts::PI));
let type_id = std::ffi::CString::new("f64_value").unwrap();
unsafe {
let opaque =
roplat_opaque_create(value as *mut c_void, ffi_destroy, 8, type_id.as_ptr());
assert!(!opaque.is_null());
assert_eq!(roplat_opaque_get_size(opaque), 8);
let ptr = roplat_opaque_get_ptr(opaque);
assert_eq!(*(ptr as *const f64), std::f64::consts::PI);
roplat_opaque_destroy(opaque);
}
assert!(FFI_DESTROYED.load(Ordering::SeqCst));
}
}