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::*;