//! 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::{CStr, CString, c_char}; use thiserror::Error as TeError; // 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: FFI String struct FfiString { msg: CString, } impl FfiString { 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) -> *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"); } } // endregion // region: Exposed Functions thread_local! { static STRING_CACHE: RefCell = RefCell::new(FfiString::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() -> *const c_char { 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: *const c_char) -> 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