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: &Error) => 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: &Error) => write!(f, "Could not open the requested library: {}", e),
256 DlError::MissingSymbol(s: &str) => 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);
387