Skip to main content

pylon_shimload/
lib.rs

1//! Runtime-loaded Rust bindings for Basler Pylon cameras.
2//!
3//! This crate loads a C ABI shim library at runtime. The shim library links
4//! against the Pylon C++ SDK. This allows the crate to be used without linking
5//! to the Pylon library at compile time, and also allows it to be used with
6//! different versions of the Pylon library without recompilation. Set the
7//! `PYLON_CABI` environment variable to point at the shim if it is not
8//! installed in a standard library location.
9
10use std::ffi::{c_char, c_int, c_void};
11
12mod runtime_impl;
13mod shim_loader;
14
15#[cfg(all(not(target_os = "windows"), feature = "stream"))]
16mod stream_unix;
17
18#[cfg(feature = "stream")]
19use std::cell::RefCell;
20
21#[cfg(all(target_os = "windows", feature = "stream"))]
22use std::thread::JoinHandle;
23
24#[cfg(all(target_os = "windows", feature = "stream"))]
25mod stream_windows;
26
27pub(crate) const EXPECTED_CABI_VERSION: u32 = 1;
28
29// =========================================================================
30// Error type
31// =========================================================================
32
33/// Errors returned by this crate.
34#[derive(Debug, Clone)]
35#[non_exhaustive]
36pub enum PylonError {
37    /// A general error message produced by this crate.
38    Msg(String),
39    /// A shim call returned an error string.
40    ShimCallFailed {
41        /// Logical shim operation name.
42        op: &'static str,
43        /// Rust callsite where the failing shim function was invoked.
44        callsite: String,
45        /// Error message returned by the shim.
46        err_str: String,
47    },
48    /// A shim call reported success but returned invalid output.
49    InvalidShimOutput {
50        /// Logical shim operation name.
51        op: &'static str,
52        /// Description of which output invariant failed.
53        detail: String,
54    },
55    /// The shim library could not be opened.
56    DlOpenFailed {
57        /// The attempted shim library path.
58        path: std::ffi::OsString,
59        source: String,
60    },
61    /// A lower-level error reported while loading or validating the shim.
62    ShimError(ShimError),
63}
64
65#[derive(Debug, Clone)]
66#[non_exhaustive]
67/// Errors encountered while loading the C ABI shim.
68pub enum ShimError {
69    /// A required symbol could not be loaded from the shim.
70    SymbolLoadFailed {
71        /// The shim library path.
72        path: std::ffi::OsString,
73        /// The missing or invalid symbol name.
74        symbol: String,
75        /// The dynamic loader error string.
76        err_str: String,
77    },
78    /// The shim returned a null API table pointer.
79    NullApi {
80        /// The shim library path.
81        path: std::ffi::OsString,
82    },
83    /// The shim API table is smaller than this crate expects.
84    ApiTableTooSmall {
85        /// The shim library path.
86        path: std::ffi::OsString,
87        /// The API table size reported by the shim.
88        got: u32,
89        /// The minimum API table size required by this crate.
90        need: u32,
91    },
92    /// The shim ABI version does not match this crate.
93    IncompatibleAbiVersion {
94        /// The shim library path.
95        path: std::ffi::OsString,
96        /// The ABI version reported by the shim.
97        got: u32,
98        /// The ABI version required by this crate.
99        need: u32,
100    },
101}
102
103impl PylonError {
104    fn new(msg: String) -> Self {
105        PylonError::Msg(msg)
106    }
107}
108
109impl From<std::str::Utf8Error> for PylonError {
110    fn from(_: std::str::Utf8Error) -> PylonError {
111        PylonError::new("Cannot convert C++ string to UTF-8".to_string())
112    }
113}
114
115impl From<std::io::Error> for PylonError {
116    fn from(orig: std::io::Error) -> PylonError {
117        PylonError::new(orig.to_string())
118    }
119}
120
121impl std::fmt::Display for PylonError {
122    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
123        match self {
124            PylonError::Msg(msg) => write!(f, "PylonError({})", msg),
125            PylonError::ShimCallFailed {
126                op,
127                callsite,
128                err_str,
129            } => write!(
130                f,
131                "PylonError(ShimCallFailed {op} at {callsite}: {err_str})"
132            ),
133            PylonError::InvalidShimOutput { op, detail } => {
134                write!(f, "PylonError(InvalidShimOutput {op}: {detail})")
135            }
136            PylonError::DlOpenFailed { path,  source } => write!(
137                f,
138                "There was a problem opening the `libpylon-cabi` shim library. The path was specified \
139                as {path:?}. You can force a specific path \
140                to the shim library using the `PYLON_CABI` environment variable. Shim libraries can \
141                be downloaded from https://strawlab.org/assets/libpylon-cabi/precompiled/ or built \
142                from source. You need v{EXPECTED_CABI_VERSION} of the shim library for this version of `pylon-shimload`.\
143                \n\nThe source of the error was:\n\n\
144                {source}"
145            ),
146            PylonError::ShimError(err) => write!(f, "PylonError(ShimError: {err:?})"),
147        }
148    }
149}
150
151impl std::error::Error for PylonError {}
152
153/// Convenient result type used throughout the crate.
154pub type PylonResult<T> = Result<T, PylonError>;
155
156// =========================================================================
157// Public enums
158// =========================================================================
159
160#[repr(i32)]
161#[derive(Clone, Copy, Debug, PartialEq, Eq)]
162/// How timeout conditions are reported by grab operations.
163pub enum TimeoutHandling {
164    /// Return `Ok(false)` when the timeout expires.
165    Return = 0,
166    /// Convert the timeout into an error reported by Pylon.
167    ThrowException = 1,
168}
169
170#[repr(i32)]
171#[derive(Clone, Copy, Debug, PartialEq, Eq)]
172/// Buffer handling strategy used while grabbing.
173pub enum GrabStrategy {
174    /// Deliver every image in order.
175    OneByOne = 0,
176    /// Keep only the most recent image.
177    LatestImageOnly = 1,
178    /// Keep a rolling set of recent images.
179    LatestImages = 2,
180    /// Wait for the next image that arrives after the call.
181    UpcomingImage = 3,
182}
183
184// =========================================================================
185// Internal helpers (shim-backed)
186// =========================================================================
187
188#[inline]
189#[track_caller]
190fn shim_check_op(op: &'static str, err: *const c_char) -> PylonResult<()> {
191    shim_loader::check_err(err).map_err(|err_str| {
192        let loc = std::panic::Location::caller();
193        PylonError::ShimCallFailed {
194            op,
195            callsite: format!("{}:{}", loc.file(), loc.line()),
196            err_str,
197        }
198    })
199}
200
201#[inline]
202#[track_caller]
203fn shim_check(err: *const c_char) -> PylonResult<()> {
204    shim_check_op("shim_call", err)
205}
206
207#[inline]
208fn ensure_non_null<T>(op: &'static str, what: &'static str, ptr: *const T) -> PylonResult<()> {
209    if ptr.is_null() {
210        Err(PylonError::InvalidShimOutput {
211            op,
212            detail: format!("{what} was null"),
213        })
214    } else {
215        Ok(())
216    }
217}
218
219macro_rules! shim_call {
220    ($shim:expr, $func:ident($($arg:expr),* $(,)?)) => {{
221        let err = unsafe { (($shim).$func)($($arg),*) };
222        shim_check_for!($func, err)
223    }};
224}
225
226macro_rules! shim_check_for {
227    ($op:ident, $err:expr) => {
228        shim_check_op(stringify!($op), $err)
229    };
230}
231
232macro_rules! ensure_non_null_for {
233    ($op:ident, $what:literal, $ptr:expr) => {
234        ensure_non_null(stringify!($op), $what, $ptr)
235    };
236}
237
238// =========================================================================
239// Public types
240// =========================================================================
241
242/// Pylon runtime lifecycle management.
243///
244/// Most callers do not need this module — the runtime initializes
245/// automatically on the first call to [`enumerate_devices`],
246/// [`create_first_device`], or [`create_device`], and shuts down when
247/// the last [`InstantCamera`] or [`DeviceInfo`] is dropped.
248///
249/// Use this module when you need explicit control: early initialization,
250/// keeping the runtime alive between handle lifetimes, or deterministic
251/// teardown.
252pub mod runtime {
253    use crate::{runtime_impl, PylonResult, PylonVersion};
254
255    /// Keeps the Pylon runtime alive for explicit lifecycle management.
256    ///
257    /// Most callers do not need this type.  The runtime is initialized
258    /// automatically on the first call to [`crate::enumerate_devices`],
259    /// [`crate::create_first_device`], or [`crate::create_device`], and is
260    /// kept alive as long as any [`crate::InstantCamera`] or
261    /// [`crate::DeviceInfo`] remains in scope.
262    ///
263    /// Obtain one via [`init`].
264    pub use crate::runtime_impl::RuntimeGuard;
265
266    /// Explicitly initializes the Pylon runtime and returns a guard that
267    /// keeps it alive until dropped.
268    ///
269    /// Calling this is **not required** before normal use — the runtime
270    /// initializes automatically on the first call to
271    /// [`crate::enumerate_devices`], [`crate::create_first_device`], or
272    /// [`crate::create_device`].
273    ///
274    /// Use `runtime::init()` when you need to:
275    /// * Detect a missing Pylon installation at startup, before opening any
276    ///   camera.
277    /// * Keep the runtime alive across a window where no camera handles
278    ///   exist.
279    /// * Drive deterministic teardown via [`shutdown`].
280    ///
281    /// The guard releases its hold on the runtime when dropped; if it is the
282    /// last holder, the runtime is terminated at that point.
283    pub fn init() -> PylonResult<RuntimeGuard> {
284        RuntimeGuard::new()
285    }
286
287    /// Returns the version of the loaded Pylon runtime.
288    ///
289    /// Initializes the runtime if it has not been initialized yet.
290    pub fn version() -> PylonResult<PylonVersion> {
291        runtime_impl::runtime_version()
292    }
293
294    /// Explicitly terminates the Pylon runtime.
295    ///
296    /// Returns an error if any [`crate::InstantCamera`], [`crate::DeviceInfo`],
297    /// or [`RuntimeGuard`] is still alive.  Intended for use alongside
298    /// [`init`] when deterministic teardown is required.
299    ///
300    /// Under normal usage the runtime terminates automatically when the last
301    /// handle is dropped, so calling this is not necessary.
302    pub fn shutdown() -> PylonResult<()> {
303        runtime_impl::shutdown()
304    }
305}
306
307/// Pylon version information.
308#[derive(Debug)]
309pub struct PylonVersion {
310    /// Major version number.
311    pub major: u32,
312    /// Minor version number.
313    pub minor: u32,
314    /// Patch-level version number.
315    pub subminor: u32,
316    /// Build number.
317    pub build: u32,
318}
319
320/// Returns the version of the loaded Pylon runtime.
321pub fn version() -> Result<PylonVersion, PylonError> {
322    runtime::version()
323}
324
325/// Enumerates all currently available camera devices.
326pub fn enumerate_devices() -> PylonResult<Vec<DeviceInfo>> {
327    let runtime = runtime_impl::acquire_runtime()?;
328    let mut raw_arr: *mut *mut c_void = std::ptr::null_mut();
329    let mut count: usize = 0;
330    shim_call!(
331        shim_loader::shim(),
332        tl_factory_enumerate_devices(&mut raw_arr, &mut count)
333    )?;
334    if count > 0 {
335        ensure_non_null_for!(tl_factory_enumerate_devices, "device array", raw_arr)?;
336    }
337    let mut result = Vec::with_capacity(count);
338    unsafe {
339        for i in 0..count {
340            result.push(DeviceInfo {
341                ptr: *raw_arr.add(i),
342                runtime: runtime.clone(),
343            });
344        }
345        (shim_loader::shim().pylon_cxx_free_ptr)(raw_arr as *mut _);
346    }
347    Ok(result)
348}
349
350/// Creates the first available camera device.
351pub fn create_first_device() -> PylonResult<InstantCamera> {
352    let runtime = runtime_impl::acquire_runtime()?;
353    let mut ptr: *mut c_void = std::ptr::null_mut();
354    shim_call!(
355        shim_loader::shim(),
356        tl_factory_create_first_device(&mut ptr)
357    )?;
358    ensure_non_null_for!(tl_factory_create_first_device, "camera pointer", ptr)?;
359    Ok(InstantCamera::from_ptr(runtime, ptr))
360}
361
362/// Creates a camera handle from previously discovered device info.
363pub fn create_device(device_info: &DeviceInfo) -> PylonResult<InstantCamera> {
364    let runtime = runtime_impl::acquire_runtime()?;
365    let mut ptr: *mut c_void = std::ptr::null_mut();
366    shim_call!(
367        shim_loader::shim(),
368        tl_factory_create_device(device_info.ptr, &mut ptr)
369    )?;
370    ensure_non_null_for!(tl_factory_create_device, "camera pointer", ptr)?;
371    Ok(InstantCamera::from_ptr(runtime, ptr))
372}
373
374// -------------------------------------------------------------------------
375// InstantCamera
376// -------------------------------------------------------------------------
377
378/// Camera handle used to open, configure, and grab from a device.
379pub struct InstantCamera {
380    ptr: *mut c_void,
381    #[cfg(all(not(target_os = "windows"), feature = "stream"))]
382    pub(crate) fd: RefCell<Option<tokio::io::unix::AsyncFd<std::os::unix::io::RawFd>>>,
383    #[cfg(all(target_os = "windows", feature = "stream"))]
384    pub(crate) wait_thread: RefCell<Option<JoinHandle<()>>>,
385    /// Kept last so that runtime lease outlives native pointer drops.
386    _runtime: runtime_impl::RuntimeLease,
387}
388
389unsafe impl Send for InstantCamera {}
390
391impl InstantCamera {
392    fn from_ptr(runtime: runtime_impl::RuntimeLease, ptr: *mut c_void) -> Self {
393        InstantCamera {
394            ptr,
395            _runtime: runtime,
396            #[cfg(all(not(target_os = "windows"), feature = "stream"))]
397            fd: RefCell::new(None),
398            #[cfg(all(target_os = "windows", feature = "stream"))]
399            wait_thread: RefCell::new(None),
400        }
401    }
402
403    /// Returns metadata for the attached device.
404    pub fn device_info(&self) -> PylonResult<DeviceInfo> {
405        {
406            let mut out: *mut c_void = std::ptr::null_mut();
407            shim_call!(
408                shim_loader::shim(),
409                instant_camera_get_device_info(self.ptr, &mut out)
410            )?;
411            ensure_non_null_for!(instant_camera_get_device_info, "device info pointer", out)?;
412            Ok(DeviceInfo {
413                ptr: out,
414                runtime: self._runtime.clone(),
415            })
416        }
417    }
418
419    /// Opens the camera.
420    pub fn open(&self) -> PylonResult<()> {
421        shim_call!(shim_loader::shim(), instant_camera_open(self.ptr))
422    }
423
424    /// Returns whether the camera is open.
425    pub fn is_open(&self) -> PylonResult<bool> {
426        {
427            let mut out: c_int = 0;
428            shim_call!(
429                shim_loader::shim(),
430                instant_camera_is_open(self.ptr, &mut out)
431            )?;
432            Ok(out != 0)
433        }
434    }
435
436    /// Closes the camera.
437    pub fn close(&self) -> PylonResult<()> {
438        shim_call!(shim_loader::shim(), instant_camera_close(self.ptr))
439    }
440
441    /// Starts grabbing using the supplied options.
442    pub fn start_grabbing(&self, options: &GrabOptions) -> PylonResult<()> {
443        {
444            // We assign the wait-object fd here for using it in the stream.
445            #[cfg(all(not(target_os = "windows"), feature = "stream"))]
446            {
447                if tokio::runtime::Handle::try_current().is_ok() {
448                    self.fd.replace(Some(tokio::io::unix::AsyncFd::new(
449                        self.get_grab_result_fd()?,
450                    )?));
451                }
452            }
453
454            let s = shim_loader::shim();
455            match (options.count, options.strategy) {
456                (Some(count), Some(strategy)) => shim_call!(
457                    s,
458                    instant_camera_start_grabbing_with_count_and_strategy(
459                        self.ptr,
460                        count,
461                        strategy as c_int
462                    )
463                ),
464                (Some(count), None) => {
465                    shim_call!(s, instant_camera_start_grabbing_with_count(self.ptr, count))
466                }
467                (None, Some(strategy)) => shim_call!(
468                    s,
469                    instant_camera_start_grabbing_with_strategy(self.ptr, strategy as c_int)
470                ),
471                (None, None) => shim_call!(s, instant_camera_start_grabbing(self.ptr)),
472            }
473        }
474    }
475
476    /// Stops any active grab operation.
477    pub fn stop_grabbing(&self) -> PylonResult<()> {
478        {
479            shim_call!(shim_loader::shim(), instant_camera_stop_grabbing(self.ptr))?;
480            #[cfg(all(not(target_os = "windows"), feature = "stream"))]
481            self.fd.replace(None);
482            #[cfg(all(target_os = "windows", feature = "stream"))]
483            self.wait_thread.replace(None);
484            Ok(())
485        }
486    }
487
488    /// Returns `true` while the camera is actively grabbing.
489    pub fn is_grabbing(&self) -> bool {
490        unsafe { (shim_loader::shim().instant_camera_is_grabbing)(self.ptr) != 0 }
491    }
492
493    /// Waits for the next grab result.
494    ///
495    /// Returns `Ok(true)` when a result was retrieved.
496    pub fn retrieve_result(
497        &self,
498        timeout_ms: u32,
499        grab_result: &mut GrabResult,
500        timeout_handling: TimeoutHandling,
501    ) -> PylonResult<bool> {
502        {
503            let mut grabbed: c_int = 0;
504            let err = unsafe {
505                (shim_loader::shim().instant_camera_retrieve_result)(
506                    self.ptr,
507                    timeout_ms,
508                    grab_result.ptr,
509                    timeout_handling as c_int,
510                    &mut grabbed,
511                )
512            };
513            shim_check_for!(instant_camera_retrieve_result, err)?;
514            Ok(grabbed != 0)
515        }
516    }
517
518    #[cfg(all(not(target_os = "windows"), feature = "stream"))]
519    /// Returns the wait-object file descriptor used by the `stream` feature.
520    pub fn get_grab_result_fd(&self) -> PylonResult<std::os::unix::io::RawFd> {
521        {
522            let mut fd: c_int = -1;
523            shim_call!(
524                shim_loader::shim(),
525                instant_camera_wait_object_fd(self.ptr, &mut fd)
526            )?;
527            Ok(fd)
528        }
529    }
530
531    #[cfg(all(target_os = "windows", feature = "stream"))]
532    pub(crate) fn get_grab_result_wait_object(&self) -> PylonResult<WaitObject> {
533        {
534            let mut ptr: *mut c_void = std::ptr::null_mut();
535            shim_call!(
536                shim_loader::shim(),
537                instant_camera_wait_object(self.ptr, &mut ptr)
538            )?;
539            ensure_non_null_for!(instant_camera_wait_object, "wait object pointer", ptr)?;
540            Ok(WaitObject(ptr))
541        }
542    }
543}
544
545impl Drop for InstantCamera {
546    fn drop(&mut self) {
547        if !self.ptr.is_null() {
548            unsafe { (shim_loader::shim().instant_camera_destroy)(self.ptr) }
549        }
550    }
551}
552
553// --- NodeMap ---------------------------------------------------------------
554
555/// Borrowed GenICam node map tied to the lifetime of its parent object.
556pub struct NodeMap<'parent> {
557    ptr: *const c_void,
558    _marker: std::marker::PhantomData<&'parent ()>,
559}
560
561impl InstantCamera {
562    /// Returns the camera node map.
563    pub fn node_map<'a>(&'a self) -> PylonResult<NodeMap<'a>> {
564        {
565            let mut ptr: *const c_void = std::ptr::null();
566            let err =
567                unsafe { (shim_loader::shim().instant_camera_get_node_map)(self.ptr, &mut ptr) };
568            shim_check_for!(instant_camera_get_node_map, err)?;
569            ensure_non_null_for!(instant_camera_get_node_map, "node map pointer", ptr)?;
570            Ok(NodeMap {
571                ptr,
572                _marker: std::marker::PhantomData,
573            })
574        }
575    }
576    /// Returns the transport-layer node map.
577    pub fn tl_node_map<'a>(&'a self) -> PylonResult<NodeMap<'a>> {
578        {
579            let mut ptr: *const c_void = std::ptr::null();
580            let err =
581                unsafe { (shim_loader::shim().instant_camera_get_tl_node_map)(self.ptr, &mut ptr) };
582            shim_check_for!(instant_camera_get_tl_node_map, err)?;
583            ensure_non_null_for!(instant_camera_get_tl_node_map, "tl node map pointer", ptr)?;
584            Ok(NodeMap {
585                ptr,
586                _marker: std::marker::PhantomData,
587            })
588        }
589    }
590    /// Returns the stream-grabber node map.
591    pub fn stream_grabber_node_map<'a>(&'a self) -> PylonResult<NodeMap<'a>> {
592        {
593            let mut ptr: *const c_void = std::ptr::null();
594            let err = unsafe {
595                (shim_loader::shim().instant_camera_get_stream_grabber_node_map)(self.ptr, &mut ptr)
596            };
597            shim_check_for!(instant_camera_get_stream_grabber_node_map, err)?;
598            ensure_non_null(
599                stringify!(instant_camera_get_stream_grabber_node_map),
600                "stream grabber node map pointer",
601                ptr,
602            )?;
603            Ok(NodeMap {
604                ptr,
605                _marker: std::marker::PhantomData,
606            })
607        }
608    }
609    /// Returns the event-grabber node map.
610    pub fn event_grabber_node_map<'a>(&'a self) -> PylonResult<NodeMap<'a>> {
611        {
612            let mut ptr: *const c_void = std::ptr::null();
613            let err = unsafe {
614                (shim_loader::shim().instant_camera_get_event_grabber_node_map)(self.ptr, &mut ptr)
615            };
616            shim_check_for!(instant_camera_get_event_grabber_node_map, err)?;
617            ensure_non_null(
618                stringify!(instant_camera_get_event_grabber_node_map),
619                "event grabber node map pointer",
620                ptr,
621            )?;
622            Ok(NodeMap {
623                ptr,
624                _marker: std::marker::PhantomData,
625            })
626        }
627    }
628    /// Returns the instant-camera node map.
629    pub fn instant_camera_node_map<'a>(&'a self) -> PylonResult<NodeMap<'a>> {
630        {
631            let mut ptr: *const c_void = std::ptr::null();
632            let err = unsafe {
633                (shim_loader::shim().instant_camera_get_instant_camera_node_map)(self.ptr, &mut ptr)
634            };
635            shim_check_for!(instant_camera_get_instant_camera_node_map, err)?;
636            ensure_non_null(
637                stringify!(instant_camera_get_instant_camera_node_map),
638                "instant camera node map pointer",
639                ptr,
640            )?;
641            Ok(NodeMap {
642                ptr,
643                _marker: std::marker::PhantomData,
644            })
645        }
646    }
647}
648
649impl<'parent> NodeMap<'parent> {
650    /// Loads feature settings from a file.
651    pub fn load<P: AsRef<std::path::Path>>(&self, path: P, validate: bool) -> PylonResult<()> {
652        {
653            let filename = path_to_string(path)?;
654            let err = unsafe {
655                (shim_loader::shim().node_map_load)(
656                    self.ptr,
657                    filename.as_ptr() as *const c_char,
658                    filename.len(),
659                    validate as c_int,
660                )
661            };
662            shim_check(err)
663        }
664    }
665    /// Saves feature settings to a file.
666    pub fn save<P: AsRef<std::path::Path>>(&self, path: P) -> PylonResult<()> {
667        {
668            let filename = path_to_string(path)?;
669            let err = unsafe {
670                (shim_loader::shim().node_map_save)(
671                    self.ptr,
672                    filename.as_ptr() as *const c_char,
673                    filename.len(),
674                )
675            };
676            shim_check(err)
677        }
678    }
679    /// Loads feature settings from a serialized string.
680    pub fn load_from_string(&self, features: String, validate: bool) -> PylonResult<()> {
681        {
682            let err = unsafe {
683                (shim_loader::shim().node_map_load_from_string)(
684                    self.ptr,
685                    features.as_ptr() as *const c_char,
686                    features.len(),
687                    validate as c_int,
688                )
689            };
690            shim_check(err)
691        }
692    }
693    /// Serializes the node map into a string.
694    pub fn save_to_string(&self) -> PylonResult<String> {
695        {
696            let mut out: *mut c_char = std::ptr::null_mut();
697            let err = unsafe { (shim_loader::shim().node_map_save_to_string)(self.ptr, &mut out) };
698            shim_check_for!(node_map_save_to_string, err)?;
699            ensure_non_null_for!(node_map_save_to_string, "serialized node map string", out)?;
700            Ok(unsafe { shim_loader::take_str(out) })
701        }
702    }
703
704    fn get_param_raw(
705        &self,
706        name: &str,
707        getter: unsafe extern "C" fn(
708            *const c_void,
709            *const c_char,
710            usize,
711            *mut *mut c_void,
712        ) -> *const c_char,
713    ) -> PylonResult<*mut c_void> {
714        {
715            let mut out: *mut c_void = std::ptr::null_mut();
716            let err = unsafe {
717                getter(
718                    self.ptr,
719                    name.as_ptr() as *const c_char,
720                    name.len(),
721                    &mut out,
722                )
723            };
724            shim_check(err)?;
725            ensure_non_null_for!(node_map_get_parameter, "parameter pointer", out)?;
726            Ok(out)
727        }
728    }
729
730    /// Returns a boolean node by name.
731    pub fn boolean_node(&self, name: &str) -> PylonResult<BooleanNode> {
732        Ok(BooleanNode {
733            name: name.to_string(),
734            ptr: self.get_param_raw(name, shim_loader::shim().node_map_get_boolean_parameter)?,
735        })
736    }
737    /// Returns an integer node by name.
738    pub fn integer_node(&self, name: &str) -> PylonResult<IntegerNode> {
739        Ok(IntegerNode {
740            name: name.to_string(),
741            ptr: self.get_param_raw(name, shim_loader::shim().node_map_get_integer_parameter)?,
742        })
743    }
744    /// Returns a floating-point node by name.
745    pub fn float_node(&self, name: &str) -> PylonResult<FloatNode> {
746        Ok(FloatNode {
747            name: name.to_string(),
748            ptr: self.get_param_raw(name, shim_loader::shim().node_map_get_float_parameter)?,
749        })
750    }
751    /// Returns an enum node by name.
752    pub fn enum_node(&self, name: &str) -> PylonResult<EnumNode> {
753        Ok(EnumNode {
754            name: name.to_string(),
755            ptr: self.get_param_raw(name, shim_loader::shim().node_map_get_enum_parameter)?,
756        })
757    }
758    /// Returns a command node by name.
759    pub fn command_node(&self, name: &str) -> PylonResult<CommandNode> {
760        Ok(CommandNode {
761            name: name.to_string(),
762            ptr: self.get_param_raw(name, shim_loader::shim().node_map_get_command_parameter)?,
763        })
764    }
765}
766
767// =========================================================================
768// GrabOptions
769// =========================================================================
770
771#[derive(Default)]
772/// Options passed to [`InstantCamera::start_grabbing`].
773pub struct GrabOptions {
774    count: Option<u32>,
775    strategy: Option<GrabStrategy>,
776}
777
778impl GrabOptions {
779    /// Limits the grab to a fixed number of images.
780    pub fn count(self, count: u32) -> GrabOptions {
781        Self {
782            count: Some(count),
783            ..self
784        }
785    }
786    /// Selects the buffer handling strategy.
787    pub fn strategy(self, strategy: GrabStrategy) -> GrabOptions {
788        Self {
789            strategy: Some(strategy),
790            ..self
791        }
792    }
793}
794
795// =========================================================================
796// Parameter nodes
797// =========================================================================
798
799/// Boolean-valued GenICam parameter.
800pub struct BooleanNode {
801    name: String,
802    ptr: *mut c_void,
803}
804
805impl BooleanNode {
806    /// Returns the node name.
807    pub fn name(&self) -> &str {
808        &self.name
809    }
810
811    /// Returns the current value.
812    pub fn value(&self) -> PylonResult<bool> {
813        {
814            let mut out: c_int = 0;
815            let err = unsafe { (shim_loader::shim().boolean_node_get_value)(self.ptr, &mut out) };
816            shim_check(err)?;
817            Ok(out != 0)
818        }
819    }
820    /// Sets the current value.
821    pub fn set_value(&mut self, value: bool) -> PylonResult<()> {
822        shim_check(unsafe {
823            (shim_loader::shim().boolean_node_set_value)(self.ptr, value as c_int)
824        })
825    }
826}
827
828impl Drop for BooleanNode {
829    fn drop(&mut self) {
830        if !self.ptr.is_null() {
831            unsafe { (shim_loader::shim().boolean_parameter_destroy)(self.ptr) }
832        }
833    }
834}
835
836/// Integer-valued GenICam parameter.
837pub struct IntegerNode {
838    name: String,
839    ptr: *mut c_void,
840}
841
842impl IntegerNode {
843    /// Returns the node name.
844    pub fn name(&self) -> &str {
845        &self.name
846    }
847
848    /// Returns the engineering unit string.
849    pub fn unit(&self) -> PylonResult<String> {
850        {
851            let mut out: *mut c_char = std::ptr::null_mut();
852            let err = unsafe { (shim_loader::shim().integer_node_get_unit)(self.ptr, &mut out) };
853            shim_check_for!(integer_node_get_unit, err)?;
854            ensure_non_null_for!(integer_node_get_unit, "unit string", out)?;
855            Ok(unsafe { shim_loader::take_str(out) })
856        }
857    }
858    /// Returns the current value.
859    pub fn value(&self) -> PylonResult<i64> {
860        {
861            let mut out = 0i64;
862            let err = unsafe { (shim_loader::shim().integer_node_get_value)(self.ptr, &mut out) };
863            shim_check(err)?;
864            Ok(out)
865        }
866    }
867    /// Returns the minimum allowed value.
868    pub fn min(&self) -> PylonResult<i64> {
869        {
870            let mut out = 0i64;
871            let err = unsafe { (shim_loader::shim().integer_node_get_min)(self.ptr, &mut out) };
872            shim_check(err)?;
873            Ok(out)
874        }
875    }
876    /// Returns the maximum allowed value.
877    pub fn max(&self) -> PylonResult<i64> {
878        {
879            let mut out = 0i64;
880            let err = unsafe { (shim_loader::shim().integer_node_get_max)(self.ptr, &mut out) };
881            shim_check(err)?;
882            Ok(out)
883        }
884    }
885    /// Sets the current value.
886    pub fn set_value(&mut self, value: i64) -> PylonResult<()> {
887        shim_check(unsafe { (shim_loader::shim().integer_node_set_value)(self.ptr, value) })
888    }
889}
890
891impl Drop for IntegerNode {
892    fn drop(&mut self) {
893        if !self.ptr.is_null() {
894            unsafe { (shim_loader::shim().integer_parameter_destroy)(self.ptr) }
895        }
896    }
897}
898
899/// Floating-point GenICam parameter.
900pub struct FloatNode {
901    name: String,
902    ptr: *mut c_void,
903}
904
905impl FloatNode {
906    /// Returns the node name.
907    pub fn name(&self) -> &str {
908        &self.name
909    }
910
911    /// Returns the engineering unit string.
912    pub fn unit(&self) -> PylonResult<String> {
913        {
914            let mut out: *mut c_char = std::ptr::null_mut();
915            let err = unsafe { (shim_loader::shim().float_node_get_unit)(self.ptr, &mut out) };
916            shim_check_for!(float_node_get_unit, err)?;
917            ensure_non_null_for!(float_node_get_unit, "unit string", out)?;
918            Ok(unsafe { shim_loader::take_str(out) })
919        }
920    }
921    /// Returns the current value.
922    pub fn value(&self) -> PylonResult<f64> {
923        {
924            let mut out = 0f64;
925            let err = unsafe { (shim_loader::shim().float_node_get_value)(self.ptr, &mut out) };
926            shim_check(err)?;
927            Ok(out)
928        }
929    }
930    /// Returns the minimum allowed value.
931    pub fn min(&self) -> PylonResult<f64> {
932        {
933            let mut out = 0f64;
934            let err = unsafe { (shim_loader::shim().float_node_get_min)(self.ptr, &mut out) };
935            shim_check(err)?;
936            Ok(out)
937        }
938    }
939    /// Returns the maximum allowed value.
940    pub fn max(&self) -> PylonResult<f64> {
941        {
942            let mut out = 0f64;
943            let err = unsafe { (shim_loader::shim().float_node_get_max)(self.ptr, &mut out) };
944            shim_check(err)?;
945            Ok(out)
946        }
947    }
948    /// Sets the current value.
949    pub fn set_value(&mut self, value: f64) -> PylonResult<()> {
950        shim_check(unsafe { (shim_loader::shim().float_node_set_value)(self.ptr, value) })
951    }
952}
953
954impl Drop for FloatNode {
955    fn drop(&mut self) {
956        if !self.ptr.is_null() {
957            unsafe { (shim_loader::shim().float_parameter_destroy)(self.ptr) }
958        }
959    }
960}
961
962/// Enum-valued GenICam parameter.
963pub struct EnumNode {
964    name: String,
965    ptr: *mut c_void,
966}
967
968impl EnumNode {
969    /// Returns the node name.
970    pub fn name(&self) -> &str {
971        &self.name
972    }
973
974    /// Returns the currently selected entry.
975    pub fn value(&self) -> PylonResult<String> {
976        {
977            let mut out: *mut c_char = std::ptr::null_mut();
978            let err = unsafe { (shim_loader::shim().enum_node_get_value)(self.ptr, &mut out) };
979            shim_check_for!(enum_node_get_value, err)?;
980            ensure_non_null_for!(enum_node_get_value, "enum value string", out)?;
981            Ok(unsafe { shim_loader::take_str(out) })
982        }
983    }
984    /// Returns the values currently accepted by the node.
985    pub fn settable_values(&self) -> PylonResult<Vec<String>> {
986        {
987            let mut arr: *mut *mut c_char = std::ptr::null_mut();
988            let mut count: usize = 0;
989            let err = unsafe {
990                (shim_loader::shim().enum_node_settable_values)(self.ptr, &mut arr, &mut count)
991            };
992            shim_check_for!(enum_node_settable_values, err)?;
993            if count > 0 {
994                ensure_non_null_for!(enum_node_settable_values, "settable values array", arr)?;
995            }
996            let mut result = Vec::with_capacity(count);
997            unsafe {
998                for i in 0..count {
999                    let s = std::ffi::CStr::from_ptr(*arr.add(i))
1000                        .to_string_lossy()
1001                        .into_owned();
1002                    result.push(s);
1003                }
1004                (shim_loader::shim().enum_node_free_settable_values)(arr, count);
1005            }
1006            Ok(result)
1007        }
1008    }
1009    /// Selects a new enum entry by name.
1010    pub fn set_value(&mut self, value: &str) -> PylonResult<()> {
1011        shim_check(unsafe {
1012            (shim_loader::shim().enum_node_set_value)(
1013                self.ptr,
1014                value.as_ptr() as *const c_char,
1015                value.len(),
1016            )
1017        })
1018    }
1019}
1020
1021impl Drop for EnumNode {
1022    fn drop(&mut self) {
1023        if !self.ptr.is_null() {
1024            unsafe { (shim_loader::shim().enum_parameter_destroy)(self.ptr) }
1025        }
1026    }
1027}
1028
1029/// Command-like GenICam parameter.
1030pub struct CommandNode {
1031    name: String,
1032    ptr: *mut c_void,
1033}
1034
1035impl CommandNode {
1036    /// Returns the node name.
1037    pub fn name(&self) -> &str {
1038        &self.name
1039    }
1040
1041    /// Executes the command.
1042    pub fn execute(&self, verify: bool) -> PylonResult<()> {
1043        shim_check(unsafe { (shim_loader::shim().command_node_execute)(self.ptr, verify as c_int) })
1044    }
1045}
1046
1047impl Drop for CommandNode {
1048    fn drop(&mut self) {
1049        if !self.ptr.is_null() {
1050            unsafe { (shim_loader::shim().command_parameter_destroy)(self.ptr) }
1051        }
1052    }
1053}
1054
1055// =========================================================================
1056// GrabResult
1057// =========================================================================
1058
1059/// Reusable container for a single grab result.
1060pub struct GrabResult {
1061    ptr: *mut c_void,
1062}
1063
1064unsafe impl Send for GrabResult {}
1065
1066impl GrabResult {
1067    /// Allocates an empty grab result.
1068    pub fn new() -> PylonResult<Self> {
1069        {
1070            let mut ptr: *mut c_void = std::ptr::null_mut();
1071            let err = unsafe { (shim_loader::shim().new_grab_result_ptr)(&mut ptr) };
1072            shim_check(err)?;
1073            Ok(GrabResult { ptr })
1074        }
1075    }
1076
1077    /// Returns whether the grab completed successfully.
1078    pub fn grab_succeeded(&self) -> PylonResult<bool> {
1079        {
1080            let mut out: c_int = 0;
1081            let err =
1082                unsafe { (shim_loader::shim().grab_result_grab_succeeded)(self.ptr, &mut out) };
1083            shim_check(err)?;
1084            Ok(out != 0)
1085        }
1086    }
1087    /// Returns the failure message for an unsuccessful grab.
1088    pub fn error_description(&self) -> PylonResult<String> {
1089        {
1090            let mut out: *mut c_char = std::ptr::null_mut();
1091            let err =
1092                unsafe { (shim_loader::shim().grab_result_error_description)(self.ptr, &mut out) };
1093            shim_check_for!(grab_result_error_description, err)?;
1094            ensure_non_null_for!(
1095                grab_result_error_description,
1096                "error description string",
1097                out
1098            )?;
1099            Ok(unsafe { shim_loader::take_str(out) })
1100        }
1101    }
1102    /// Returns the camera-specific error code for an unsuccessful grab.
1103    pub fn error_code(&self) -> PylonResult<u32> {
1104        {
1105            let mut out = 0u32;
1106            let err = unsafe { (shim_loader::shim().grab_result_error_code)(self.ptr, &mut out) };
1107            shim_check(err)?;
1108            Ok(out)
1109        }
1110    }
1111    /// Returns the image width in pixels.
1112    pub fn width(&self) -> PylonResult<u32> {
1113        {
1114            let mut out = 0u32;
1115            shim_check(unsafe { (shim_loader::shim().grab_result_width)(self.ptr, &mut out) })?;
1116            Ok(out)
1117        }
1118    }
1119    /// Returns the image height in pixels.
1120    pub fn height(&self) -> PylonResult<u32> {
1121        {
1122            let mut out = 0u32;
1123            shim_check(unsafe { (shim_loader::shim().grab_result_height)(self.ptr, &mut out) })?;
1124            Ok(out)
1125        }
1126    }
1127    /// Returns the horizontal image offset.
1128    pub fn offset_x(&self) -> PylonResult<u32> {
1129        {
1130            let mut out = 0u32;
1131            shim_check(unsafe { (shim_loader::shim().grab_result_offset_x)(self.ptr, &mut out) })?;
1132            Ok(out)
1133        }
1134    }
1135    /// Returns the vertical image offset.
1136    pub fn offset_y(&self) -> PylonResult<u32> {
1137        {
1138            let mut out = 0u32;
1139            shim_check(unsafe { (shim_loader::shim().grab_result_offset_y)(self.ptr, &mut out) })?;
1140            Ok(out)
1141        }
1142    }
1143    /// Returns the horizontal padding in bytes.
1144    pub fn padding_x(&self) -> PylonResult<u32> {
1145        {
1146            let mut out = 0u32;
1147            shim_check(unsafe { (shim_loader::shim().grab_result_padding_x)(self.ptr, &mut out) })?;
1148            Ok(out)
1149        }
1150    }
1151    /// Returns the vertical padding in bytes.
1152    pub fn padding_y(&self) -> PylonResult<u32> {
1153        {
1154            let mut out = 0u32;
1155            shim_check(unsafe { (shim_loader::shim().grab_result_padding_y)(self.ptr, &mut out) })?;
1156            Ok(out)
1157        }
1158    }
1159    /// Returns the image buffer.
1160    pub fn buffer(&self) -> PylonResult<&[u8]> {
1161        {
1162            let mut buf: *const u8 = std::ptr::null();
1163            let mut len: usize = 0;
1164            let err =
1165                unsafe { (shim_loader::shim().grab_result_buffer)(self.ptr, &mut buf, &mut len) };
1166            shim_check_for!(grab_result_buffer, err)?;
1167            if len > 0 {
1168                ensure_non_null_for!(grab_result_buffer, "image buffer", buf)?;
1169            }
1170            Ok(unsafe { std::slice::from_raw_parts(buf, len) })
1171        }
1172    }
1173    /// Returns the payload size in bytes.
1174    pub fn payload_size(&self) -> PylonResult<u32> {
1175        {
1176            let mut out = 0u32;
1177            shim_check(unsafe {
1178                (shim_loader::shim().grab_result_payload_size)(self.ptr, &mut out)
1179            })?;
1180            Ok(out)
1181        }
1182    }
1183    /// Returns the backing buffer capacity in bytes.
1184    pub fn buffer_size(&self) -> PylonResult<u32> {
1185        {
1186            let mut out = 0u32;
1187            shim_check(unsafe {
1188                (shim_loader::shim().grab_result_buffer_size)(self.ptr, &mut out)
1189            })?;
1190            Ok(out)
1191        }
1192    }
1193    /// Returns the block identifier.
1194    pub fn block_id(&self) -> PylonResult<u64> {
1195        {
1196            let mut out = 0u64;
1197            shim_check(unsafe { (shim_loader::shim().grab_result_block_id)(self.ptr, &mut out) })?;
1198            Ok(out)
1199        }
1200    }
1201    /// Returns the camera timestamp.
1202    pub fn time_stamp(&self) -> PylonResult<u64> {
1203        {
1204            let mut out = 0u64;
1205            shim_check(unsafe {
1206                (shim_loader::shim().grab_result_time_stamp)(self.ptr, &mut out)
1207            })?;
1208            Ok(out)
1209        }
1210    }
1211    /// Returns the image stride in bytes.
1212    pub fn stride(&self) -> PylonResult<usize> {
1213        {
1214            let mut out = 0usize;
1215            shim_check(unsafe { (shim_loader::shim().grab_result_stride)(self.ptr, &mut out) })?;
1216            Ok(out)
1217        }
1218    }
1219    /// Returns the image size in bytes.
1220    pub fn image_size(&self) -> PylonResult<u32> {
1221        {
1222            let mut out = 0u32;
1223            shim_check(unsafe {
1224                (shim_loader::shim().grab_result_image_size)(self.ptr, &mut out)
1225            })?;
1226            Ok(out)
1227        }
1228    }
1229    /// Returns the chunk-data node map for this result.
1230    pub fn chunk_data_node_map(&self) -> PylonResult<NodeMap<'_>> {
1231        {
1232            let mut ptr: *const c_void = std::ptr::null();
1233            let err = unsafe {
1234                (shim_loader::shim().grab_result_get_chunk_data_node_map)(self.ptr, &mut ptr)
1235            };
1236            shim_check_for!(grab_result_get_chunk_data_node_map, err)?;
1237            ensure_non_null(
1238                stringify!(grab_result_get_chunk_data_node_map),
1239                "chunk data node map pointer",
1240                ptr,
1241            )?;
1242            Ok(NodeMap {
1243                ptr,
1244                _marker: std::marker::PhantomData,
1245            })
1246        }
1247    }
1248}
1249
1250impl Drop for GrabResult {
1251    fn drop(&mut self) {
1252        if !self.ptr.is_null() {
1253            unsafe { (shim_loader::shim().grab_result_ptr_destroy)(self.ptr) }
1254        }
1255    }
1256}
1257
1258// =========================================================================
1259// DeviceInfo
1260// =========================================================================
1261
1262/// Immutable information about a discovered device.
1263pub struct DeviceInfo {
1264    pub(crate) ptr: *mut c_void,
1265    runtime: runtime_impl::RuntimeLease,
1266}
1267
1268unsafe impl Send for DeviceInfo {}
1269
1270impl Clone for DeviceInfo {
1271    fn clone(&self) -> DeviceInfo {
1272        {
1273            let mut out: *mut c_void = std::ptr::null_mut();
1274            let err = unsafe { (shim_loader::shim().device_info_clone)(self.ptr, &mut out) };
1275            shim_loader::check_err(err)
1276                .map_err(PylonError::new)
1277                .expect("device_info_clone should not fail");
1278            DeviceInfo {
1279                ptr: out,
1280                runtime: self.runtime.clone(),
1281            }
1282        }
1283    }
1284}
1285
1286impl Drop for DeviceInfo {
1287    fn drop(&mut self) {
1288        if !self.ptr.is_null() {
1289            unsafe { (shim_loader::shim().device_info_destroy)(self.ptr) }
1290        }
1291    }
1292}
1293
1294impl DeviceInfo {
1295    /// Returns the device model name.
1296    pub fn model_name(&self) -> PylonResult<String> {
1297        {
1298            let mut out: *mut c_char = std::ptr::null_mut();
1299            let err =
1300                unsafe { (shim_loader::shim().device_info_get_model_name)(self.ptr, &mut out) };
1301            shim_check_for!(device_info_get_model_name, err)?;
1302            ensure_non_null_for!(device_info_get_model_name, "model name string", out)?;
1303            Ok(unsafe { shim_loader::take_str(out) })
1304        }
1305    }
1306}
1307
1308/// Shared access to name/value device properties.
1309pub trait HasProperties {
1310    /// Returns the available property names.
1311    fn property_names(&self) -> PylonResult<Vec<String>>;
1312
1313    /// Returns the value for a single property.
1314    fn property_value(&self, name: &str) -> PylonResult<String>;
1315}
1316
1317impl HasProperties for DeviceInfo {
1318    fn property_names(&self) -> PylonResult<Vec<String>> {
1319        {
1320            let mut arr: *mut *mut c_char = std::ptr::null_mut();
1321            let mut count: usize = 0;
1322            let err = unsafe {
1323                (shim_loader::shim().device_info_get_property_names)(self.ptr, &mut arr, &mut count)
1324            };
1325            shim_check_for!(device_info_get_property_names, err)?;
1326            if count > 0 {
1327                ensure_non_null_for!(device_info_get_property_names, "property names array", arr)?;
1328            }
1329            let mut result = Vec::with_capacity(count);
1330            unsafe {
1331                for i in 0..count {
1332                    let s = std::ffi::CStr::from_ptr(*arr.add(i))
1333                        .to_string_lossy()
1334                        .into_owned();
1335                    result.push(s);
1336                }
1337                (shim_loader::shim().device_info_free_property_names)(arr, count);
1338            }
1339            Ok(result)
1340        }
1341    }
1342
1343    fn property_value(&self, name: &str) -> PylonResult<String> {
1344        {
1345            let mut out: *mut c_char = std::ptr::null_mut();
1346            let err = unsafe {
1347                (shim_loader::shim().device_info_get_property_value)(
1348                    self.ptr,
1349                    name.as_ptr() as *const c_char,
1350                    name.len(),
1351                    &mut out,
1352                )
1353            };
1354            shim_check_for!(device_info_get_property_value, err)?;
1355            ensure_non_null_for!(device_info_get_property_value, "property value string", out)?;
1356            Ok(unsafe { shim_loader::take_str(out) })
1357        }
1358    }
1359}
1360
1361// =========================================================================
1362// WaitObject (Windows stream)
1363// =========================================================================
1364
1365#[cfg(all(target_os = "windows", feature = "stream"))]
1366pub struct WaitObject(pub(crate) *mut c_void);
1367
1368#[cfg(all(target_os = "windows", feature = "stream"))]
1369unsafe impl Send for WaitObject {}
1370
1371#[cfg(all(target_os = "windows", feature = "stream"))]
1372impl Drop for WaitObject {
1373    fn drop(&mut self) {
1374        if !self.0.is_null() {
1375            unsafe { (shim_loader::shim().wait_object_destroy)(self.0) }
1376        }
1377    }
1378}
1379
1380#[cfg(all(target_os = "windows", feature = "stream"))]
1381impl WaitObject {
1382    pub fn wait(&self, timeout: u64) -> PylonResult<bool> {
1383        {
1384            let mut out: c_int = 0;
1385            let err = unsafe { (shim_loader::shim().wait_object_wait)(self.0, timeout, &mut out) };
1386            shim_check(err)?;
1387            Ok(out != 0)
1388        }
1389    }
1390}
1391
1392// =========================================================================
1393// Helpers
1394// =========================================================================
1395
1396fn path_to_string<P: AsRef<std::path::Path>>(path: P) -> PylonResult<String> {
1397    match path.as_ref().to_str() {
1398        Some(filename) => Ok(filename.into()),
1399        None => Err(PylonError::new("Cannot convert path to UTF-8".to_string())),
1400    }
1401}