Skip to main content

aic_sdk_sys/
lib.rs

1//! Raw FFI bindings to the AIC library.
2//!
3//! This module contains automatically generated bindings from the C header file.
4//! The bindings are generated using bindgen and may contain naming conventions
5//! that don't match Rust standards, which is expected for FFI code.
6//!
7//! # Runtime linking
8//!
9//! Enable the `runtime-linking` feature to load the AIC dynamic library at runtime instead of
10//! linking to `libaic` at build time. The library is loaded automatically the first time any
11//! `aic_*` function is called, using the platform's default name (`libaic.so`, `libaic.dylib`,
12//! or `aic.dll`) resolved through the normal dynamic loader search path (`LD_LIBRARY_PATH`,
13//! rpath, system directories, …).
14//!
15//! To load a specific file instead, call [`load_library`] with an explicit path before the first
16//! SDK call:
17//!
18//! ```no_run
19//! # #[cfg(feature = "runtime-linking")]
20//! # unsafe fn example() -> Result<(), aic_sdk_sys::DynamicLoadingError> {
21//! unsafe { aic_sdk_sys::load_library("/path/to/libaic.so")? };
22//! # Ok(())
23//! # }
24//! ```
25
26#![allow(non_upper_case_globals)]
27#![allow(non_camel_case_types)]
28#![allow(non_snake_case)]
29#![allow(dead_code)]
30#![allow(clippy::all)]
31
32include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
33
34#[cfg(not(feature = "runtime-linking"))]
35unsafe extern "C" {
36    /// Sets the SDK wrapper ID.
37    ///
38    /// This function is not included in the C SDK's header file, but it is part of the library.
39    pub fn aic_set_sdk_wrapper_id(id: u32);
40}
41
42#[cfg(feature = "runtime-linking")]
43mod runtime_linking {
44    use super::*;
45    use libloading::Library;
46    use std::{
47        fmt,
48        path::{Path, PathBuf},
49        sync::OnceLock,
50    };
51
52    static AIC_LIBRARY: OnceLock<LoadedLibrary> = OnceLock::new();
53
54    /// Error returned when loading the AIC dynamic library at runtime fails.
55    #[derive(Debug)]
56    pub enum DynamicLoadingError {
57        /// A dynamic library was already loaded. The active library cannot be replaced safely.
58        AlreadyLoaded,
59        /// Opening the dynamic library failed.
60        OpenLibrary {
61            /// Path that was passed to [`load_library`].
62            path: PathBuf,
63            /// Error reported by the platform dynamic loader.
64            source: libloading::Error,
65        },
66        /// A required AIC symbol could not be found in the dynamic library.
67        LoadSymbol {
68            /// Name of the missing symbol.
69            symbol: &'static str,
70            /// Error reported by the platform dynamic loader.
71            source: libloading::Error,
72        },
73    }
74
75    impl fmt::Display for DynamicLoadingError {
76        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77            match self {
78                Self::AlreadyLoaded => write!(f, "the AIC dynamic library is already loaded"),
79                Self::OpenLibrary { path, source } => write!(
80                    f,
81                    "failed to load AIC dynamic library '{}': {source}",
82                    path.display()
83                ),
84                Self::LoadSymbol { symbol, source } => {
85                    write!(f, "failed to load AIC symbol '{symbol}': {source}")
86                }
87            }
88        }
89    }
90
91    impl std::error::Error for DynamicLoadingError {
92        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
93            match self {
94                Self::AlreadyLoaded => None,
95                Self::OpenLibrary { source, .. } | Self::LoadSymbol { source, .. } => Some(source),
96            }
97        }
98    }
99
100    struct LoadedLibrary {
101        _library: Library,
102        symbols: Symbols,
103    }
104
105    impl LoadedLibrary {
106        unsafe fn load(path: &Path) -> Result<Self, DynamicLoadingError> {
107            let library = unsafe { Library::new(path) }.map_err(|source| {
108                DynamicLoadingError::OpenLibrary {
109                    path: path.to_path_buf(),
110                    source,
111                }
112            })?;
113            let symbols = unsafe { Symbols::load(&library)? };
114
115            Ok(Self {
116                _library: library,
117                symbols,
118            })
119        }
120    }
121
122    fn symbols() -> &'static Symbols {
123        &AIC_LIBRARY
124            .get_or_init(|| {
125                // No explicit `load_library` happened, so load the platform's default `aic`
126                // library by name and let the OS dynamic loader resolve it.
127                let name = libloading::library_filename("aic");
128                // SAFETY: opening the platform `aic` library; the operator is responsible for
129                // making an ABI-compatible build discoverable on the loader search path.
130                unsafe { LoadedLibrary::load(Path::new(&name)) }.unwrap_or_else(|err| {
131                    panic!(
132                        "{err}. Make it discoverable on the dynamic loader search path (e.g. \
133                         LD_LIBRARY_PATH, rpath, or a system install), or call \
134                         `aic_sdk_sys::load_library` with an explicit path before using the SDK."
135                    )
136                })
137            })
138            .symbols
139    }
140
141    macro_rules! aic_symbols {
142        ($(
143            fn $name:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret:ty)?;
144        )+) => {
145            struct Symbols {
146                $(
147                    $name: unsafe extern "C" fn($($arg_ty),*) $(-> $ret)?,
148                )+
149            }
150
151            impl Symbols {
152                unsafe fn load(library: &Library) -> Result<Self, DynamicLoadingError> {
153                    $(
154                        let $name = *unsafe {
155                            library.get(concat!(stringify!($name), "\0").as_bytes())
156                        }
157                        .map_err(|source| DynamicLoadingError::LoadSymbol {
158                            symbol: stringify!($name),
159                            source,
160                        })?;
161                    )+
162
163                    Ok(Self { $($name,)+ })
164                }
165            }
166
167            $(
168                pub unsafe fn $name($($arg: $arg_ty),*) $(-> $ret)? {
169                    unsafe { (symbols().$name)($($arg),*) }
170                }
171            )+
172        };
173    }
174
175    // This file contains an automatically generated file with the following code:
176    // `aic_symbols! { list of fn declarations }`
177    include!(concat!(env!("OUT_DIR"), "/runtime_symbols.rs"));
178
179    /// Loads the AIC dynamic library from an explicit `path`.
180    ///
181    /// This is **optional**: if it is never called, the library is loaded automatically on first
182    /// use from the platform's default name (`libaic.so` / `libaic.dylib` / `aic.dll`) via the OS
183    /// loader search path. Call this when you need to choose the exact file instead.
184    ///
185    /// It must run before the first SDK call; once the library is loaded (explicitly or
186    /// automatically) it is kept for the rest of the process so function pointers and SDK-owned
187    /// objects stay valid, and a later call returns [`DynamicLoadingError::AlreadyLoaded`].
188    ///
189    /// # Safety
190    ///
191    /// `path` must point to a dynamic library that is ABI-compatible with the bundled `aic.h`
192    /// header used to build these bindings. Loading an incompatible library can cause undefined
193    /// behavior when its functions are called.
194    pub unsafe fn load_library<P: AsRef<Path>>(path: P) -> Result<(), DynamicLoadingError> {
195        let loaded = unsafe { LoadedLibrary::load(path.as_ref())? };
196        AIC_LIBRARY
197            .set(loaded)
198            .map_err(|_| DynamicLoadingError::AlreadyLoaded)
199    }
200
201    /// Returns whether an AIC dynamic library has already been loaded.
202    pub fn is_library_loaded() -> bool {
203        AIC_LIBRARY.get().is_some()
204    }
205}
206
207#[cfg(feature = "runtime-linking")]
208pub use runtime_linking::*;