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 | |
132 | extern crate libloading; |
133 | |
134 | pub use libloading::Error as LibLoadingError; |
135 | #[doc (hidden)] |
136 | pub 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 ] |
158 | macro_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 ] |
192 | macro_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 ] |
210 | macro_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)] |
233 | pub 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 | |
243 | impl 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 | |
252 | impl 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 ] |
263 | macro_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 ] |
354 | macro_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 | |