Skip to main content

visa_sys/
lib.rs

1//! FFI bindings to the VISA (Virtual Instrument Software Architecture) library.
2//!
3//! By default the system VISA library is linked at build time. Enabling the
4//! `dynamic_load` feature instead loads it at run time via `libloading`, so the
5//! same binary can run with or without VISA installed. In that mode the library
6//! auto-loads on the first VISA call (or explicitly via `load_visa_library` /
7//! `load_visa_library_from_path`); the free VISA functions keep identical
8//! signatures in both modes. See the module docs and the crate README for the
9//! initialization model, limitations, and the build-time environment variables
10//! (`LIB_VISA_NAME`, `LIB_VISA_PATH`, `INCLUDE_VISA_PATH`).
11#![allow(non_upper_case_globals)]
12#![allow(non_camel_case_types)]
13#![allow(non_snake_case)]
14
15// The bindings (generated or pre-generated) are thin FFI surfaces: their `unsafe`
16// items carry no `# Safety` docs and some VISA calls take many arguments. Each
17// `include!` lives in a `mod` so those allows are scoped to the bindings rather
18// than applied crate-wide; `pub use` keeps the API flat at the crate root.
19#[cfg(feature = "bindgen")]
20#[allow(clippy::missing_safety_doc, clippy::too_many_arguments)]
21mod bindings {
22    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
23}
24
25#[cfg(all(not(feature = "bindgen"), feature = "dynamic_load"))]
26#[allow(clippy::missing_safety_doc, clippy::too_many_arguments)]
27mod bindings {
28    include!("./prebind/bindings_dynamic.rs");
29}
30
31#[cfg(all(not(feature = "bindgen"), not(feature = "dynamic_load")))]
32#[allow(clippy::missing_safety_doc, clippy::too_many_arguments)]
33mod bindings {
34    include!("./prebind/bindings.rs");
35}
36
37pub use bindings::*;
38
39#[cfg(feature = "dynamic_load")]
40mod dynamic_loading;
41
42#[cfg(feature = "dynamic_load")]
43pub use dynamic_loading::*;
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    // Hardware-free: exercises the dynamic-loading plumbing without touching the
50    // global singleton (so it is order-independent and runs anywhere). Loading a
51    // missing library must return `Err`, not panic.
52    #[cfg(feature = "dynamic_load")]
53    #[test]
54    fn load_missing_library_is_err() {
55        let r = unsafe { LibVisa::new("nonexistent-visa-library-xyz123") };
56        assert!(r.is_err(), "loading a missing library should return Err");
57    }
58
59    // Real end-to-end exercise of the `va_list` path: `viVSPrintf` reads an
60    // argument *through* the `va_list` we pass and formats it into a buffer, so a
61    // correct result proves the `VaListArg` conversion forwards the list to the
62    // loaded library intact. `viVSPrintf` validates the session and needs an
63    // instrument one (it formats into our buffer, no device traffic), so we open
64    // the first discoverable resource and skip if none is present. The hand-built
65    // `va_list` is a pointer to a packed argument area, matching the aarch64-macOS
66    // ABI; gated to that host so the layout is valid and verifiable directly.
67    #[cfg(all(feature = "dynamic_load", target_os = "macos", target_arch = "aarch64"))]
68    #[test]
69    fn vsprintf_reads_va_list_arg() {
70        use std::ffi::{CStr, CString};
71        unsafe {
72            let mut rm: ViSession = 0;
73            assert_eq!(
74                viOpenDefaultRM(&mut rm as ViPSession),
75                VI_SUCCESS as ViStatus
76            );
77
78            let mut find: ViFindList = 0;
79            let mut cnt: ViUInt32 = 0;
80            let mut desc = [0 as ViChar; VI_FIND_BUFLEN as usize];
81            let expr = CString::new("?*INSTR").unwrap();
82            let found = viFindRsrc(rm, expr.as_ptr(), &mut find, &mut cnt, desc.as_mut_ptr());
83            if found != VI_SUCCESS as ViStatus || cnt == 0 {
84                viClose(rm);
85                return; // no instrument available; nothing to exercise
86            }
87            let mut instr: ViSession = 0;
88            if viOpen(
89                rm,
90                desc.as_ptr(),
91                VI_NULL as ViAccessMode,
92                VI_NULL as ViUInt32,
93                &mut instr as ViPSession,
94            ) != VI_SUCCESS as ViStatus
95            {
96                viClose(rm);
97                return; // could not open the resource; skip
98            }
99
100            // Three `%d` arguments, so the library must `va_arg` three times,
101            // advancing through the list. On aarch64 macOS each variadic argument
102            // occupies an 8-byte slot in the `va_list`; an `int` sits in the low
103            // 4 bytes (little-endian), so the layout is one `u64` slot per value.
104            let mut args: [u64; 3] = [1, 22, 333];
105            let mut out = [0u8; 64];
106            let st = viVSPrintf(
107                instr,
108                out.as_mut_ptr() as ViPBuf,
109                c"%d %d %d".as_ptr() as ViConstString,
110                args.as_mut_ptr() as ViVAList,
111            );
112            viClose(instr);
113            viClose(rm);
114
115            assert_eq!(st, VI_SUCCESS as ViStatus, "viVSPrintf returned an error");
116            let got = CStr::from_ptr(out.as_ptr() as *const std::os::raw::c_char)
117                .to_str()
118                .unwrap();
119            assert_eq!(got, "1 22 333", "va_list arguments not read correctly");
120        }
121    }
122
123    #[test]
124    fn open_default_RM() {
125        let mut session: ViSession = 0;
126        unsafe {
127            assert_eq!(viOpenDefaultRM(&mut session as ViPSession), VI_SUCCESS as _,);
128        }
129    }
130    #[test]
131    fn find_resource() {
132        let mut defaultRM: ViSession = 0;
133        let mut find_list: ViFindList = 0;
134        let mut ret_cnt: ViUInt32 = 0;
135        let mut instr_desc: [ViChar; VI_FIND_BUFLEN as usize] = [0; VI_FIND_BUFLEN as usize];
136        unsafe {
137            assert_eq!(
138                viOpenDefaultRM(&mut defaultRM as ViPSession),
139                VI_SUCCESS as _,
140                "Could not open a session to the VISA Resource Manager!\n"
141            );
142            let expr = std::ffi::CString::new(*b"?*INSTR").unwrap();
143            assert_eq!(
144                viFindRsrc(
145                    defaultRM,
146                    expr.as_ptr(),
147                    &mut find_list,
148                    &mut ret_cnt,
149                    &mut instr_desc as _
150                ),
151                VI_SUCCESS as _,
152                "An error occurred while finding resources.\n"
153            );
154            eprintln!(
155                "{} instruments, serial ports, and other resources found:",
156                ret_cnt,
157            );
158            let mut instr: ViSession = 0;
159
160            loop {
161                eprintln!("{} targets to connect", ret_cnt);
162                let resource = instr_desc
163                    .into_iter()
164                    .map(|x| x as u8 as char)
165                    .collect::<String>();
166                eprintln!("connecting to '{}'", resource);
167                let open_status = viOpen(
168                    defaultRM,
169                    &instr_desc as _,
170                    VI_NULL as _,
171                    VI_NULL as _,
172                    &mut instr as _,
173                );
174                if open_status != VI_SUCCESS as _ {
175                    eprintln!(
176                        "An error '{}' occurred opening a session to '{}'\n",
177                        open_status,
178                        instr_desc
179                            .into_iter()
180                            .map(|x| x as u8 as char)
181                            .collect::<String>()
182                    );
183                } else {
184                    eprintln!("connected to '{}'", resource);
185                    viClose(instr);
186                }
187                ret_cnt -= 1;
188                if ret_cnt > 0 {
189                    let find_status = viFindNext(find_list, &mut instr_desc as _);
190                    if find_status != VI_SUCCESS as _ {
191                        eprintln!(
192                            "An error '{}' occurred finding the next resource\n.",
193                            find_status
194                        );
195                        break;
196                    }
197                } else {
198                    break;
199                }
200            }
201            viClose(defaultRM);
202        }
203    }
204}