ferroui_native/
frn_string.rs1use crate::interop::*;
5use ferroui_microcom::{ComPtr, HResult};
6use std::ffi::{c_void, CString};
7
8pub struct FrnString {
14 string: String,
15 native: Vec<u8>,
17}
18
19impl FrnString {
20 pub fn new(s: &str) -> Self {
21 let mut native = Vec::with_capacity(s.len() + 1);
22 native.extend_from_slice(s.as_bytes());
23 native.push(0);
24 Self { string: s.to_owned(), native }
25 }
26
27 pub fn string(&self) -> &str {
29 &self.string
30 }
31
32 pub fn bytes(&self) -> &[u8] {
34 self.string.as_bytes()
35 }
36}
37
38impl IFrnStringImpl for FrnString {
39 fn pointer(&self) -> Result<*mut c_void, HResult> {
40 if self.string.is_empty() {
43 return Ok(std::ptr::null_mut());
44 }
45 Ok(self.native.as_ptr() as *mut c_void)
46 }
47
48 fn length(&self) -> Result<i32, HResult> {
49 Ok(self.string.len() as i32)
50 }
51}
52
53pub struct FrnStringArray {
55 items: Vec<(String, ComPtr<IFrnString>)>,
56}
57
58impl FrnStringArray {
59 pub fn new<S: AsRef<str>>(items: impl IntoIterator<Item = S>) -> Self {
60 Self {
61 items: items
62 .into_iter()
63 .map(|s| (s.as_ref().to_owned(), IFrnString::from_impl(FrnString::new(s.as_ref()))))
64 .collect(),
65 }
66 }
67
68 pub fn to_string_array(&self) -> Vec<String> {
70 self.items.iter().map(|(s, _)| s.clone()).collect()
71 }
72}
73
74impl IFrnStringArrayImpl for FrnStringArray {
75 fn get_count(&self) -> u32 {
76 self.items.len() as u32
77 }
78
79 fn get(&self, index: u32) -> Result<Option<ComPtr<IFrnString>>, HResult> {
80 match self.items.get(index as usize) {
81 Some((_, item)) => Ok(Some(item.clone())),
82 None => Err(HResult::INVALIDARG),
83 }
84 }
85}
86
87pub fn frn_string_bytes(s: &IFrnString) -> Vec<u8> {
89 let (Ok(ptr), Ok(len)) = (s.pointer(), s.length()) else {
90 return Vec::new();
91 };
92 if ptr.is_null() || len <= 0 {
93 return Vec::new();
94 }
95 unsafe { std::slice::from_raw_parts(ptr as *const u8, len as usize) }.to_vec()
98}
99
100pub fn frn_string_to_string(s: &IFrnString) -> Option<String> {
102 match s.pointer() {
103 Ok(ptr) if !ptr.is_null() => Some(String::from_utf8_lossy(&frn_string_bytes(s)).into_owned()),
104 _ => None,
105 }
106}
107
108pub fn frn_string_array_to_vec(array: &IFrnStringArray) -> Vec<String> {
111 (0..array.get_count())
112 .map(|index| match array.get(index) {
113 Ok(Some(s)) => frn_string_to_string(&s).unwrap_or_default(),
114 _ => String::new(),
115 })
116 .collect()
117}
118
119pub(crate) fn to_c_string(s: &str) -> CString {
122 let end = s.bytes().position(|b| b == 0).unwrap_or(s.len());
123 CString::new(&s.as_bytes()[..end]).unwrap_or_default()
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn string_round_trips_through_its_com_interface() {
132 let s = IFrnString::from_impl(FrnString::new("héllo ⌘"));
133 assert_eq!(s.length(), Ok("héllo ⌘".len() as i32));
134 assert_eq!(frn_string_to_string(&s).as_deref(), Some("héllo ⌘"));
135 assert_eq!(frn_string_bytes(&s), "héllo ⌘".as_bytes());
136 let ptr = s.pointer().unwrap() as *const u8;
138 assert_eq!(unsafe { *ptr.add("héllo ⌘".len()) }, 0);
140 }
141
142 #[test]
143 fn empty_string_has_no_buffer() {
144 let s = IFrnString::from_impl(FrnString::new(""));
145 assert!(s.pointer().unwrap().is_null());
146 assert_eq!(s.length(), Ok(0));
147 assert_eq!(frn_string_to_string(&s), None);
148 assert!(frn_string_bytes(&s).is_empty());
149 }
150
151 #[test]
152 fn string_array_round_trips() {
153 let source = FrnStringArray::new(["a", "", "ccc"]);
154 assert_eq!(source.to_string_array(), ["a", "", "ccc"]);
155 let array = IFrnStringArray::from_impl(source);
156 assert_eq!(array.get_count(), 3);
157 assert_eq!(frn_string_array_to_vec(&array), ["a", "", "ccc"]);
158 assert_eq!(array.get(3).unwrap_err(), HResult::INVALIDARG);
159 }
160
161 #[test]
162 fn c_strings_stop_at_the_first_nul() {
163 assert_eq!(to_c_string("title").as_bytes(), b"title");
164 assert_eq!(to_c_string("a\0b").as_bytes(), b"a");
165 assert_eq!(to_c_string("").as_bytes(), b"");
166 }
167}