labview_interop/types/
string.rs

1//! Handle the various string times that the LabVIEW
2//! interface provides.
3//!
4
5#[cfg(feature = "link")]
6use crate::errors::Result;
7use crate::labview_layout;
8#[cfg(feature = "link")]
9use crate::memory::OwnedUHandle;
10use crate::memory::{LVCopy, UHandle, UPtr};
11use encoding_rs::Encoding;
12use std::borrow::Cow;
13use std::sync::LazyLock;
14
15#[cfg(target_os = "windows")]
16fn get_encoding() -> &'static Encoding {
17    #[link(name = "kernel32")]
18    extern "stdcall" {
19        fn GetACP() -> u32;
20    }
21
22    //SAFETY: No real concerns with this call.
23    let code_page = unsafe { GetACP() };
24
25    // Crap - ctor errors again. I think it is reasonably safe
26    // to assume LabVIEW isn't going to hit anything to unusual
27    // due to it's level of support.
28    codepage::to_encoding(code_page as u16).expect("Unknown code page.")
29}
30
31#[cfg(target_os = "linux")]
32fn get_encoding() -> &'static Encoding {
33    encoding_rs::WINDOWS_1252
34}
35
36#[cfg(target_os = "macos")]
37fn get_encoding() -> &'static Encoding {
38    encoding_rs::UTF_8
39}
40
41/// The encoding that LabVIEW uses on the current platform.
42pub(crate) static LV_ENCODING: LazyLock<&'static Encoding> = LazyLock::new(get_encoding);
43
44labview_layout!(
45    /// Internal LabVIEW string structure.
46    ///
47    /// This is the recommended type when interfacing with LabVIEW
48    /// as it is also the internal format so no translation is needed.
49    pub struct LStr {
50        size: i32,
51        data: [u8],
52    }
53);
54
55/// Copyable inside a handle.
56impl LVCopy for LStr {}
57
58/// Definition of a handle to an LabVIEW String. Helper for FFI definition and
59/// required for any functions that need to resize the string.
60pub type LStrHandle<'a> = UHandle<'a, LStr>;
61/// Definition of a pointer to an LabVIEW String. Helper for FFI definition.
62pub type LStrPtr = UPtr<LStr>;
63/// Definition of an owned LStr Handle.
64#[cfg(feature = "link")]
65pub type LStrOwned = OwnedUHandle<LStr>;
66
67impl LStr {
68    /// Access the data from the string as a binary slice.
69    pub fn as_slice(&self) -> &[u8] {
70        unsafe { std::slice::from_raw_parts(self.data.as_ptr(), self.size as usize) }
71    }
72
73    /// Access the data from the string as a mutable slice.
74    ///
75    /// Use this function for modifying the data without changing the size.
76    ///
77    /// If you need to change the size you must access the handle that contains
78    /// the data and access [`LStrHandle::set`]
79    pub fn as_mut_slice(&mut self) -> &mut [u8] {
80        unsafe { std::slice::from_raw_parts_mut(self.data.as_mut_ptr(), self.size as usize) }
81    }
82
83    /// Get the size of this LStr instance.
84    /// Would LStr ever be padded?
85    pub fn size(&self) -> usize {
86        std::mem::size_of::<i32>() + self.data.len()
87    }
88
89    /// Get the size of LStr given a specific data slice.
90    /// Would LStr ever be padded?
91    pub fn size_with_data(data: &[u8]) -> usize {
92        std::mem::size_of::<i32>() + data.len()
93    }
94
95    /// Uses a system appropriate decoder to return a rust compatible string.
96    ///
97    /// This returns a [`std::borrow::Cow`] to avoid any allocations if the
98    /// input is already valid UTF8.
99    pub fn to_rust_string_with_encoding(&self, encoding: &'static Encoding) -> Cow<str> {
100        let (result, _, _) = encoding.decode(self.as_slice());
101        result
102    }
103
104    /// Uses a system appropriate decoder to return a rust compatible string.
105    ///
106    /// This returns a [`std::borrow::Cow`] to avoid any allocations if the
107    /// input is already valid UTF8.
108    ///
109    /// # Example
110    /// ```
111    /// use labview_interop::types::{LVStatusCode, LStrHandle};
112    /// #[no_mangle]
113    /// pub extern "C" fn string_check(mut string: LStrHandle) -> LVStatusCode {
114    ///    let string_value = string.to_string();
115    ///    format!("Read value: {string_value}");
116    ///    LVStatusCode::SUCCESS
117    /// }
118    ///```
119    pub fn to_rust_string(&self) -> Cow<str> {
120        self.to_rust_string_with_encoding(&LV_ENCODING)
121    }
122}
123
124impl std::fmt::Display for LStr {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        write!(f, "{}", self.to_rust_string())
127    }
128}
129
130impl std::fmt::Debug for LStr {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        write!(f, "\"{}\"", self.to_rust_string())
133    }
134}
135
136impl PartialEq for LStr {
137    fn eq(&self, other: &Self) -> bool {
138        self.as_slice() == other.as_slice()
139    }
140}
141
142/// Implement features that require a full string handle rather than just the [`LStr`]
143/// type.
144///
145/// Requires the link feature.
146#[cfg(feature = "link")]
147impl LStrHandle<'_> {
148    /// Set the string as a binary value against the handle.
149    ///
150    /// This function will resize the handle based on the size of the input value.
151    ///
152    /// # Errors
153    ///
154    /// * This will error if the string handle is invalid (likely a null pointer).
155    ///
156    /// # Example
157    /// ```
158    /// use labview_interop::types::{LVStatusCode, LStrHandle};
159    /// #[no_mangle]
160    /// pub extern "C" fn hello_world(mut string: LStrHandle) -> LVStatusCode {
161    ///    let result = string.set(b"Hello World");
162    ///    result.into()
163    /// }
164    //```
165    pub fn set(&mut self, value: &[u8]) -> Result<()> {
166        let input_length = value.len();
167        let struct_size = LStr::size_with_data(value);
168
169        unsafe {
170            //Safety: Is this alignment ever wrong. Would it even pad between the size and data.
171            // I believe not.
172            self.resize(struct_size)?;
173
174            let l_str = self.as_ref_mut()?;
175            l_str.size = input_length as i32;
176            for (value, output) in value.iter().zip(l_str.data.iter_mut()) {
177                *output = *value;
178            }
179        }
180
181        Ok(())
182    }
183
184    /// Set string takes a Rust string and puts it into the LabVIEW String.
185    ///
186    /// This is a two step process:
187    /// 1. Encode from Rust (UTF8) to LabVIEW encoding (based on system code page on Windows).
188    /// 2. Write this encoding into the LabVIEW string.
189    ///
190    /// If the input is valid ASCII then no additional data copies are made. If not then this will
191    /// allocate a new intermediate buffer to hold the decoded results before writing to the
192    /// LabVIEW string.
193    pub fn set_str(&mut self, value: &str) -> Result<()> {
194        self.set_str_with_encoding(&LV_ENCODING, value)
195    }
196
197    /// Set string with encoder takes a Rust string and puts it into the LabVIEW String.
198    ///
199    /// This is a two step process:
200    /// 1. Encode from Rust (UTF8) to LabVIEW encoding with the provided encoder.
201    /// 2. Write this encoding into the LabVIEW string.
202    ///
203    /// If the input is valid ASCII then no additional data copies are made. If not then this will
204    /// allocate a new intermediate buffer to hold the decoded results before writing to the
205    /// LabVIEW string.
206    ///
207    /// The encoder should be an encoder provided by the encoding_rs crate.
208    pub fn set_str_with_encoding(&mut self, encoder: &'static Encoding, value: &str) -> Result<()> {
209        let (buffer, _, _) = encoder.encode(value);
210        self.set(&buffer)
211    }
212}
213
214#[cfg(feature = "link")]
215impl LStrOwned {
216    /// Create a new owned `LStr` with a size of zero.
217    pub fn empty_string() -> Result<Self> {
218        unsafe { OwnedUHandle::<LStr>::new_unsized(|handle| handle.set(&[])) }
219    }
220    ///
221    /// # Example
222    /// ```
223    /// use labview_interop::types::{LStrHandle, LStrOwned};
224    /// use labview_interop::types::LVStatusCode;
225    /// #[no_mangle]
226    /// pub extern "C" fn hello_world(mut strn: String, output_string: *mut LStrHandle) {
227    ///    let handle = LStrOwned::from_data(strn.as_bytes()).unwrap();
228    ///    unsafe {
229    ///        handle.clone_into_pointer(output_string).unwrap();
230    ///    }
231    /// }
232    /// ```
233    pub fn from_data(data: &[u8]) -> Result<Self> {
234        unsafe { OwnedUHandle::<LStr>::new_unsized(|handle| handle.set(data)) }
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241    use std::alloc::{alloc, Layout, LayoutError};
242
243    /// Implements a questionable allocation strategy based on
244    /// https://www.reddit.com/r/rust/comments/mq3kqe/is_this_the_best_way_to_do_custom_dsts_unsized/
245    ///
246    /// For test purposes this is useful though.
247    ///
248    /// # Safety
249    /// These can be used for read-only testing. Writing will want to resize which is unavailable here.
250    impl LStr {
251        pub(crate) fn layout_of(n: usize) -> std::result::Result<Layout, LayoutError> {
252            // Build a layout describing an instance of this DST.
253            let (layout, _) = Layout::new::<i32>().extend(Layout::array::<u8>(n)?)?;
254            let layout = layout.pad_to_align();
255            Ok(layout)
256        }
257
258        pub(crate) unsafe fn boxed_uninit(n: usize) -> Box<Self> {
259            // Find the layout with a helper function.
260            let layout = Self::layout_of(n).unwrap();
261            // Make a heap allocation.
262            let ptr = alloc(layout);
263            // Construct a fat pointer by making a fake slice.
264            // The first argument is the pointer, the second argument is the metadata.
265            // In this case, its just the length of the slice.
266            let ptr = core::slice::from_raw_parts(ptr, n);
267            // Transmute the slice into the real fat pointer type.
268            let ptr = ptr as *const [u8] as *mut LStr;
269            // Build a box from the raw pointer.
270            let b = Box::from_raw(ptr);
271            // Make sure its the correct size.
272            debug_assert_eq!(std::mem::size_of_val(&*ptr), layout.size());
273            b
274        }
275
276        pub fn boxed_from_str(value: &str) -> Box<LStr> {
277            let length = value.len();
278            let bytes = value.as_bytes();
279            let mut boxed = unsafe { Self::boxed_uninit(length) };
280            boxed.size = length as i32;
281            for (i, byte) in bytes.iter().enumerate() {
282                boxed.data[i] = *byte;
283            }
284            boxed
285        }
286    }
287
288    #[test]
289    fn test_lstr_handle_debug() {
290        let string = LStr::boxed_from_str("Hello World");
291        let mut pointer = Box::into_raw(string);
292        let raw_handle = std::ptr::addr_of_mut!(pointer);
293        let handle = LStrHandle::from_raw(raw_handle);
294        let debug = format!("{:?}", handle);
295        assert!(debug.contains("Hello World"));
296    }
297}