//! C ABI for dynamically loaded parts (see `include/mize.h`). //! //! Names passed to `opt` / `method` / `thread` / `get_config` / `set_config` //! are absolute dotted paths (e.g. `"vicinae.open"`). //! //! Errors use a thread-local slot (like errno): C code calls `set_err(...)`; //! Rust checks via [`take_err`] after the call. use std::cell::RefCell; use std::ffi::{c_char, c_void, CStr}; use std::ptr; use crate::config::{ConfigOpt, MizeFun}; use crate::error::MizeError; use crate::get_global_mize; use crate::item::ItemData; use crate::mize_err; use crate::write_to_mize_stdout; type CFun = unsafe extern "C" fn(*mut ItemData) -> *mut ItemData; thread_local! { /// Per-thread error set by C via `set_err`, consumed by Rust after the call. static CUR_ERR: RefCell> = const { RefCell::new(None) }; } /// Clear the thread-local C error (call before invoking a C part entry / callback). pub fn clear_err() { CUR_ERR.with(|slot| { *slot.borrow_mut() = None; }); } /// Take the thread-local C error, if any. pub fn take_err() -> Option { CUR_ERR.with(|slot| slot.borrow_mut().take()) } /// Opaque pointer stored via `data_from_native` / read via `data_as_native`. struct NativePtr(*mut c_void); // SAFETY: the C++ part is responsible for only using the pointer on the right thread. unsafe impl Send for NativePtr {} unsafe impl Sync for NativePtr {} fn cstr<'a>(p: *const c_char) -> Option<&'a str> { if p.is_null() { return None; } unsafe { CStr::from_ptr(p) }.to_str().ok() } fn wrap_c_fun(c_fun: CFun) -> MizeFun { Box::new(move |args| { clear_err(); let mut args_box = Box::new(args); let ret_ptr = unsafe { c_fun(args_box.as_mut()) }; drop(args_box); if let Some(err) = take_err() { if !ret_ptr.is_null() { unsafe { drop(Box::from_raw(ret_ptr)) }; } return Err(err); } if ret_ptr.is_null() { return Ok(ItemData::new()); } Ok(*unsafe { Box::from_raw(ret_ptr) }) }) } fn register_fun(name: &str, kind: &str, signature: &str, fun: MizeFun) { let mut mize = match get_global_mize() { Ok(m) => m, Err(e) => { let _ = write_to_mize_stdout(format_args!("[c_api] get_global_mize failed: {e:?}\n")); return; } }; let id = match mize.id_new_dots(name) { Ok(id) => id, Err(e) => { let _ = write_to_mize_stdout(format_args!("[c_api] bad id '{name}': {e:?}\n")); return; } }; if mize.is_generate_mode().unwrap_or(false) { let short = name.rsplit('.').next().unwrap_or(name); let _ = mize.record_method(short, id.clone(), kind, signature); return; } let opt = ConfigOpt { name: id.dot(), val: None, fun: None, deps: Vec::new(), }; mize.config_opts.lock().unwrap().insert(id.clone(), opt); mize.fun_values.lock().unwrap().insert(id, fun); } #[unsafe(no_mangle)] pub extern "C" fn opt(name: *const c_char) { let Some(name) = cstr(name) else { return }; let mut mize = match get_global_mize() { Ok(m) => m, Err(_) => return, }; let Ok(id) = mize.id_new_dots(name) else { return }; if mize.is_generate_mode().unwrap_or(false) { let short = name.rsplit('.').next().unwrap_or(name); let _ = mize.record_opt(short, id.clone(), None); return; } let opt = ConfigOpt { name: id.dot(), val: None, fun: None, deps: Vec::new(), }; mize.config_opts.lock().unwrap().insert(id, opt); } #[unsafe(no_mangle)] pub extern "C" fn method(name: *const c_char, signature: *const c_char, c_fun: CFun) { let Some(name) = cstr(name) else { return }; let sig = cstr(signature).unwrap_or(""); register_fun(name, "method", sig, wrap_c_fun(c_fun)); } #[unsafe(no_mangle)] pub extern "C" fn effect(name: *const c_char, c_fun: CFun) { let Some(name) = cstr(name) else { return }; register_fun(name, "effect", "", wrap_c_fun(c_fun)); } #[unsafe(no_mangle)] pub extern "C" fn apply(name: *const c_char, c_fun: CFun) { let Some(name) = cstr(name) else { return }; register_fun(name, "apply", "", wrap_c_fun(c_fun)); } #[unsafe(no_mangle)] pub extern "C" fn thread(name: *const c_char, c_fun: CFun) { let Some(name) = cstr(name) else { return }; register_fun(name, "thread", "", wrap_c_fun(c_fun)); } #[unsafe(no_mangle)] pub extern "C" fn set_config(name: *const c_char, value: *mut ItemData) { if value.is_null() { return; } let Some(name) = cstr(name) else { unsafe { drop(Box::from_raw(value)) }; return; }; let value = unsafe { *Box::from_raw(value) }; let mut mize = match get_global_mize() { Ok(m) => m, Err(_) => return, }; let Ok(id) = mize.id_new_dots(name) else { return }; let mut opts = mize.config_opts.lock().unwrap(); match opts.get_mut(&id) { Some(opt) => { opt.val = Some(value); } None => { opts.insert( id.clone(), ConfigOpt { name: id.dot(), val: Some(value), fun: None, deps: Vec::new(), }, ); } } } #[unsafe(no_mangle)] pub extern "C" fn get_config(name: *const c_char) -> *mut ItemData { let Some(name) = cstr(name) else { return ptr::null_mut(); }; let mut mize = match get_global_mize() { Ok(m) => m, Err(_) => return ptr::null_mut(), }; match mize.get_config(name) { Ok(data) => Box::into_raw(Box::new(data)), Err(_) => ptr::null_mut(), } } #[unsafe(no_mangle)] pub extern "C" fn data_new() -> *mut ItemData { Box::into_raw(Box::new(ItemData::new())) } #[unsafe(no_mangle)] pub extern "C" fn data_from_native(ptr: *mut c_void) -> *mut ItemData { match ItemData::from_native(NativePtr(ptr)) { Ok(data) => Box::into_raw(Box::new(data)), Err(e) => { let _ = write_to_mize_stdout(format_args!("[c_api] data_from_native: {e:?}\n")); ptr::null_mut() } } } #[unsafe(no_mangle)] pub extern "C" fn data_as_native(data: *mut ItemData) -> *mut c_void { if data.is_null() { return ptr::null_mut(); } let data = unsafe { &*data }; match data.native::() { Ok(guard) => guard.0, Err(e) => { let _ = write_to_mize_stdout(format_args!("[c_api] data_as_native: {e:?}\n")); ptr::null_mut() } } } #[unsafe(no_mangle)] pub extern "C" fn data_free(data: *mut ItemData) { if !data.is_null() { unsafe { drop(Box::from_raw(data)) }; } } #[unsafe(no_mangle)] pub extern "C" fn mize_write_stdout(str_ptr: *const c_char) { let Some(s) = cstr(str_ptr) else { return }; let _ = write_to_mize_stdout(format_args!("{s}")); } /// Set the thread-local error (like errno). `load_part` / C-callback wrappers turn this into `Err`. #[unsafe(no_mangle)] pub extern "C" fn set_err(msg: *const c_char) { let text = cstr(msg).unwrap_or("set_err called with null/invalid message"); CUR_ERR.with(|slot| { *slot.borrow_mut() = Some(mize_err!("{}", text)); }); }