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