From 76ddddb6cd66205ea37d9a7513312cc4165b5e73 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Mon, 11 May 2026 13:33:01 +0800 Subject: [PATCH] feat: fix LazyLock issue and add something in cdylib --- wfassoc-cdylib/src/cstr_ffi.rs | 17 ++--- wfassoc-cdylib/src/lib.rs | 109 ++++++++++++++++++++++++--------- wfassoc/src/highlevel.rs | 32 +++++----- wfassoc/src/win32/concept.rs | 45 +++++++++----- 4 files changed, 134 insertions(+), 69 deletions(-) diff --git a/wfassoc-cdylib/src/cstr_ffi.rs b/wfassoc-cdylib/src/cstr_ffi.rs index d44e72f..8ff80be 100644 --- a/wfassoc-cdylib/src/cstr_ffi.rs +++ b/wfassoc-cdylib/src/cstr_ffi.rs @@ -12,6 +12,9 @@ use std::cell::RefCell; use std::ffi::{CStr, CString, c_char}; 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. @@ -31,13 +34,13 @@ type Result = std::result::Result; // endregion -// region: FFI String +// region: String Cache for Exposing -struct FfiString { +struct StringCache { msg: CString, } -impl FfiString { +impl StringCache { fn new() -> Self { Self { msg: CString::new("").expect("empty string must be valid for CString"), @@ -49,7 +52,7 @@ impl FfiString { Ok(()) } - pub fn get_msg(&self) -> *const c_char { + pub fn get_msg(&self) -> CStyleString { self.msg.as_ptr() } @@ -63,7 +66,7 @@ impl FfiString { // region: Exposed Functions thread_local! { - static STRING_CACHE: RefCell = RefCell::new(FfiString::new()); + static STRING_CACHE: RefCell = RefCell::new(StringCache::new()); } /// Set thread local string exposed for C code. @@ -74,7 +77,7 @@ pub fn set_ffi_string(msg: &str) -> Result<()> { } /// Get const pointer to thread local string exposed for C code. -pub fn get_ffi_string() -> *const c_char { +pub fn get_ffi_string() -> CStyleString { STRING_CACHE.with(|e| e.borrow().get_msg()) } @@ -88,7 +91,7 @@ pub fn clear_ffi_string() { } /// Parse string given by C code into Rust string. -pub fn parse_ffi_string<'a>(ptr: *const c_char) -> Result<&'a str> { +pub fn parse_ffi_string<'a>(ptr: CStyleString) -> Result<&'a str> { if ptr.is_null() { Err(Error::NullPtr) } else { diff --git a/wfassoc-cdylib/src/lib.rs b/wfassoc-cdylib/src/lib.rs index e0a98b4..35288b3 100644 --- a/wfassoc-cdylib/src/lib.rs +++ b/wfassoc-cdylib/src/lib.rs @@ -3,11 +3,11 @@ mod last_error; mod object_pool; use object_pool::ObjectPool; -use std::ffi::{CString, c_char}; -use std::sync::{LazyLock, PoisonError, RwLock}; +use std::sync::{LazyLock, RwLock}; use thiserror::Error as TeError; use wfassoc::highlevel::{Program, Schema}; +pub use cstr_ffi::CStyleString; pub use object_pool::Token; // region: Error @@ -44,6 +44,36 @@ type Result = std::result::Result; // region: Macros +macro_rules! in_param_ty { + ($t:ty) => { + $t + }; +} + +macro_rules! out_param_ty { + ($t:ty) => { + *mut $t + }; +} + +macro_rules! set_out_param { + ($lhs:expr, $rhs:expr) => { + unsafe { *$lhs = $rhs }; + }; +} + +macro_rules! pull_reader { + ($pool:expr) => { + $pool.read().map_err(|_| Error::PoisonRwLock) + }; +} + +macro_rules! pull_writer { + ($pool:expr) => { + $pool.write().map_err(|_| Error::PoisonRwLock) + }; +} + // endregion // region: Exposed Functions @@ -52,16 +82,18 @@ type Result = std::result::Result; #[unsafe(no_mangle)] pub extern "C" fn WFStartup() -> bool { + // TODO: Initialize all pool by fetching writer from them true } #[unsafe(no_mangle)] pub extern "C" fn WFShutdown() -> bool { + // TODO: Free all pool stored objects true } #[unsafe(no_mangle)] -pub extern "C" fn WFGetLastError() -> *const c_char { +pub extern "C" fn WFGetLastError() -> CStyleString { last_error::get_last_error() } @@ -70,32 +102,24 @@ pub extern "C" fn WFHasPrivilege() -> bool { wfassoc::win32::utilities::has_privilege() } -#[unsafe(no_mangle)] -pub extern "C" fn WFAdd(left: u32, right: u32, rv: *mut u32) -> bool { - unsafe { - *rv = left + right; - } - return true; -} - // endregion // region: Schema -const SCHEMA_POOL: LazyLock>> = +static SCHEMA_POOL: LazyLock>> = LazyLock::new(|| RwLock::new(ObjectPool::new())); #[unsafe(no_mangle)] -pub extern "C" fn WFSchemaCreate(out_token: *mut Token) -> bool { +pub extern "C" fn WFSchemaCreate(out_schema: out_param_ty!(Token)) -> bool { fn inner() -> Result { - let binding = &SCHEMA_POOL; - let mut pool = binding.write().map_err(|_| Error::PoisonRwLock)?; + let mut pool = pull_writer!(SCHEMA_POOL)?; Ok(pool.allocate(Schema::new())?) } match inner() { Ok(rv) => { - unsafe { *out_token = rv }; + set_out_param!(out_schema, rv); + last_error::clear_last_error(); true } Err(e) => { @@ -105,23 +129,48 @@ pub extern "C" fn WFSchemaCreate(out_token: *mut Token) -> bool { } } +#[unsafe(no_mangle)] +pub extern "C" fn WFSchemaSetIdentifier( + in_schema: in_param_ty!(Token), + in_value: in_param_ty!(CStyleString), +) -> bool { + fn inner(in_schema: Token, in_value: CStyleString) -> Result<()> { + let mut pool = pull_writer!(SCHEMA_POOL)?; + let schema = pool.get_mut(in_schema)?; + schema.set_identifier(cstr_ffi::parse_ffi_string(in_value)?); + Ok(()) + } + + match inner(in_schema, in_value) { + Ok(_) => { + last_error::clear_last_error(); + true + } + Err(e) => { + last_error::set_last_error(e.to_string().as_str()); + false + } + } +} #[unsafe(no_mangle)] -pub extern "C" fn WFSchemaIntoProgram(in_token: Token, out_token: *mut Token) -> bool { +pub extern "C" fn WFSchemaIntoProgram( + in_schema: in_param_ty!(Token), + out_program: out_param_ty!(Token), +) -> bool { fn inner(in_token: Token) -> Result { - let binding = &SCHEMA_POOL; - let mut pool = binding.write().map_err(|_| Error::PoisonRwLock)?; + let mut pool = pull_writer!(SCHEMA_POOL)?; let schema = pool.pop(in_token)?; - let binding = &PROGRAM_POOL; - let mut pool = binding.write().map_err(|_| Error::PoisonRwLock)?; + let mut pool = pull_writer!(PROGRAM_POOL)?; let program = schema.into_program()?; Ok(pool.allocate(program)?) } - match inner(in_token) { + match inner(in_schema) { Ok(rv) => { - unsafe { *out_token = rv }; + set_out_param!(out_program, rv); + last_error::clear_last_error(); true } Err(e) => { @@ -132,15 +181,17 @@ pub extern "C" fn WFSchemaIntoProgram(in_token: Token, out_token: *mut Token) -> } #[unsafe(no_mangle)] -pub extern "C" fn WFSchemaDestroy(in_token: Token) -> bool { +pub extern "C" fn WFSchemaDestroy(in_schema: in_param_ty!(Token)) -> bool { fn inner(in_token: Token) -> Result<()> { - let binding = &SCHEMA_POOL; - let mut pool = binding.write().map_err(|_| Error::PoisonRwLock)?; + let mut pool = pull_writer!(SCHEMA_POOL)?; Ok(pool.free(in_token)?) } - match inner(in_token) { - Ok(_) => true, + match inner(in_schema) { + Ok(_) => { + last_error::clear_last_error(); + true + } Err(e) => { last_error::set_last_error(e.to_string().as_str()); false @@ -152,7 +203,7 @@ pub extern "C" fn WFSchemaDestroy(in_token: Token) -> bool { // region: Program -const PROGRAM_POOL: LazyLock>> = +static PROGRAM_POOL: LazyLock>> = LazyLock::new(|| RwLock::new(ObjectPool::new())); // endregion diff --git a/wfassoc/src/highlevel.rs b/wfassoc/src/highlevel.rs index 011a356..f32cd65 100644 --- a/wfassoc/src/highlevel.rs +++ b/wfassoc/src/highlevel.rs @@ -6,7 +6,7 @@ use regex::Regex; use std::collections::HashMap; use std::ffi::OsStr; use std::path::Path; -use std::rc::Rc; +use std::sync::Arc; use std::sync::LazyLock; use thiserror::Error as TeError; @@ -234,16 +234,16 @@ pub struct Program { applications_key: lowlevel::ApplicationsKey, app_path: String, app_dir_path: String, - name: Option>, - icon: Option>, - behavior: Option>, + name: Option>, + icon: Option>, + behavior: Option>, #[allow(dead_code)] - strs: Vec>, + strs: Vec>, #[allow(dead_code)] - icons: Vec>, + icons: Vec>, #[allow(dead_code)] - behaviors: Vec>, + behaviors: Vec>, ext_keys: Vec, ext_keys_map: HashMap, @@ -293,9 +293,9 @@ impl Program { fn resolve_index( key: &String, - vector: &Vec>, + vector: &Vec>, index_map: &HashMap, - ) -> Result, ParseProgramError> { + ) -> Result, ParseProgramError> { match index_map.get(key) { Some(index) => Ok(vector .get(*index) @@ -355,22 +355,22 @@ impl Program { let program_str = ProgramStr { inner: str_res_variant, }; - Ok(Rc::new(program_str)) + Ok(Arc::new(program_str)) })?; let (icons, icons_index_map) = Self::flat_hashmap(&schema.icons, |entry| { let icon_res_variant: lowlevel::IconResVariant = entry.as_str().into(); let program_icon = ProgramIcon { inner: icon_res_variant, }; - Ok(Rc::new(program_icon)) + Ok(Arc::new(program_icon)) })?; let (behaviors, behaviors_index_map) = Self::flat_hashmap(&schema.behaviors, |entry| { // We simply always use "Open" verb. let cmdline: concept::CmdLine = entry.as_str().parse()?; - let verb = concept::Verb::OPEN.clone(); + let verb = concept::Verb::OPEN(); let shell_verb = lowlevel::ShellVerb::new(verb, cmdline); let program_behavior = ProgramBehavior { inner: shell_verb }; - Ok(Rc::new(program_behavior)) + Ok(Arc::new(program_behavior)) })?; // Setup default name, icon and behavior @@ -764,9 +764,9 @@ struct ProgramProgIdExtKey { ext_key: lowlevel::ExtKey, progid_key: lowlevel::ProgIdKey, - name: Rc, - icon: Rc, - behavior: Rc, + name: Arc, + icon: Arc, + behavior: Arc, } // endregion diff --git a/wfassoc/src/win32/concept.rs b/wfassoc/src/win32/concept.rs index 556e1a3..7daf38c 100644 --- a/wfassoc/src/win32/concept.rs +++ b/wfassoc/src/win32/concept.rs @@ -699,13 +699,10 @@ pub struct ExpandString { inner: String, } -impl ExpandString { - /// Internal shared compiled regex pattern matching Variable, - /// the `%` braced string like `%SystemRoot%`. - const VAR_RE: LazyLock = LazyLock::new(|| { - Regex::new(r"%[a-zA-Z0-9_]+%").expect("unexpected bad regex pattern string") - }); -} +/// Internal shared compiled regex pattern matching Variable, +/// the `%` braced string like `%SystemRoot%`. +static WINVAR_RE: LazyLock = + LazyLock::new(|| Regex::new(r"%[a-zA-Z0-9_]+%").expect("unexpected bad regex pattern string")); impl ExpandString { /// Create a new expand string @@ -743,7 +740,7 @@ impl ExpandString { // If the final string still has environment variable, // we think we fail to expand it. - if Self::VAR_RE.is_match(rv.as_str()) { + if WINVAR_RE.is_match(rv.as_str()) { Err(ExpandEnvVarError::NoEnvVar) } else { Ok(rv) @@ -761,7 +758,7 @@ impl FromStr for ExpandString { type Err = ParseExpandStrError; fn from_str(s: &str) -> Result { - if Self::VAR_RE.is_match(s) { + if WINVAR_RE.is_match(s) { Ok(Self { inner: s.to_string(), }) @@ -858,9 +855,20 @@ pub struct Verb { } impl Verb { - pub const OPEN: LazyLock = LazyLock::new(|| Verb::new("open").expect("unexpected bad verb")); - pub const EDIT: LazyLock = LazyLock::new(|| Verb::new("edit").expect("unexpected bad verb")); - pub const PLAY: LazyLock = LazyLock::new(|| Verb::new("play").expect("unexpected bad verb")); + #[allow(non_snake_case)] + pub fn OPEN() -> Self { + Verb::new("open").expect("unexpected bad verb") + } + + #[allow(non_snake_case)] + pub fn EDIT() -> Self { + Verb::new("edit").expect("unexpected bad verb") + } + + #[allow(non_snake_case)] + pub fn PLAY() -> Self { + Verb::new("play").expect("unexpected bad verb") + } } impl Verb { @@ -927,9 +935,9 @@ pub struct BadCmdLineError { } /// The struct representing a Windows command line. -/// +/// /// # Note -/// +/// /// This struct currently does nothing for validation for given command line. /// Because there is no standard for validating this. /// So I just write this struct as a placeholder for future extension. @@ -941,7 +949,9 @@ pub struct CmdLine { impl CmdLine { /// Create a new command line. pub fn new>(args: &[S]) -> Result { - Ok(Self { inner: args.iter().map(|s| s.as_ref().to_string()).collect() }) + Ok(Self { + inner: args.iter().map(|s| s.as_ref().to_string()).collect(), + }) } /// Get the full command line. @@ -981,7 +991,8 @@ impl FromStr for CmdLine { fn from_str(s: &str) -> Result { // We simply split it by space for rough result. - Ok(Self { inner: s.split(' ').map(|s| s.to_string()).collect() }) + Ok(Self { + inner: s.split(' ').map(|s| s.to_string()).collect(), + }) } } -