Skip to content

Commit 16539f3

Browse files
wip: OwnedUPtr and Refnum
Had to extend the UPtr with ownership semantics and finalised the API for the cookie jar without drop behaviour. The API looks good but the tests in LabVIEW fail at this stage. Not sure why.
1 parent 145faba commit 16539f3

19 files changed

Lines changed: 430 additions & 22 deletions

justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
22

3-
lv_ver := "2020"
3+
lv_ver := "2024"
44

55
unit-tests:
66
cargo test --no-default-features --features chrono,ndarray

labview-interop/src/labview.rs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ use std::sync::LazyLock;
77

88
use dlopen2::wrapper::{Container, WrapperApi};
99

10-
use crate::errors::LVInteropError;
10+
use crate::errors::{LVInteropError, MgError};
1111
use crate::{
1212
errors::{InternalError, Result},
1313
memory::MagicCookie,
1414
types::LVStatusCode,
1515
};
16+
use crate::memory::UPtr;
1617

1718
/// The path to the LabVIEW runtime library.
1819
/// Set as const, so we can swap this for different platforms if required.
@@ -44,7 +45,7 @@ pub(crate) type UHandleValue = usize;
4445
/// Represents as UPtr passed by value. Can't use the generic
4546
/// version from the memory module else since the functions
4647
/// aren't generic.
47-
pub(crate) type UPtrValue = usize;
48+
pub(crate) type UPtrValue = *mut c_void;
4849

4950
static SYNC_API: LazyLock<Result<Container<SyncApi>>> = LazyLock::new(load_container);
5051

@@ -68,6 +69,19 @@ pub fn memory_api() -> Result<&'static Container<MemoryApi>> {
6869
MEMORY_API.as_ref().map_err(|e| e.clone())
6970
}
7071

72+
static COOKIE_API: LazyLock<Result<Container<CookieApi>>> = LazyLock::new(|| {
73+
let result = load_container::<CookieApi>();
74+
75+
if let Err(e) = &result {
76+
eprintln!("Failed to load LabVIEW Cookie API: {e}");
77+
}
78+
result
79+
});
80+
81+
pub fn cookie_api() -> Result<&'static Container<CookieApi>> {
82+
COOKIE_API.as_ref().map_err(|e| e.clone())
83+
}
84+
7185
/// The LabVIEW synchronisation features are part of the Support Manager API.
7286
///
7387
/// The [official documentation](https://www.ni.com/docs/en-US/bundle/labview-api-ref/page/properties-and-methods/lv-manager/support-manager-functions.html) for the Support Manager can be found (last verified 2024-jul-09) on the webpage of National Instruments.
@@ -140,6 +154,34 @@ pub struct MemoryApi {
140154
/// `DSNewHClr` is an alternative that also initialized the memory to zero.
141155
#[dlopen2_name = "DSNewHandle"]
142156
new_handle: unsafe extern "C" fn(size: usize) -> *mut *mut std::ffi::c_void,
157+
158+
/// UPtr DSNewPtr(size);
159+
///
160+
/// Creates a new pointer to a non-relocatable block of memory of the specified size.
161+
///
162+
/// ```C
163+
/// UPtr DSNewPtr(size_t size);
164+
/// ```
165+
///
166+
/// - `size`: `size_t`, Size, in bytes, of the pointer you want to create.
167+
///
168+
/// Return Value
169+
///
170+
/// A pointer to a block of size bytes. If an error occurs, this function returns NULL.
171+
#[dlopen2_name = "DSNewPtr"]
172+
new_ptr: unsafe extern "C" fn(size: usize) -> UPtrValue,
173+
174+
/// Release the memory referenced by the specified pointer.
175+
///
176+
/// ```C
177+
/// MgErr DsDisposePtr(p)
178+
/// ```
179+
///
180+
/// - `p`: `UPtr` Pointer you wish to dispose of.
181+
///
182+
/// Returns `MgErr`: Either `noErr` or `mZoneErr`.
183+
#[dlopen2_name = "DSDisposePtr"]
184+
dispose_ptr: unsafe extern "C" fn(p: UPtrValue) -> LVStatusCode,
143185

144186
/// Copies the data referenced by the handle hsrc into the handle pointed to by ph or a new handle if ph points to NULL.
145187
///
@@ -244,3 +286,38 @@ pub struct MemoryApi {
244286
total_new_size: usize,
245287
) -> LVStatusCode,
246288
}
289+
290+
pub type CookieJar = *mut *mut std::ffi::c_void;
291+
pub struct CookieInfo;
292+
pub type Bool32 = i32;
293+
// Cleanup modes enum
294+
#[repr(i32)]
295+
enum CleanupMode {
296+
CleanRemove = 0,
297+
CleanExit = 1,
298+
CleanOnIdle = 2,
299+
CleanAfterReset = 3,
300+
CleanOnIdleIfNotTop = 4,
301+
CleanAfterResetIfNotTop = 5,
302+
}
303+
type CleanupProcPtr = extern "C" fn(usize) -> i32;
304+
305+
#[derive(WrapperApi)]
306+
pub struct CookieApi {
307+
#[dlopen2_name = "MCNewBigJar"]
308+
new_big_jar: unsafe extern "C" fn(item_size: i32) -> CookieJar,
309+
#[dlopen2_name = "MCDisposeJar"]
310+
dispose_jar: unsafe extern "C" fn(jar: CookieJar) -> MgError,
311+
#[dlopen2_name = "MCNewCookie"]
312+
new_cookie: unsafe extern "C" fn(jar: CookieJar, info: UPtrValue) -> MagicCookie,
313+
#[dlopen2_name = "MCDisposeCookie"]
314+
dispose_cookie: unsafe extern "C" fn(jar: CookieJar, cookie: MagicCookie, info: UPtrValue) -> LVStatusCode,
315+
#[dlopen2_name = "MCGetCookieInfo"]
316+
get_cookie_info: unsafe extern "C" fn(jar: CookieJar, cookie: MagicCookie, info: UPtrValue) -> LVStatusCode,
317+
#[dlopen2_name = "MCGetCookieInfoPtr"]
318+
get_cookie_info_ptr: unsafe extern "C" fn(jar: CookieJar, cookie: MagicCookie, info: *mut UPtrValue) -> LVStatusCode,
319+
#[dlopen2_name = "MCIsACookie"]
320+
is_a_cookie: unsafe extern "C" fn(jar: CookieJar, cookie: MagicCookie) -> Bool32,
321+
#[dlopen2_name = "RTSetCleanupProc"]
322+
rt_set_cleanup_proc: unsafe extern "C" fn(proc: CleanupProcPtr, data_uptr: UPtrValue, cleanup_mode: CleanupMode) -> LVStatusCode,
323+
}

labview-interop/src/memory/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
mod owned_handle;
77
mod uhandle;
88
mod uptr;
9+
#[cfg(feature = "link")]
10+
mod refnum;
11+
#[cfg(feature = "link")]
12+
mod owned_uptr;
913

1014
use std::fmt::Debug;
1115

@@ -25,8 +29,18 @@ impl<T: Copy> LVCopy for T {}
2529
#[doc(hidden)]
2630
pub struct MagicCookie(u32);
2731

32+
impl MagicCookie {
33+
pub const fn null() -> Self {
34+
Self(0)
35+
}
36+
}
37+
2838
#[cfg(feature = "link")]
2939
pub use owned_handle::OwnedUHandle;
40+
#[cfg(feature = "link")]
41+
pub use owned_uptr::OwnedUPtr;
42+
#[cfg(feature = "link")]
43+
pub use refnum::{RefNum, CookieJar};
3044
pub use uhandle::UHandle;
3145
pub use uptr::UPtr;
3246

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use std::mem::MaybeUninit;
2+
use std::ops::{Deref, DerefMut};
3+
use crate::errors::LVInteropError;
4+
use crate::labview::memory_api;
5+
use crate::memory::UPtr;
6+
7+
/// An owned UPtr which means we implement drop to free the memory.
8+
///
9+
/// This should be used with caution to avoid double free.
10+
///
11+
/// Functionally this is equivalent to a Box<T>
12+
#[derive(Debug)]
13+
pub struct OwnedUPtr<T: 'static>(UPtr<'static, T>);
14+
15+
impl<T> OwnedUPtr<T> {
16+
pub fn new_uninit() -> Result<MaybeUninit<Self>, LVInteropError> {
17+
unsafe {
18+
let allocated = memory_api()?.new_ptr(size_of::<T>()) as *mut T;
19+
Ok(MaybeUninit::new(Self(UPtr::from_raw(allocated))))
20+
}
21+
}
22+
/// Allocate a new UPtr with the provided data. This then operates
23+
/// like the `Box` type.
24+
///
25+
/// You can then use this like a UPtr type but it will be freed on drop.
26+
///
27+
/// ## Errors
28+
///
29+
/// This can error if the memory API is unavailable.
30+
pub fn new(data: T) -> Result<Self, LVInteropError> {
31+
let pointer = unsafe {
32+
let allocated = memory_api()?.new_ptr(size_of::<T>()) as *mut T;
33+
*allocated = data;
34+
UPtr::from_raw(allocated)
35+
};
36+
Ok(Self(pointer))
37+
}
38+
39+
/// Get back to an owned type for a UPtr that you know
40+
/// you need to free when you are finished with it.
41+
///
42+
/// This is analogous to `Box::from_raw` and requires
43+
/// management across FFI boundaries.
44+
///
45+
/// ## Safety
46+
///
47+
/// This will be freed when dropped. Ensure you are
48+
/// responsible for the drop to avoid double frees.
49+
pub unsafe fn from_raw(ptr: UPtr<'static, T>) -> Self {
50+
Self(ptr)
51+
}
52+
53+
/// Get the value as a UPtr
54+
pub fn as_inner(&self) -> UPtr<'_, T> {
55+
unsafe {
56+
UPtr::from_raw(self.0.as_uptr_value() as *mut T)
57+
}
58+
}
59+
}
60+
61+
impl<T: Sized> Drop for OwnedUPtr<T> {
62+
fn drop(&mut self) {
63+
if let Some(memory_api) = memory_api().ok() {
64+
unsafe {
65+
memory_api.dispose_ptr(self.0.as_uptr_value());
66+
}
67+
}
68+
}
69+
}
70+
71+
impl<T> Deref for OwnedUPtr<T> {
72+
type Target = UPtr<'static, T>;
73+
fn deref(&self) -> &Self::Target {
74+
&self.0
75+
}
76+
}
77+
78+
impl<T> DerefMut for OwnedUPtr<T> {
79+
fn deref_mut(&mut self) -> &mut Self::Target {
80+
&mut self.0
81+
}
82+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//! This module wraps the "Cookie Jar" functionality of LabVIEW to allow
2+
//! you to create your own unique references in LabVIEW.
3+
4+
use std::marker::PhantomData;
5+
use crate::labview::{cookie_api};
6+
use crate::memory::{MagicCookie, UPtr, OwnedUPtr};
7+
8+
pub type Result<T> = crate::errors::Result<T>;
9+
10+
pub struct CookieJar<T: Sync> {
11+
jar: crate::labview::CookieJar,
12+
phantom_data: PhantomData<T>
13+
}
14+
//todo: Need to think about these.
15+
unsafe impl<T: Sync> Send for CookieJar<T> {}
16+
unsafe impl<T: Sync> Sync for CookieJar<T> {}
17+
18+
impl<T: Sync + 'static> CookieJar<T> {
19+
pub fn new() -> Result<Self> {
20+
let jar = unsafe {
21+
cookie_api()?.new_big_jar(size_of::<T>() as i32)
22+
};
23+
Ok(Self {
24+
jar,
25+
phantom_data: PhantomData
26+
})
27+
}
28+
29+
pub fn new_refnum(&mut self, cookie_info: UPtr<T>) -> Result<RefNum<T>> {
30+
let cookie = unsafe {
31+
cookie_api()?.new_cookie(self.jar, cookie_info.as_uptr_value())
32+
};
33+
Ok(RefNum {
34+
cookie,
35+
phantom_data: PhantomData
36+
})
37+
38+
}
39+
40+
pub fn cookie_info(&self, ref_num: RefNum<T>) -> Result<OwnedUPtr<T>> {
41+
unsafe {
42+
let ptr = OwnedUPtr::<T>::new_uninit()?;
43+
let uptr_raw = ptr.as_ptr().as_ref().unwrap().as_uptr_value();
44+
cookie_api()?.get_cookie_info(self.jar, ref_num.cookie, uptr_raw).to_specific_result(())?;
45+
Ok(ptr.assume_init())
46+
}
47+
48+
}
49+
50+
pub fn dispose_refnum(&mut self, ref_num: RefNum<T>) -> Result<()>{
51+
let info = self.cookie_info(ref_num)?;
52+
unsafe {
53+
cookie_api()?.dispose_cookie(self.jar, ref_num.cookie, info.as_uptr_value()).to_specific_result(())?;
54+
}
55+
todo!("Cleanup Routine")
56+
}
57+
}
58+
59+
#[derive(Debug)]
60+
#[repr(transparent)]
61+
pub struct RefNum<T> {
62+
cookie: MagicCookie,
63+
phantom_data: PhantomData<T>
64+
}
65+
66+
impl<T> RefNum<T> {
67+
pub fn null() -> Self {
68+
Self {
69+
cookie: MagicCookie::null(),
70+
phantom_data: PhantomData
71+
}
72+
}
73+
}
74+
75+
impl<T> Clone for RefNum<T> {
76+
fn clone(&self) -> Self {
77+
Self {
78+
cookie: self.cookie,
79+
phantom_data: PhantomData
80+
}
81+
}
82+
}
83+
84+
impl<T> Copy for RefNum<T> {}

0 commit comments

Comments
 (0)