256 lines
7.2 KiB
Rust
256 lines
7.2 KiB
Rust
//! 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<Option<MizeError>> = 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<MizeError> {
|
|
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::<NativePtr>() {
|
|
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));
|
|
});
|
|
}
|