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);