From 5ba64953bb3b743d71ac8a2d91d47100802f4a8e Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Wed, 29 Jul 2026 15:22:16 +0800 Subject: [PATCH] feat: move wfassoc code here --- omrf/Cargo.lock | 74 +++++++++++++++++++ omrf/Cargo.toml | 2 + omrf/src/cstr_ffi.rs | 103 +++++++++++++++++++++++++++ omrf/src/last_error.rs | 58 +++++++++++++++ omrf/src/lib.rs | 152 +++++++++++++++++++++++++++++++++++++--- omrf/src/object_pool.rs | 95 +++++++++++++++++++++++++ 6 files changed, 473 insertions(+), 11 deletions(-) create mode 100644 omrf/src/cstr_ffi.rs create mode 100644 omrf/src/last_error.rs create mode 100644 omrf/src/object_pool.rs diff --git a/omrf/Cargo.lock b/omrf/Cargo.lock index e7e93af..b38c0e7 100644 --- a/omrf/Cargo.lock +++ b/omrf/Cargo.lock @@ -2,6 +2,80 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + [[package]] name = "sarasacw-omrf" version = "0.1.0" +dependencies = [ + "slotmap", + "thiserror", +] + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" diff --git a/omrf/Cargo.toml b/omrf/Cargo.toml index f455867..fd6a000 100644 --- a/omrf/Cargo.toml +++ b/omrf/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +thiserror = "2.0.12" +slotmap = "1.1.1" diff --git a/omrf/src/cstr_ffi.rs b/omrf/src/cstr_ffi.rs new file mode 100644 index 0000000..3bcfadc --- /dev/null +++ b/omrf/src/cstr_ffi.rs @@ -0,0 +1,103 @@ +//! When calling this dynamic library with outside programs, +//! outer programs may usually need to fetch string resource produced by Rust code. +//! However it is impossible pass Rust string directly to outer program. +//! +//! This module provide **thread independent** string cache for resolving this issue. +//! When we need pass string to outer programs, we push that string into this string as C-like format, +//! then return its pointer to outer program. +//! So that outside program can utilize it like calling C/C++ library. +//! The only thing that outer programs should note is that this string is volatile, +//! once they get it, they must dupliate it immediately before any futher calling to this dynamic library. +use std::cell::RefCell; +use std::ffi::{c_char, CStr, CString}; +use thiserror::Error as TeError; + +/// The type representing the raw pointer to immutable C-style NUL-terminated string. +pub type CStyleString = *const c_char; + +// region: Error + +/// Error occurs in this crate. +#[derive(Debug, TeError)] +pub enum Error { + #[error("unexpected NUL when parsing into C/C++ string")] + UnexpectedNul(#[from] std::ffi::NulError), + + #[error("given string pointer is nullptr when parsing from C/C++ string")] + NullPtr, + #[error("invalid UTF8 sequence when parsing from C/C++ string")] + InvalidEncoding(#[from] std::str::Utf8Error), +} + +/// Result type used in this crate. +type Result = std::result::Result; + +// endregion + +// region: String Cache for Exposing + +struct StringCache { + msg: CString, +} + +impl StringCache { + fn new() -> Self { + Self { + msg: CString::new("").expect("empty string must be valid for CString"), + } + } + + pub fn set_msg(&mut self, msg: &str) -> Result<()> { + self.msg = CString::new(msg)?; + Ok(()) + } + + pub fn get_msg(&self) -> CStyleString { + self.msg.as_ptr() + } + + pub fn clear_msg(&mut self) { + self.msg = CString::new("").expect("empty string must be valid for CString"); + } +} + +// endregion + +// region: Exposed Functions + +thread_local! { + static STRING_CACHE: RefCell = RefCell::new(StringCache::new()); +} + +/// Set thread local string exposed for C code. +pub fn set_ffi_string(msg: &str) -> Result<()> { + STRING_CACHE.with(|e| { + e.borrow_mut().set_msg(msg) + }) +} + +/// Get const pointer to thread local string exposed for C code. +pub fn get_ffi_string() -> CStyleString { + STRING_CACHE.with(|e| e.borrow().get_msg()) +} + +/// Clear thread local string exposed for C code. +/// +/// This function usually should be called at the beginning of every exposed C functions. +pub fn clear_ffi_string() { + STRING_CACHE.with(|e| { + e.borrow_mut().clear_msg(); + }); +} + +/// Parse string given by C code into Rust string. +pub fn parse_ffi_string<'a>(ptr: CStyleString) -> Result<&'a str> { + if ptr.is_null() { + Err(Error::NullPtr) + } else { + let c_str = unsafe { CStr::from_ptr(ptr) }; + Ok(c_str.to_str()?) + } +} + +// endregion diff --git a/omrf/src/last_error.rs b/omrf/src/last_error.rs new file mode 100644 index 0000000..6f1997b --- /dev/null +++ b/omrf/src/last_error.rs @@ -0,0 +1,58 @@ +//! When calling function with dynamic library, +//! function return value indicates whether function has been successfully executed. +//! When function return `false`, programmer may want to know which error occurs. +//! +//! This module provide **thread independent** error message string storage, +//! which is more like Win32 `GetLastError()` but return error message instead of error code. +//! These module provided functions will be called when executing main module functions. + +use std::cell::RefCell; +use std::ffi::{CString, c_char}; + +struct LastError { + msg: CString, +} + +impl LastError { + fn new() -> Self { + Self { + msg: CString::new("").expect("empty string must be valid for CString"), + } + } + + pub fn set_msg(&mut self, msg: &str) { + self.msg = CString::new(msg).expect("unexpected blank in error message output"); + } + + pub fn get_msg(&self) -> *const c_char { + self.msg.as_ptr() + } + + pub fn clear_msg(&mut self) { + self.msg = CString::new("").expect("empty string must be valid for CString"); + } +} + +thread_local! { + static LAST_ERROR: RefCell = RefCell::new(LastError::new()); +} + +/// Set thread local error message. +pub fn set_last_error(msg: &str) { + LAST_ERROR.with(|e| { + e.borrow_mut().set_msg(msg); + }); +} + +/// Get const pointer to thread local error message string. +/// If there is no error, return pointer will point to empty string. +pub fn get_last_error() -> *const c_char { + LAST_ERROR.with(|e| e.borrow().get_msg()) +} + +/// Clear thread local error message (reset to empty string). +pub fn clear_last_error() { + LAST_ERROR.with(|e| { + e.borrow_mut().clear_msg(); + }); +} diff --git a/omrf/src/lib.rs b/omrf/src/lib.rs index b93cf3f..7f0059c 100644 --- a/omrf/src/lib.rs +++ b/omrf/src/lib.rs @@ -1,14 +1,144 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right +pub mod cstr_ffi; +pub mod last_error; +pub mod object_pool; + +#[macro_export] +macro_rules! in_param_ty { + ($t:ty) => { + $t + }; } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } +#[macro_export] +macro_rules! out_param_ty { + ($t:ty) => { + *mut $t + }; } + +#[macro_export] +macro_rules! set_out_param { + ($lhs:expr, $rhs:expr) => { + unsafe { *$lhs = $rhs }; + }; +} + +#[macro_export] +macro_rules! resolve_enum { + ($t:ty, $v:expr) => { + <$t>::try_from($v).map_err(|_| Error::EnumOutOfRange) + }; +} + +#[macro_export] +macro_rules! pull_reader { + ($pool:expr) => { + $pool.read().map_err(|_| Error::PoisonRwLock) + }; +} + +#[macro_export] +macro_rules! pull_writer { + ($pool:expr) => { + $pool.write().map_err(|_| Error::PoisonRwLock) + }; +} + +/// Macro to wrap inner function execution with standard error handling pattern. +/// +/// For functions with no output parameter and no input parameters: +/// ```ignore +/// cffi_wrapper!(|| { +/// // inner function body returning Result<()> +/// }); +/// ``` +/// +/// For functions with no output parameter and with input parameters: +/// ```ignore +/// cffi_wrapper!(|param1: Type1, param2: Type2| { +/// // inner function body using param1, param2 returning Result<()> +/// }); +/// ``` +/// +/// For functions with one output parameter and no input parameters: +/// ```ignore +/// cffi_wrapper!(|| -> (out_param, OutType) { +/// // inner function body returning Result +/// }); +/// ``` +/// +/// For functions with one output parameter and with input parameters: +/// ```ignore +/// cffi_wrapper!(|param1: Type1| -> (out_param, OutType) { +/// // inner function body using param1 returning Result +/// }); +/// ``` +#[macro_export] +macro_rules! cffi_wrapper { + // Case with output parameter and input parameters + (|$($param:ident: $param_ty:ty),*| -> ($out_param:ident: $out_param_ty:ty) $inner_body:block) => {{ + fn inner($($param: $param_ty),*) -> Result<$out_param_ty> $inner_body + + match inner($($param),*) { + Ok(rv) => { + set_out_param!($out_param, rv); + last_error::clear_last_error(); + true + } + Err(e) => { + last_error::set_last_error(e.to_string().as_str()); + false + } + } + }}; + + // Case with output parameter and no input parameters + (|| -> ($out_param:ident: $out_param_ty:ty) $inner_body:block) => {{ + fn inner() -> Result<$out_param_ty> $inner_body + + match inner() { + Ok(rv) => { + set_out_param!($out_param, rv); + last_error::clear_last_error(); + true + } + Err(e) => { + last_error::set_last_error(e.to_string().as_str()); + false + } + } + }}; + + // Case without output parameter but with input parameters + (|$($param:ident: $param_ty:ty),*| $inner_body:block) => {{ + fn inner($($param: $param_ty),*) -> Result<()> $inner_body + + match inner($($param),*) { + Ok(_) => { + last_error::clear_last_error(); + true + } + Err(e) => { + last_error::set_last_error(e.to_string().as_str()); + false + } + } + }}; + + // Case without output parameter and no input parameters + (|| $inner_body:block) => {{ + fn inner() -> Result<()> $inner_body + + match inner() { + Ok(_) => { + last_error::clear_last_error(); + true + } + Err(e) => { + last_error::set_last_error(e.to_string().as_str()); + false + } + } + }}; +} + diff --git a/omrf/src/object_pool.rs b/omrf/src/object_pool.rs new file mode 100644 index 0000000..380cc50 --- /dev/null +++ b/omrf/src/object_pool.rs @@ -0,0 +1,95 @@ +//! When exporting resources for C interface, resource management and ownership are important things. +//! In this dynamic library, we hold all resources' ownership in Rust world, +//! and only expose a token for C code manipulation. +//! +//! We need to create a container for holding all resources and providing corresponding operations. +//! So we introduce [ObjectPool] in this module for this purpose. +use slotmap::{DefaultKey, Key, KeyData, SlotMap}; +use thiserror::Error as TeError; + +/// Error occurs when operating with [ObjectPool]. +#[derive(Debug, TeError)] +pub enum Error { + #[error("given token is not presented in object pool")] + NoSuchToken, +} + +/// The token for fetching object in [ObjectPool]. +pub type Token = u64; + +/// Get the invalid token. +/// +/// Invalid token is always invalid for fetching object in pool, +/// And can be useful in FFI scenario. +pub fn invalid_token() -> Token { + DefaultKey::null().data().as_ffi() +} + +/// A pool for managing objects with unique tokens. +/// +/// It is highly suggested to use this pool with [std::sync::RwLock] guard. +pub struct ObjectPool { + objs: SlotMap, +} + +impl ObjectPool { + /// Create a new [ObjectPool]. + pub fn new() -> Self { + Self { + objs: SlotMap::new(), + } + } + + /// Convert [slotmap] crate's [DefaultKey] to our [Token]. + fn key_to_token(key: &DefaultKey) -> Token { + key.data().as_ffi() + } + + /// Convert our [Token] to [slotmap] crate's [DefaultKey]. + fn token_to_key(token: Token) -> DefaultKey { + DefaultKey::from(KeyData::from_ffi(token)) + } + + /// Put given object into the pool and return its token. + /// + /// The ownership of given object is transferred to the pool. + pub fn allocate(&mut self, value: T) -> Result { + let key = self.objs.insert(value); + Ok(Self::key_to_token(&key)) + } + + /// Free the object in the pool corresponding to the given token. + pub fn free(&mut self, token: Token) -> Result<(), Error> { + let _ = self.pop(token)?; + Ok(()) + } + + /// Remove the object from the pool corresponding to the given token and return it. + /// + /// The ownership of the object is transferred to the caller. + pub fn pop(&mut self, token: Token) -> Result { + match self.objs.remove(Self::token_to_key(token)) { + Some(obj) => Ok(obj), + None => Err(Error::NoSuchToken), + } + } + + /// Clear all objects in the pool. + pub fn clear(&mut self) -> () { + self.objs.clear(); + } + + /// Get a reference to the object in the pool corresponding to the given token. + pub fn get(&self, token: Token) -> Result<&T, Error> { + self.objs + .get(Self::token_to_key(token)) + .ok_or(Error::NoSuchToken) + } + + /// Get a mutable reference to the object in the pool corresponding to the given token. + pub fn get_mut(&mut self, token: Token) -> Result<&mut T, Error> { + self.objs + .get_mut(Self::token_to_key(token)) + .ok_or(Error::NoSuchToken) + } +}