| 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: &'static 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 | |