Skip to content

Commit

Permalink
feat: support Option<fn()> in WrapperApi
Browse files Browse the repository at this point in the history
```rust
use dlopen2::wrapper::{Container, WrapperApi};
use std::os::raw::c_int;

struct Api<'a> {
    c_fun: unsafe extern "C" fn(),
    // A function may not exist in the library.
    c_option_fun: Option<unsafe extern "C" fn() -> c_int>,
}

fn main(){
    let mut cont: Container<Api> =
        unsafe { Container::load("libexample.so") }.expect("Could not open library or load symbols");
    unsafe{ cont.c_fun() };

    if cont.has_c_option_fun() {
      // do something
    }
    // option function returns Option<fn_return_type>, it's Option<c_int> here.
    unsafe{ cont.c_option_fun().map(|i| i == 0) };
}
```
  • Loading branch information
zitsen committed Jul 5, 2023
1 parent ee52359 commit ce50672
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 17 deletions.
106 changes: 94 additions & 12 deletions dlopen2-derive/src/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,33 @@ fn field_to_tokens(field: &Field) -> proc_macro2::TokenStream {
match field.ty {
Type::BareFn(_) | Type::Reference(_) => {
if allow_null {
panic!("Only pointers can have the '{}' attribute assigned", ALLOW_NULL);
panic!(
"Only pointers can have the '{}' attribute assigned",
ALLOW_NULL
);
}
normal_field(field)
},
Type::Ptr(ref ptr) => if allow_null {
allow_null_field(field, ptr)
} else {
normal_field(field)
},
_ => panic!("Only bare functions, references and pointers are allowed in structures implementing WrapperApi trait")
}
Type::Ptr(ref ptr) => {
if allow_null {
allow_null_field(field, ptr)
} else {
normal_field(field)
}
}
Type::Path(ref path) => {
assert!(path.qself.is_none());
let path = &path.path;
assert!(path.leading_colon.is_none());
let segments = &path.segments;
let segment = segments.first().unwrap();
assert!(segment.ident.to_string() == "Option", "Only bare functions, optional bare functions, references and pointers are allowed in structures implementing WrapperApi trait");
optional_field(field)
}
_ => {
// dbg!();
panic!("Only bare functions, references and pointers are allowed in structures implementing WrapperApi trait")
}
}
}

Expand Down Expand Up @@ -95,8 +112,27 @@ fn allow_null_field(field: &Field, ptr: &TypePtr) -> proc_macro2::TokenStream {
}
}

fn optional_field(field: &Field) -> proc_macro2::TokenStream {
let field_name = &field.ident;
let symbol_name = symbol_name(field);

let tokens = quote! {
#field_name : match lib.symbol_cstr(
::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(#symbol_name, "\0").as_bytes())
) {
::std::result::Result::Ok(val) => Some(val),
::std::result::Result::Err(err) => match err {
::dlopen2::Error::NullSymbol => None,
::dlopen2::Error::SymbolGettingError(_) => None,
_ => return ::std::result::Result::Err(err)
}
}
};
tokens
}

fn field_to_wrapper(field: &Field) -> Option<proc_macro2::TokenStream> {
let ident = &field.ident;
let ident = field.ident.as_ref().expect("field must have ident");
let attrs = get_non_marker_attrs(field);

match field.ty {
Expand All @@ -109,7 +145,7 @@ fn field_to_wrapper(field: &Field) -> Option<proc_macro2::TokenStream> {
let arg_iter = fun
.inputs
.iter()
.map(|a| fun_arg_to_tokens(a, &ident.as_ref().unwrap().to_string()));
.map(|a| fun_arg_to_tokens(a, &ident.to_string()));
let arg_names = fun.inputs.iter().map(|a| match a.name {
::std::option::Option::Some((ref arg_name, _)) => arg_name,
::std::option::Option::None => panic!("This should never happen"),
Expand All @@ -126,8 +162,8 @@ fn field_to_wrapper(field: &Field) -> Option<proc_macro2::TokenStream> {
let ty = &ref_ty.elem;
let mut_acc = match ref_ty.mutability {
Some(_token) => {
let mut_ident = &format!("{}_mut", ident.as_ref().unwrap());
let method_name = syn::Ident::new(mut_ident, ident.as_ref().unwrap().span());
let mut_ident = &format!("{}_mut", ident);
let method_name = syn::Ident::new(mut_ident, ident.span());
Some(quote! {
#(#attrs)*
pub fn #method_name (&mut self) -> &mut #ty {
Expand All @@ -151,6 +187,52 @@ fn field_to_wrapper(field: &Field) -> Option<proc_macro2::TokenStream> {
})
}
Type::Ptr(_) => None,
// For `field: Option<fn(...) -> ...>`
Type::Path(ref path) => {
let path = &path.path;
let segments = &path.segments;
let segment = segments.first().unwrap();
let args = &segment.arguments;
match args {
syn::PathArguments::AngleBracketed(args) => {
let args_inner = &args.args;
let token = quote!(# args_inner);
// panic!("{}", token);
let fun = syn::parse::<syn::TypeBareFn>(token.into()).unwrap();

if fun.variadic.is_some() {
return None;
} else {
let output = &fun.output;
let output = match output {
syn::ReturnType::Default => quote!(-> Option<()>),
syn::ReturnType::Type(_, ty) => quote!( -> Option<#ty>),
};
let unsafety = &fun.unsafety;
let arg_iter = fun
.inputs
.iter()
.map(|a| fun_arg_to_tokens(a, &ident.to_string()));
let arg_names = fun.inputs.iter().map(|a| match a.name {
::std::option::Option::Some((ref arg_name, _)) => arg_name,
::std::option::Option::None => panic!("This should never happen"),
});
let has_ident = quote::format_ident!("has_{}", ident);
return Some(quote! {
#(#attrs)*
pub #unsafety fn #ident (&self, #(#arg_iter),* ) #output {
self.#ident.map(|f| (f)(#(#arg_names),*))
}
#(#attrs)*
pub fn #has_ident (&self) -> bool {
self.#ident.is_some()
}
});
}
}
_ => panic!("Unknown optional type, this should not happen!"),
}
}
_ => panic!("Unknown field type, this should not happen!"),
}
}
Expand Down
11 changes: 8 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ I hope that this library will help you to quickly get what you need and avoid er
```no_run
use dlopen2::wrapper::{Container, WrapperApi};
use std::os::raw::c_int;
#[derive(WrapperApi)]
struct Api<'a> {
example_rust_fun: fn(arg: i32) -> u32,
example_c_fun: unsafe extern "C" fn(),
// A function may not exist in the library.
example_c_option_fun: Option<unsafe extern "C" fn() -> c_int>,
example_reference: &'a mut i32,
}
Expand All @@ -25,6 +28,8 @@ fn main(){
unsafe { Container::load("libexample.so") }.expect("Could not open library or load symbols");
cont.example_rust_fun(5);
unsafe{cont.example_c_fun()};
// option function returns Option<fn_return_type>, it's Option<c_int> here.
unsafe{cont.example_c_option_fun().map(|i| i == 0)};
*cont.example_reference_mut() = 5;
}
```
Expand All @@ -46,11 +51,11 @@ fn main(){
## Compare with other libraries
|Feature | dlopen2 | [libloading](https://github.com/nagisa/rust_libloading) | [sharedlib](https://github.com/Tyleo/sharedlib) |
| Feature | dlopen2 | [libloading](https://github.com/nagisa/rust_libloading) | [sharedlib](https://github.com/Tyleo/sharedlib) |
|------------------------------------|------------|---------------------------------------------------------|-------------------------------------------------|
| Basic functionality | Yes | Yes | Yes |
| Multiplatform | Yes | Yes | Yes |
|Dangling symbol prevention | Yes | Yes | Yes |
| Dangling symbol prevention | Yes | Yes | Yes |
| Thread safety | Yes | **Potential problem with thread-safety of `dlerror()` on some platforms like FreeBSD** | **No support for SetErrorMode (library may block the application on Windows)**|
| Loading of symbols into structures | Yes | **No** | **No**
| Overhead | Minimal | Minimal | **Some overhead** |
Expand All @@ -71,7 +76,7 @@ Cargo.toml:
```toml
[dependencies]
dlopen2 = "0.4"
dlopen2 = "0.5"
```
# Documentation
Expand Down
10 changes: 8 additions & 2 deletions tests/wrapper_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ struct Api<'a> {
rust_fun_print_something: fn(),
rust_fun_add_one: fn(arg: i32) -> i32,
c_fun_print_something_else: unsafe extern "C" fn(),
c_fun_add_two: unsafe extern "C" fn(arg: c_int) -> c_int,
#[dlopen2_name = "c_fun_print_something_else"]
c_fun_print_something_else_optional: Option<unsafe extern "C" fn()>,
c_fun_add_two: Option<unsafe extern "C" fn(arg: c_int) -> c_int>,
c_fun_add_two_not_found: Option<unsafe extern "C" fn(arg: c_int)>,
rust_i32: &'a i32,
rust_i32_mut: &'a mut i32,
#[dlopen2_name = "rust_i32_mut"]
Expand Down Expand Up @@ -41,7 +44,10 @@ fn open_play_close_wrapper_api() {
cont.rust_fun_print_something(); //should not crash
assert_eq!(cont.rust_fun_add_one(5), 6);
unsafe { cont.c_fun_print_something_else() }; //should not crash
assert_eq!(unsafe { cont.c_fun_add_two(2) }, 4);
unsafe { cont.c_fun_print_something_else_optional() };
assert!(cont.has_c_fun_print_something_else_optional());
assert_eq!(unsafe { cont.c_fun_add_two(2) }, Some(4));
assert_eq!(unsafe { cont.c_fun_add_two_not_found(2) }, None);
assert_eq!(43, *cont.rust_i32());
assert_eq!(42, *cont.rust_i32_mut_mut());
*cont.rust_i32_mut_mut() = 55; //should not crash
Expand Down

0 comments on commit ce50672

Please sign in to comment.