dlib/lib.rs
1//! dlib is a small crate providing macros to make easy the use of external system libraries that
2//! can or cannot be optionally loaded at runtime, depending on whether a certain feature is enabled.
3//!
4//! ## Usage
5//!
6//! dlib defines the `external_library!` macro, which can be invoked in this way:
7//!
8//! ```rust
9//! # use dlib::external_library;
10//! # use std::ffi::{c_float, c_int};
11//! external_library!(feature="dlopen-foo", Foo, "foo",
12//! statics:
13//! me: c_int,
14//! you: c_float,
15//! functions:
16//! fn foo() -> c_int,
17//! fn bar(c_int, c_float) -> (),
18//! fn baz(*const c_int) -> c_int,
19//! varargs:
20//! fn blah(c_int, c_int ...) -> *const c_void,
21//! fn bleh(c_int ...) -> (),
22//! );
23//! ```
24//!
25//! As you can see, it is required to separate static values from functions and from function
26//! having variadic arguments. Each of these 3 categories is optional, but the ones used must appear
27//! in this order. Return types of the functions must all be explicit (hence `-> ()` for void functions).
28//!
29//! If the feature named by the `feature` argument (in this example, `dlopen-foo`) is absent on your crate,
30//! this macro will expand to an extern block defining each of the items, using the third argument
31//! of the macro as a link name:
32//!
33//! ```rust no_run
34//! # use std::ffi::{c_float, c_int, c_void};
35//! #[link(name = "foo")]
36//! extern "C" {
37//! pub static me: c_int;
38//! pub static you: c_float;
39//! pub fn foo() -> c_int;
40//! pub fn bar(_: c_int, _: c_float) -> ();
41//! pub fn baz(_: *const c_int) -> c_int;
42//! pub fn blah(_: c_int, _: c_int, ...) -> *const c_void;
43//! pub fn bleh(_: c_int, ...) -> ();
44//! }
45//!
46//! ```
47//!
48//! If the feature named by the `feature` argument is present on your crate, it will expand to a
49//! `struct` named by the second argument of the macro, with one field for each of the symbols defined;
50//! and a method `open`, which tries to load the library from the name or path given as an argument.
51//!
52//! ```rust
53//! # use dlib::DlError;
54//! # use std::ffi::{c_float, c_int, c_void};
55//! pub struct Foo {
56//! pub me: &'static c_int,
57//! pub you: &'static c_float,
58//! pub foo: unsafe extern "C" fn() -> c_int,
59//! pub bar: unsafe extern "C" fn(c_int, c_float) -> (),
60//! pub baz: unsafe extern "C" fn(*const c_int) -> c_int,
61//! pub blah: unsafe extern "C" fn(c_int, c_int, ...) -> *const c_void,
62//! pub bleh: unsafe extern "C" fn(c_int, ...) -> (),
63//! }
64//!
65//!
66//! impl Foo {
67//! pub unsafe fn open(name: &str) -> Result<Foo, DlError> {
68//! /* ... */
69//! # todo!()
70//! }
71//! }
72//! ```
73//!
74//! This method returns `Ok(..)` if the loading was successful. It contains an instance of the defined struct
75//! with all of its fields pointing to the appropriate symbol.
76//!
77//! If the library specified by `name` could not be openened, it returns `Err(DlError::CantOpen(e))`, with
78//! `e` the error reported by `libloading` (see [LibLoadingError]);
79//!
80//! It will also fail on the first missing symbol, with `Err(DlError::MissingSymbol(symb))` where `symb`
81//! is a `&str` containing the missing symbol name.
82//!
83//! Note that this method is unsafe, as loading (and unloading on drop) an external C library can run arbitrary
84//! code. As such, you need to ensure that the specific library you want to load is safe to load in the context
85//! you want to load it.
86//!
87//! ## Remaining generic in your crate
88//!
89//! If you want your crate to remain generic over dlopen vs. linking, simply add a feature to your `Cargo.toml`:
90//!
91//! ```toml
92//! [dependencies]
93//! dlib = "0.5"
94//!
95//! [features]
96//! dlopen-foo = []
97//! ```
98//!
99//! Then give the name of that feature as the `feature` argument to dlib's macros:
100//!
101//! ```rust no_run
102//! # use dlib::external_library;
103//! # use std::ffi::c_int;
104//! external_library!(feature="dlopen-foo", Foo, "foo",
105//! functions:
106//! fn foo() -> c_int,
107//! );
108//! ```
109//!
110//! `dlib` provides helper macros to dispatch the access to foreign symbols:
111//!
112//! ```rust no_run
113//! # use dlib::{ffi_dispatch, ffi_dispatch_static};
114//! # let arg1 = todo!();
115//! # let arg2 = todo!();
116//! # let function: fn(u32, u32) = todo!();
117//! # let my_static_var = todo!();
118//! ffi_dispatch!(feature="dlopen-foo", Foo, function, arg1, arg2);
119//! ffi_dispatch_static!(feature="dlopen-foo", Foo, my_static_var);
120//! ```
121//!
122//! These will expand to the appropriate value or function call depending on the presence or absence of the
123//! `dlopen-foo` feature on your crate.
124//!
125//! You must still ensure that the functions/statics or the wrapper struct `Foo` are in scope. For example,
126//! you could use the [`lazy_static`](https://crates.io/crates/lazy_static) crate to do the initialization,
127//! and store the wrapper struct in a static variable that you import wherever needed:
128//!
129//! ```rust
130//! #[cfg(feature = "dlopen-foo")]
131//! lazy_static::lazy_static! {
132//! pub static ref FOO_STATIC: Foo =
133//! Foo::open("libfoo.so").ok().expect("could not find libfoo");
134//! }
135//! ```
136//!
137//! Then, it can become as simple as putting this on top of all modules using the FFI:
138//!
139//! ```rust
140//! # #![allow(unexpected_cfgs)]
141//! # mod ffi {}
142//! #[cfg(feature = "dlopen-foo")]
143//! use ffi::FOO_STATIC;
144//! #[cfg(not(feature = "dlopen-foo"))]
145//! use ffi::*;
146//! ```
147#![warn(missing_docs)]
148
149extern crate libloading;
150
151pub use libloading::Error as LibLoadingError;
152#[doc(hidden)]
153pub use libloading::{Library, Symbol};
154
155/// Macro for generically invoking a FFI function
156///
157/// The expected arguments are, in order:
158/// - (Optional) The name of the cargo feature conditioning the usage of dlopen, in the form
159/// `feature="feature-name"`. If ommited, the feature `"dlopen"` will be used.
160/// - A value of the handle generated by the macro [`external_library!`] when the
161/// dlopen-controlling feature is enabled
162/// - The name of the function to invoke
163/// - The arguments to be passed to the function
164///
165/// The macro invocation evaluates to the return value of the FFI function.
166///
167/// #### Example
168///
169/// Assuming an FFI function of signature `fn(u32, u32) -> u32`:
170///
171/// ```rust,ignore
172/// let sum = unsafe { ffi_dispatch!(feature="dlopen", LIBRARY_HANDLE, sum, 2, 2) };
173/// ```
174#[macro_export]
175macro_rules! ffi_dispatch(
176 (feature=$feature: expr, $handle: expr, $func: ident, $($arg: expr),*) => (
177 {
178 #[cfg(feature = $feature)]
179 let ret = ($handle.$func)($($arg),*);
180 #[cfg(not(feature = $feature))]
181 let ret = $func($($arg),*);
182
183 ret
184 }
185 );
186 ($handle: expr, $func: ident, $($arg: expr),*) => (
187 // NOTE: this "dlopen" refers to a feature on the crate *using* dlib
188 $crate::ffi_dispatch!(feature="dlopen", $handle, $func, $($arg),*)
189 );
190);
191
192/// Macro for generically accessing a FFI static
193///
194/// The expected arguments are, in order:
195/// - (Optional) The name of the cargo feature conditioning the usage of dlopen, in the form
196/// `feature="feature-name"`. If ommited, the feature `"dlopen"` will be used.
197/// - A value of the handle generated by the macro [`external_library!`] when the
198/// dlopen-controlling feature is enabled
199/// - The name of the static
200///
201/// The macro invocation evaluates to a `&T` reference to the static
202///
203/// #### Example
204///
205/// ```rust,ignore
206/// let my_static = unsafe { ffi_dispatch!(feature="dlopen", LIBRARY_HANDLE, my_static) };
207/// ```
208#[macro_export]
209macro_rules! ffi_dispatch_static(
210 (feature=$feature: expr, $handle: expr, $name: ident) => (
211 {
212 #[cfg(feature = $feature)]
213 let ret = $handle.$name;
214 #[cfg(not(feature = $feature))]
215 let ret = &$name;
216
217 ret
218 }
219 );
220 ($handle:expr, $name: ident) => (
221 $crate::ffi_dispatch_static!(feature="dlopen", $handle, $name)
222 );
223);
224
225#[doc(hidden)]
226#[macro_export]
227macro_rules! link_external_library(
228 ($link: expr,
229 $(statics: $($(#[$sattr:meta])* $sname: ident: $stype: ty),+,)|*
230 $(functions: $($(#[$fattr:meta])* fn $fname: ident($($farg: ty),*) -> $fret:ty),+,)|*
231 $(varargs: $($(#[$vattr:meta])* fn $vname: ident($($vargs: ty),+) -> $vret: ty),+,)|*
232 ) => (
233 #[link(name = $link)]
234 extern "C" {
235 $($(
236 $(#[$sattr])*
237 pub static $sname: $stype;
238 )+)*
239 $($(
240 $(#[$fattr])*
241 pub fn $fname($(_: $farg),*) -> $fret;
242 )+)*
243 $($(
244 $(#[$vattr])*
245 pub fn $vname($(_: $vargs),+ , ...) -> $vret;
246 )+)*
247 }
248 );
249);
250
251/// An error generated when failing to load a library
252#[derive(Debug)]
253pub enum DlError {
254 /// The requested library would not be opened
255 ///
256 /// Includes the error reported by `libloading` when trying to
257 /// open the library.
258 CantOpen(LibLoadingError),
259 /// Some required symbol was missing in the library
260 MissingSymbol(&'static str),
261}
262
263impl std::error::Error for DlError {
264 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
265 match *self {
266 DlError::CantOpen(ref e) => Some(e),
267 DlError::MissingSymbol(_) => None,
268 }
269 }
270}
271
272impl std::fmt::Display for DlError {
273 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
274 match *self {
275 DlError::CantOpen(ref e) => write!(f, "Could not open the requested library: {}", e),
276 DlError::MissingSymbol(s) => write!(f, "The requested symbol was missing: {}", s),
277 }
278 }
279}
280
281#[doc(hidden)]
282#[macro_export]
283macro_rules! dlopen_external_library(
284 (__struct, $structname: ident,
285 $(statics: $($(#[$sattr:meta])* $sname: ident: $stype: ty),+,)|*
286 $(functions: $($(#[$fattr:meta])* fn $fname: ident($($farg: ty),*) -> $fret:ty),+,)|*
287 $(varargs: $($(#[$vattr:meta])* fn $vname: ident($($vargs: ty),+) -> $vret: ty),+,)|*
288 ) => (
289 pub struct $structname {
290 __lib: $crate::Library,
291 $($(
292 $(#[$sattr])*
293 pub $sname: $crate::Symbol<'static, &'static $stype>,
294 )+)*
295 $($(
296 $(#[$fattr])*
297 pub $fname: $crate::Symbol<'static, unsafe extern "C" fn($($farg),*) -> $fret>,
298 )+)*
299 $($(
300 $(#[$vattr])*
301 pub $vname: $crate::Symbol<'static, unsafe extern "C" fn($($vargs),+ , ...) -> $vret>,
302 )+)*
303 }
304 );
305 (__impl, $structname: ident,
306 $(statics: $($(#[$sattr:meta])* $sname: ident: $stype: ty),+,)|*
307 $(functions: $($(#[$fattr:meta])* fn $fname: ident($($farg: ty),*) -> $fret:ty),+,)|*
308 $(varargs: $($(#[$vattr:meta])* fn $vname: ident($($vargs: ty),+) -> $vret: ty),+,)|*
309 ) => (
310 impl $structname {
311 pub unsafe fn open(name: &str) -> Result<$structname, $crate::DlError> {
312 // we use it to ensure the 'static lifetime
313 use std::mem::transmute;
314 let lib = $crate::Library::new(name).map_err($crate::DlError::CantOpen)?;
315 let s = $structname {
316 $($($(#[$sattr])* $sname: {
317 let s_name = concat!(stringify!($sname), "\0");
318 transmute(match lib.get::<&'static $stype>(s_name.as_bytes()) {
319 Ok(s) => s,
320 Err(_) => return Err($crate::DlError::MissingSymbol(s_name))
321 })
322 },
323 )+)*
324 $($($(#[$fattr])* $fname: {
325 let s_name = concat!(stringify!($fname), "\0");
326 transmute(match lib.get::<unsafe extern "C" fn($($farg),*) -> $fret>(s_name.as_bytes()) {
327 Ok(s) => s,
328 Err(_) => return Err($crate::DlError::MissingSymbol(s_name))
329 })
330 },
331 )+)*
332 $($($(#[$vattr])* $vname: {
333 let s_name = concat!(stringify!($vname), "\0");
334 transmute(match lib.get::<unsafe extern "C" fn($($vargs),+ , ...) -> $vret>(s_name.as_bytes()) {
335 Ok(s) => s,
336 Err(_) => return Err($crate::DlError::MissingSymbol(s_name))
337 })
338 },
339 )+)*
340 __lib: lib
341 };
342 Ok(s)
343 }
344 }
345 );
346 ($structname: ident,
347 $(statics: $($(#[$sattr:meta])* $sname: ident: $stype: ty),+,)|*
348 $(functions: $($(#[$fattr:meta])* fn $fname: ident($($farg: ty),*) -> $fret:ty),+,)|*
349 $(varargs: $($(#[$vattr:meta])* fn $vname: ident($($vargs: ty),+) -> $vret: ty),+,)|*
350 ) => (
351 $crate::dlopen_external_library!(__struct,
352 $structname, $(statics: $($(#[$sattr])* $sname: $stype),+,)|*
353 $(functions: $($(#[$fattr])* fn $fname($($farg),*) -> $fret),+,)|*
354 $(varargs: $($(#[$vattr])* fn $vname($($vargs),+) -> $vret),+,)|*
355 );
356 $crate::dlopen_external_library!(__impl,
357 $structname, $(statics: $($(#[$sattr])* $sname: $stype),+,)|*
358 $(functions: $($(#[$fattr])* fn $fname($($farg),*) -> $fret),+,)|*
359 $(varargs: $($(#[$vattr])* fn $vname($($vargs),+) -> $vret),+,)|*
360 );
361 unsafe impl Sync for $structname { }
362 );
363);
364
365/// Main macro of this library, used to generate the the FFI bindings.
366///
367/// The expected arguments are, in order:
368/// - (Optional) The name of the cargo feature conditioning the usage of dlopen, in the form
369/// `feature="feature-name"`. If ommited, the feature `"dlopen"` will be used.
370/// - The name of the struct that will be generated when the dlopen-controlling feature is
371/// enabled
372/// - The link name of the target library
373/// - The desctription of the statics, functions, and vararg functions that should be linked
374///
375/// See crate-level documentation for a detailed example of use.
376#[macro_export]
377macro_rules! external_library(
378 (feature=$feature: expr, $structname: ident, $link: expr,
379 $(statics: $($(#[$sattr:meta])* $sname: ident: $stype: ty),+,)|*
380 $(functions: $($(#[$fattr:meta])* fn $fname: ident($($farg: ty),*) -> $fret:ty),+,)|*
381 $(varargs: $($(#[$vattr:meta])* fn $vname: ident($($vargs: ty),+) -> $vret: ty),+,)|*
382 ) => (
383 #[cfg(feature = $feature)]
384 $crate::dlopen_external_library!(
385 $structname, $(statics: $($(#[$sattr])* $sname: $stype),+,)|*
386 $(functions: $($(#[$fattr])* fn $fname($($farg),*) -> $fret),+,)|*
387 $(varargs: $($(#[$vattr])* fn $vname($($vargs),+) -> $vret),+,)|*
388 );
389
390 #[cfg(not(feature = $feature))]
391 $crate::link_external_library!(
392 $link, $(statics: $($(#[$sattr])* $sname: $stype),+,)|*
393 $(functions: $($(#[$fattr])* fn $fname($($farg),*) -> $fret),+,)|*
394 $(varargs: $($(#[$vattr])* fn $vname($($vargs),+) -> $vret),+,)|*
395 );
396 );
397 ($structname: ident, $link: expr,
398 $(statics: $($(#[$sattr:meta])* $sname: ident: $stype: ty),+,)|*
399 $(functions: $($(#[$fattr:meta])* fn $fname: ident($($farg: ty),*) -> $fret:ty),+,)|*
400 $(varargs: $($(#[$vattr:meta])* fn $vname: ident($($vargs: ty),+) -> $vret: ty),+,)|*
401 ) => (
402 $crate::external_library!(
403 feature="dlopen", $structname, $link,
404 $(statics: $($(#[$sattr])* $sname: $stype),+,)|*
405 $(functions: $($(#[$fattr])* fn $fname($($farg),*) -> $fret),+,)|*
406 $(varargs: $($(#[$vattr])* fn $vname($($vargs),+) -> $vret),+,)|*
407 );
408 );
409);