diff --git a/dlopen2-derive/src/wrapper.rs b/dlopen2-derive/src/wrapper.rs index 0f9eb52..3200186 100644 --- a/dlopen2-derive/src/wrapper.rs +++ b/dlopen2-derive/src/wrapper.rs @@ -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") + } } } @@ -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 { - 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 { @@ -109,7 +145,7 @@ fn field_to_wrapper(field: &Field) -> Option { 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"), @@ -126,8 +162,8 @@ fn field_to_wrapper(field: &Field) -> Option { 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 { @@ -151,6 +187,52 @@ fn field_to_wrapper(field: &Field) -> Option { }) } Type::Ptr(_) => None, + // For `field: Option ...>` + 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::(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!"), } } diff --git a/src/lib.rs b/src/lib.rs index 3b3d876..3df421f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 c_int>, example_reference: &'a mut i32, } @@ -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, it's Option here. + unsafe{cont.example_c_option_fun().map(|i| i == 0)}; *cont.example_reference_mut() = 5; } ``` @@ -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** | diff --git a/tests/wrapper_api.rs b/tests/wrapper_api.rs index a7b7520..3e90201 100644 --- a/tests/wrapper_api.rs +++ b/tests/wrapper_api.rs @@ -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, + c_fun_add_two: Option c_int>, + c_fun_add_two_not_found: Option, rust_i32: &'a i32, rust_i32_mut: &'a mut i32, #[dlopen2_name = "rust_i32_mut"] @@ -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