├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── dlopen2-derive ├── Cargo.toml └── src │ ├── api.rs │ ├── common.rs │ ├── lib.rs │ ├── multi_api.rs │ └── wrapper.rs ├── dlopen2 ├── Cargo.toml ├── examples │ ├── README.md │ ├── commons │ │ └── mod.rs │ ├── raw.rs │ ├── raw_addr_info.rs │ ├── symbor.rs │ ├── symbor_api.rs │ ├── wrapper_api.rs │ └── wrapper_multi_api.rs ├── src │ ├── err.rs │ ├── lib.rs │ ├── raw │ │ ├── common.rs │ │ ├── mod.rs │ │ ├── tests.rs │ │ ├── unix.rs │ │ └── windows.rs │ ├── symbor │ │ ├── api.rs │ │ ├── container.rs │ │ ├── from_raw.rs │ │ ├── library.rs │ │ ├── mod.rs │ │ ├── option.rs │ │ ├── ptr_or_null.rs │ │ ├── ptr_or_null_mut.rs │ │ ├── reference.rs │ │ ├── reference_mut.rs │ │ └── symbol.rs │ ├── utils.rs │ └── wrapper │ │ ├── api.rs │ │ ├── container.rs │ │ ├── mod.rs │ │ ├── multi_api.rs │ │ ├── option.rs │ │ └── optional.rs └── tests │ ├── commons │ └── mod.rs │ ├── raw.rs │ ├── symbor.rs │ ├── symbor_api.rs │ └── wrapper_api.rs └── example-dylib ├── Cargo.toml └── src └── lib.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | RUSTDOCFLAGS: "--deny warnings" 12 | RUSTFLAGS: "--deny warnings" 13 | 14 | jobs: 15 | test: 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | toolchain: ["nightly", "stable"] 21 | target: ["x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc", "i686-pc-windows-msvc", "x86_64-apple-darwin", "x86_64-pc-windows-gnu"] 22 | include: 23 | - target: x86_64-unknown-linux-gnu 24 | os: ubuntu-latest 25 | - target: x86_64-pc-windows-msvc 26 | os: windows-latest 27 | - target: i686-pc-windows-msvc 28 | os: windows-latest 29 | - target: x86_64-pc-windows-gnu 30 | os: windows-latest 31 | - target: x86_64-apple-darwin 32 | os: macos-latest 33 | steps: 34 | - uses: actions/checkout@v2 35 | 36 | - name: Install latest ${{ matrix.toolchain }} 37 | uses: actions-rs/toolchain@v1 38 | with: 39 | target: ${{ matrix.target }} 40 | toolchain: ${{ matrix.toolchain }} 41 | override: true 42 | 43 | - name: Build 44 | uses: actions-rs/cargo@v1 45 | with: 46 | use-cross: false 47 | command: build 48 | args: --release --target ${{ matrix.target }} 49 | 50 | - name: Test 51 | uses: actions-rs/cargo@v1 52 | with: 53 | use-cross: false 54 | command: test 55 | args: --target ${{ matrix.target }} --all-targets --no-fail-fast 56 | 57 | documentation: 58 | runs-on: ${{ matrix.os }} 59 | strategy: 60 | matrix: 61 | include: 62 | - os: ubuntu-latest 63 | - os: windows-latest 64 | steps: 65 | - uses: actions/checkout@v2 66 | - name: Install latest nightly 67 | uses: actions-rs/toolchain@v1 68 | with: 69 | profile: minimal 70 | toolchain: nightly 71 | override: true 72 | - name: Generate documentation 73 | run: cargo doc --all-features 74 | 75 | clippy: 76 | runs-on: ${{ matrix.os }} 77 | strategy: 78 | matrix: 79 | include: 80 | - os: ubuntu-latest 81 | - os: windows-latest 82 | steps: 83 | - uses: actions/checkout@v2 84 | - name: Install latest nightly 85 | uses: actions-rs/toolchain@v1 86 | with: 87 | toolchain: nightly 88 | components: clippy 89 | override: true 90 | 91 | - name: Clippy check 92 | uses: actions-rs/clippy-check@v1 93 | with: 94 | token: ${{ secrets.GITHUB_TOKEN }} 95 | args: --all-features 96 | 97 | fmt: 98 | runs-on: ${{ matrix.os }} 99 | strategy: 100 | matrix: 101 | include: 102 | - os: ubuntu-latest 103 | - os: windows-latest 104 | steps: 105 | - uses: actions/checkout@v2 106 | - name: Install latest nightly 107 | uses: actions-rs/toolchain@v1 108 | with: 109 | profile: minimal 110 | toolchain: nightly 111 | override: true 112 | components: rustfmt 113 | 114 | - name: Format check 115 | uses: actions-rs/cargo@v1 116 | with: 117 | command: fmt 118 | args: --all -- --check 119 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "dlopen2", 4 | "dlopen2-derive", 5 | "example-dylib", 6 | ] 7 | resolver = "2" 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Szymon Wieloch 4 | Copyright (C) 2019 Ahmed Masud 5 | Copyright (C) 2022 OpenByte 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dlopen2 2 | 3 | [![CI](https://github.com/OpenByteDev/dlopen2/actions/workflows/ci.yml/badge.svg)](https://github.com/OpenByteDev/dlopen2/actions/workflows/ci.yml) 4 | [![crates.io](https://img.shields.io/crates/v/dlopen2.svg)](https://crates.io/crates/dlopen2) 5 | [![Documentation](https://docs.rs/dlopen2/badge.svg)](https://docs.rs/dlopen2) 6 | [![dependency status](https://deps.rs/repo/github/openbytedev/dlopen2/status.svg)](https://deps.rs/repo/github/openbytedev/dlopen2) 7 | [![MIT](https://img.shields.io/crates/l/dlopen2.svg)](https://github.com/OpenByteDev/dlopen2/blob/master/LICENSE) 8 | 9 | This is a fork of the original, now unmaintained [`dlopen`](https://github.com/szymonwieloch/rust-dlopen) with updated dependencies, bug fixes and some new features. 10 | 11 | ## Overview 12 | 13 | This library is my effort to make use of dynamic link libraries in Rust simple. 14 | Previously existing solutions were either unsafe, provided huge overhead of required writing too much code to achieve simple things. 15 | I hope that this library will help you to quickly get what you need and avoid errors. 16 | 17 | ## Quick example 18 | 19 | ```rust 20 | use dlopen2::wrapper::{Container, WrapperApi}; 21 | 22 | #[derive(WrapperApi)] 23 | struct Api<'a> { 24 | example_rust_fun: fn(arg: i32) -> u32, 25 | example_c_fun: unsafe extern "C" fn(), 26 | example_reference: &'a mut i32, 27 | // A function or field may not always exist in the library. 28 | example_c_fun_option: Option, 29 | example_reference_option: Option<&'a mut i32>, 30 | } 31 | 32 | fn main(){ 33 | let mut cont: Container = 34 | unsafe { Container::load("libexample.so") }.expect("Could not open library or load symbols"); 35 | cont.example_rust_fun(5); 36 | unsafe{ cont.example_c_fun() }; 37 | *cont.example_reference_mut() = 5; 38 | 39 | // Optional functions return Some(result) if the function is present or None if absent. 40 | unsafe{ cont.example_c_fun_option() }; 41 | // Optional fields are Some(value) if present and None if absent. 42 | if let Some(example_reference) = &mut cont.example_reference_option { 43 | *example_reference = 5; 44 | } 45 | } 46 | ``` 47 | 48 | ## Features 49 | 50 | ### Main features 51 | 52 | * Supports majority of platforms and is platform independent. 53 | * Is consistent with Rust error handling mechanism and makes making mistakes much more difficult. 54 | * Is very lightweight. It mostly uses zero cost wrappers to create safer abstractions over platform API. 55 | * Is thread safe. 56 | * Is object-oriented programming friendly. 57 | * Has a low-level API that provides full flexibility of using libraries. 58 | * Has two high-level APIs that protect against dangling symbols - each in its own way. 59 | * High level APIs support automatic loading of symbols into structures. You only need to define a 60 | structure that represents an API. The rest happens automatically and requires only minimal amount of code. 61 | * Automatic loading of symbols helps you to follow the DRY paradigm. 62 | 63 | ## Comparison with other libraries 64 | 65 | | Feature | dlopen2 | [libloading](https://github.com/nagisa/rust_libloading) | [sharedlib](https://github.com/Tyleo/sharedlib) | 66 | |------------------------------------|------------|---------------------------------------------------------|-------------------------------------------------| 67 | | Basic functionality | Yes | Yes | Yes | 68 | | Multiplatform | Yes | Yes | Yes | 69 | | Dangling symbol prevention | Yes | Yes | Yes | 70 | | 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)** | 71 | | Loading of symbols into structures | Yes | **No** | **No** | 72 | | Overhead | Minimal | Minimal | **Some overhead** | 73 | | Low-level, unsafe API | Yes | Yes | Yes | 74 | | Object-oriented friendly | Yes | **No** | Yes | 75 | | Load from the program itself | Yes | **No** | **No** | 76 | | Obtaining address information (dladdr) | Yes | **Unix only** | **No** | 77 | 78 | ## Safety 79 | 80 | Please note that while Rust aims at being 100% safe language, it does not yet provide mechanisms that would allow me to create a 100% safe library, so I had to settle on 99%. 81 | Also the nature of dynamic link libraries requires casting obtained pointers into types that are defined on the application side. And this cannot be safe. 82 | Having said that I still think that this library provides the best approach and greatest safety possible in Rust. 83 | 84 | ## Usage 85 | 86 | Cargo.toml: 87 | 88 | ```toml 89 | [dependencies] 90 | dlopen2 = "0.6" 91 | ``` 92 | 93 | ## Documentation 94 | 95 | [Cargo documentation](https://docs.rs/dlopen2) 96 | 97 | ## License 98 | 99 | This code is licensed under the [MIT](./LICENSE) license. 100 | 101 | ## Changelog 102 | 103 | [GitHub changelog](https://github.com/OpenByteDev/dlopen2/releases) 104 | 105 | ## Acknowledgement 106 | 107 | Special thanks to [Simonas Kazlauskas](https://github.com/nagisa) whose [libloading](https://github.com/nagisa/rust_libloading) became code base for my project. 108 | -------------------------------------------------------------------------------- /dlopen2-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dlopen2_derive" 3 | version = "0.4.1" 4 | authors = ["Szymon Wieloch ", 5 | "OpenByte "] 6 | description = "Derive macros for the dlopen2 crate." 7 | license-file = "../LICENSE" 8 | repository = "https://github.com/OpenByteDev/dlopen2" 9 | edition = "2021" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | syn = { version = "2.0", features = ["extra-traits"] } 16 | quote = "1.0" 17 | proc-macro2 = "1.0" 18 | -------------------------------------------------------------------------------- /dlopen2-derive/src/api.rs: -------------------------------------------------------------------------------- 1 | use super::common::{get_fields, symbol_name}; 2 | use quote::quote; 3 | use syn::{DeriveInput, Field}; 4 | 5 | pub fn impl_library_api(ast: &DeriveInput) -> proc_macro2::TokenStream { 6 | let name = &ast.ident; 7 | let fields = get_fields(ast, "SymBorApi"); 8 | 9 | let tok_iter = fields.named.iter().map(field_to_tokens); 10 | let q = quote! { 11 | impl<'a> SymBorApi<'a> for #name<'a> { 12 | unsafe fn load(lib: &'a ::dlopen2::symbor::Library) -> ::std::result::Result<#name<'a>,::dlopen2::Error> { 13 | ::std::result::Result::Ok(#name { 14 | #(#tok_iter),* 15 | }) 16 | } 17 | } 18 | }; 19 | 20 | q 21 | } 22 | 23 | fn field_to_tokens(field: &Field) -> proc_macro2::TokenStream { 24 | let field_name = &field.ident; 25 | let symbol_name = symbol_name(field); 26 | 27 | quote! { 28 | #field_name: { 29 | let raw_result = lib.ptr_or_null_cstr::<()>( 30 | ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(#symbol_name, "\0").as_bytes()) 31 | ); 32 | ::dlopen2::symbor::FromRawResult::from_raw_result(raw_result)? 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /dlopen2-derive/src/common.rs: -------------------------------------------------------------------------------- 1 | use syn::{Attribute, Data, DeriveInput, Expr, ExprLit, Field, Fields, FieldsNamed, Lit, Meta}; 2 | 3 | pub fn symbol_name(field: &Field) -> String { 4 | match find_str_attr_val(field, "dlopen2_name") { 5 | Some(val) => val, 6 | None => { 7 | // not found, so use field name 8 | match field.ident { 9 | Some(ref val) => val.to_string(), 10 | None => panic!("All structure fields need to be identifiable"), 11 | } 12 | } 13 | } 14 | } 15 | 16 | pub fn find_str_attr_val(field: &Field, attr_name: &str) -> Option { 17 | for attr in field.attrs.iter() { 18 | match attr.meta { 19 | Meta::NameValue(ref meta) => { 20 | if let Some(ident) = meta.path.get_ident() { 21 | if ident == attr_name { 22 | return match &meta.value { 23 | Expr::Lit(ExprLit { 24 | lit: Lit::Str(val), .. 25 | }) => Some(val.value()), 26 | _ => panic!("{attr_name} attribute must be a string"), 27 | }; 28 | } 29 | } 30 | } 31 | _ => continue, 32 | } 33 | } 34 | None 35 | } 36 | 37 | pub fn get_non_marker_attrs(field: &Field) -> Vec<&Attribute> { 38 | field 39 | .attrs 40 | .iter() 41 | .filter(|attr| { 42 | if let Some(ident) = attr.path().get_ident() { 43 | if ident.to_string().starts_with("dlopen2_") { 44 | return false; 45 | } 46 | } 47 | true 48 | }) 49 | .collect::>() 50 | } 51 | 52 | pub fn has_marker_attr(field: &Field, attr_name: &str) -> bool { 53 | for attr in field.attrs.iter() { 54 | match attr.meta { 55 | Meta::Path(ref val) => { 56 | if let Some(ident) = val.get_ident() { 57 | return ident == attr_name; 58 | } 59 | } 60 | _ => continue, 61 | } 62 | } 63 | false 64 | } 65 | 66 | pub fn get_fields<'a>(ast: &'a DeriveInput, trait_name: &str) -> &'a FieldsNamed { 67 | let vd = match ast.data { 68 | Data::Enum(_) | Data::Union(_) => { 69 | panic!("{trait_name} can be only implemented for structures") 70 | } 71 | Data::Struct(ref val) => val, 72 | }; 73 | match vd.fields { 74 | Fields::Named(ref f) => f, 75 | Fields::Unnamed(_) | Fields::Unit => { 76 | panic!("{trait_name} can be only implemented for structures") 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /dlopen2-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "128"] 2 | 3 | extern crate proc_macro; 4 | extern crate quote; 5 | extern crate syn; 6 | 7 | mod api; 8 | mod common; 9 | mod multi_api; 10 | mod wrapper; 11 | 12 | use api::impl_library_api; 13 | use multi_api::impl_wrapper_multi_api; 14 | use proc_macro::TokenStream; 15 | use syn::{parse_macro_input, DeriveInput}; 16 | use wrapper::impl_wrapper_api; 17 | 18 | #[proc_macro_derive(WrapperApi, attributes(dlopen2_name, dlopen2_allow_null))] 19 | pub fn wrapper_api(input: TokenStream) -> TokenStream { 20 | // Parse the string representation 21 | let ast = parse_macro_input!(input as DeriveInput); 22 | 23 | // Build the impl 24 | let gen = impl_wrapper_api(&ast); 25 | 26 | // Return the generated impl 27 | TokenStream::from(gen) 28 | } 29 | 30 | #[proc_macro_derive(WrapperMultiApi)] 31 | pub fn wrapper_multi_api(input: TokenStream) -> TokenStream { 32 | // Parse the string representation 33 | let ast = parse_macro_input!(input as DeriveInput); 34 | 35 | // Build the impl 36 | let gen = impl_wrapper_multi_api(&ast); 37 | 38 | // Return the generated impl 39 | TokenStream::from(gen) 40 | } 41 | 42 | #[proc_macro_derive(SymBorApi, attributes(dlopen2_name))] 43 | pub fn library_api(input: TokenStream) -> TokenStream { 44 | // Parse the string representation 45 | let ast = parse_macro_input!(input as DeriveInput); 46 | 47 | // Build the impl 48 | let gen = impl_library_api(&ast); 49 | 50 | // Return the generated impl 51 | TokenStream::from(gen) 52 | } 53 | -------------------------------------------------------------------------------- /dlopen2-derive/src/multi_api.rs: -------------------------------------------------------------------------------- 1 | use super::common::get_fields; 2 | use quote::quote; 3 | use syn::{DeriveInput, Field}; 4 | 5 | const TRATIT_NAME: &str = "WrapperMultiApi"; 6 | 7 | pub fn impl_wrapper_multi_api(ast: &DeriveInput) -> proc_macro2::TokenStream { 8 | let name = &ast.ident; 9 | let generics = &ast.generics; 10 | let fields = get_fields(ast, TRATIT_NAME); 11 | 12 | let tok_iter = fields.named.iter().map(field_to_tokens); 13 | let q = quote! { 14 | impl #generics WrapperMultiApi for #name #generics{} 15 | 16 | impl #generics ::dlopen2::wrapper::WrapperApi for # name #generics{ 17 | unsafe fn load(lib: & ::dlopen2::raw::Library) -> ::std::result::Result { 18 | ::std::result::Result::Ok(#name { 19 | #(#tok_iter),* 20 | }) 21 | } 22 | } 23 | }; 24 | 25 | q 26 | } 27 | 28 | fn field_to_tokens(field: &Field) -> proc_macro2::TokenStream { 29 | let field_name = &field.ident; 30 | 31 | quote! { 32 | #field_name: ::dlopen2::wrapper::WrapperApi::load(&lib)? 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /dlopen2-derive/src/wrapper.rs: -------------------------------------------------------------------------------- 1 | use super::common::{get_fields, get_non_marker_attrs, has_marker_attr, symbol_name}; 2 | use quote::quote; 3 | use syn::{self, BareFnArg, DeriveInput, Field, GenericArgument, Type, TypePtr, Visibility}; 4 | 5 | const ALLOW_NULL: &str = "dlopen2_allow_null"; 6 | const TRAIT_NAME: &str = "WrapperApi"; 7 | 8 | pub fn impl_wrapper_api(ast: &DeriveInput) -> proc_macro2::TokenStream { 9 | let struct_name = &ast.ident; 10 | let fields = get_fields(ast, TRAIT_NAME); 11 | let generics = &ast.generics; 12 | // make sure that all fields are private - panic otherwise 13 | // make sure that all fields are identifiable - panic otherwise 14 | for field in fields.named.iter() { 15 | let _ = field 16 | .ident 17 | .as_ref() 18 | .expect("All fields of structures deriving WrapperAPI need to be identificable"); 19 | match field.vis { 20 | Visibility::Inherited => (), 21 | _ => panic!( 22 | "All fields of structures deriving {} need to be private and '{}' is not", 23 | TRAIT_NAME, 24 | field.ident.as_ref().unwrap() 25 | ), 26 | } 27 | } 28 | 29 | let field_iter = fields.named.iter().map(field_to_tokens); 30 | let wrapper_iter = fields.named.iter().filter_map(field_to_wrapper); 31 | let q = quote! { 32 | impl #generics WrapperApi for #struct_name #generics { 33 | unsafe fn load(lib: & ::dlopen2::raw::Library ) -> ::std::result::Result { 34 | Ok(Self{ 35 | #(#field_iter),* 36 | }) 37 | } 38 | } 39 | 40 | #[allow(dead_code)] 41 | impl #generics #struct_name #generics { 42 | #(#wrapper_iter)* 43 | } 44 | }; 45 | 46 | q 47 | } 48 | 49 | fn field_to_tokens(field: &Field) -> proc_macro2::TokenStream { 50 | let allow_null = has_marker_attr(field, ALLOW_NULL); 51 | match skip_groups(&field.ty) { 52 | Type::BareFn(_) | Type::Reference(_) => { 53 | if allow_null { 54 | panic!("Only pointers can have the '{ALLOW_NULL}' attribute assigned"); 55 | } 56 | normal_field(field) 57 | } 58 | Type::Ptr(ref ptr) => { 59 | if allow_null { 60 | allow_null_field(field, ptr) 61 | } else { 62 | normal_field(field) 63 | } 64 | } 65 | Type::Path(ref path) => { 66 | let path = &path.path; 67 | let segments_string: Vec = path 68 | .segments 69 | .iter() 70 | .map(|segment| segment.ident.to_string()) 71 | .collect(); 72 | let segments_str: Vec<&str> = segments_string 73 | .iter() 74 | .map(|segment| segment.as_str()) 75 | .collect(); 76 | match (path.leading_colon.is_some(), segments_str.as_slice()) { 77 | (_, ["core" | "std", "option", "Option"]) | (false, ["option", "Option"]) | (false, ["Option"]) => { 78 | optional_field(field) 79 | } 80 | _ => panic!("Only bare functions, optional bare functions, references and pointers are allowed in structures implementing WrapperApi trait") 81 | } 82 | } 83 | _ => { 84 | panic!("Only bare functions, references and pointers are allowed in structures implementing WrapperApi trait") 85 | } 86 | } 87 | } 88 | 89 | fn normal_field(field: &Field) -> proc_macro2::TokenStream { 90 | let field_name = &field.ident; 91 | let symbol_name = symbol_name(field); 92 | quote! { 93 | #field_name : lib.symbol_cstr( 94 | ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(#symbol_name, "\0").as_bytes()) 95 | )? 96 | } 97 | } 98 | 99 | fn allow_null_field(field: &Field, ptr: &TypePtr) -> proc_macro2::TokenStream { 100 | let field_name = &field.ident; 101 | let symbol_name = symbol_name(field); 102 | let null_fun = match ptr.mutability { 103 | Some(_) => quote! {null}, 104 | None => quote! {null_mut}, 105 | }; 106 | 107 | quote! { 108 | #field_name : match lib.symbol_cstr( 109 | ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(#symbol_name, "\0").as_bytes()) 110 | ) { 111 | ::std::result::Result::Ok(val) => val, 112 | ::std::result::Result::Err(err) => match err { 113 | ::dlopen2::Error::NullSymbol => ::std::ptr:: #null_fun (), 114 | _ => return ::std::result::Result::Err(err) 115 | } 116 | } 117 | } 118 | } 119 | 120 | fn optional_field(field: &Field) -> proc_macro2::TokenStream { 121 | let field_name = &field.ident; 122 | let symbol_name = symbol_name(field); 123 | 124 | let tokens = quote! { 125 | #field_name : match lib.symbol_cstr( 126 | ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(#symbol_name, "\0").as_bytes()) 127 | ) { 128 | ::std::result::Result::Ok(val) => Some(val), 129 | ::std::result::Result::Err(err) => match err { 130 | ::dlopen2::Error::NullSymbol => None, 131 | ::dlopen2::Error::SymbolGettingError(_) => None, 132 | _ => return ::std::result::Result::Err(err) 133 | } 134 | } 135 | }; 136 | tokens 137 | } 138 | 139 | fn skip_groups(ty: &Type) -> &Type { 140 | match ty { 141 | Type::Group(ref group) => skip_groups(&group.elem), 142 | _ => ty, 143 | } 144 | } 145 | 146 | fn field_to_wrapper(field: &Field) -> Option { 147 | let ident = field 148 | .ident 149 | .as_ref() 150 | .expect("Fields must have idents (tuple structs are not supported)"); 151 | let attrs = get_non_marker_attrs(field); 152 | 153 | match skip_groups(&field.ty) { 154 | Type::BareFn(ref fun) => { 155 | if fun.variadic.is_some() { 156 | None 157 | } else { 158 | let output = &fun.output; 159 | let unsafety = &fun.unsafety; 160 | let arg_iter = fun 161 | .inputs 162 | .iter() 163 | .map(|a| fun_arg_to_tokens(a, &ident.to_string())); 164 | let arg_names = fun.inputs.iter().map(|a| match a.name { 165 | ::std::option::Option::Some((ref arg_name, _)) => arg_name, 166 | ::std::option::Option::None => unreachable!(), 167 | }); 168 | Some(quote! { 169 | #(#attrs)* 170 | pub #unsafety fn #ident (&self, #(#arg_iter),* ) #output { 171 | (self.#ident)(#(#arg_names),*) 172 | } 173 | }) 174 | } 175 | } 176 | Type::Reference(ref ref_ty) => { 177 | let ty = &ref_ty.elem; 178 | let mut_acc = match ref_ty.mutability { 179 | Some(_token) => { 180 | let mut_ident = &format!("{ident}_mut"); 181 | let method_name = syn::Ident::new(mut_ident, ident.span()); 182 | Some(quote! { 183 | #(#attrs)* 184 | pub fn #method_name (&mut self) -> &mut #ty { 185 | self.#ident 186 | } 187 | }) 188 | } 189 | None => None, 190 | }; 191 | // constant accessor 192 | let const_acc = quote! { 193 | #(#attrs)* 194 | pub fn #ident (&self) -> & #ty { 195 | self.#ident 196 | } 197 | }; 198 | 199 | Some(quote! { 200 | #const_acc 201 | #mut_acc 202 | }) 203 | } 204 | Type::Ptr(_) => None, 205 | // For `field: Option ...>` 206 | Type::Path(ref path) => { 207 | let path = &path.path; 208 | let segments = &path.segments; 209 | let segment = segments 210 | .iter() 211 | .find(|segment| segment.ident == "Option") 212 | .unwrap(); 213 | let args = &segment.arguments; 214 | match args { 215 | syn::PathArguments::AngleBracketed(args) => match args.args.first().unwrap() { 216 | GenericArgument::Type(ty) => match skip_groups(ty) { 217 | Type::BareFn(fun) => { 218 | if fun.variadic.is_some() { 219 | None 220 | } else { 221 | let output = &fun.output; 222 | let output = match output { 223 | syn::ReturnType::Default => quote!(-> Option<()>), 224 | syn::ReturnType::Type(_, ty) => quote!( -> Option<#ty>), 225 | }; 226 | let unsafety = &fun.unsafety; 227 | let arg_iter = fun 228 | .inputs 229 | .iter() 230 | .map(|a| fun_arg_to_tokens(a, &ident.to_string())); 231 | let arg_names = fun.inputs.iter().map(|a| match a.name { 232 | ::std::option::Option::Some((ref arg_name, _)) => arg_name, 233 | ::std::option::Option::None => unreachable!(), 234 | }); 235 | let has_ident = quote::format_ident!("has_{}", ident); 236 | Some(quote! { 237 | #(#attrs)* 238 | pub #unsafety fn #ident (&self, #(#arg_iter),* ) #output { 239 | self.#ident.map(|f| (f)(#(#arg_names),*)) 240 | } 241 | #(#attrs)* 242 | pub fn #has_ident (&self) -> bool { 243 | self.#ident.is_some() 244 | } 245 | }) 246 | } 247 | } 248 | Type::Reference(ref_ty) => { 249 | let ty = &ref_ty.elem; 250 | match ref_ty.mutability { 251 | Some(_token) => { 252 | let mut_ident = &format!("{ident}"); 253 | let method_name = syn::Ident::new(mut_ident, ident.span()); 254 | Some(quote! { 255 | #(#attrs)* 256 | pub fn #method_name (&mut self) -> ::core::option::Option<&mut #ty> { 257 | if let Some(&mut ref mut val) = self.#ident { 258 | Some(val) 259 | } else { 260 | None 261 | } 262 | } 263 | }) 264 | } 265 | None => Some(quote! { 266 | #(#attrs)* 267 | pub fn #ident (&self) -> ::core::option::Option<& #ty> { 268 | self.#ident 269 | } 270 | }), 271 | } 272 | } 273 | _ => panic!("Unsupported field type"), 274 | }, 275 | _ => panic!("Unknown optional type!"), 276 | }, 277 | _ => panic!("Unknown optional type!"), 278 | } 279 | } 280 | _ => panic!("Unsupported field type"), 281 | } 282 | } 283 | 284 | fn fun_arg_to_tokens(arg: &BareFnArg, function_name: &str) -> proc_macro2::TokenStream { 285 | let arg_name = match arg.name { 286 | Some(ref val) => &val.0, 287 | None => panic!("Function {function_name} has an unnamed argument."), 288 | }; 289 | let ty = &arg.ty; 290 | quote! { 291 | #arg_name: #ty 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /dlopen2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dlopen2" 3 | version = "0.8.0" 4 | authors = [ 5 | "Szymon Wieloch ", 6 | "Ahmed Masud ", 7 | "OpenByte "] 8 | description = "Library for opening and operating on dynamic link libraries (also known as shared objects or shared libraries)." 9 | keywords = [ 10 | "dlopen", "dll", "so", "dylib", "shared"] 11 | license-file = "../LICENSE" 12 | repository = "https://github.com/OpenByteDev/dlopen2" 13 | edition = "2021" 14 | readme = "../README.md" 15 | 16 | [dependencies] 17 | dlopen2_derive = { path = "../dlopen2-derive", version = "0.4", optional = true } 18 | once_cell = "1.19" 19 | 20 | [target.'cfg(windows)'.dependencies] 21 | winapi = { version = "0.3", features = ["winnt", "minwindef", "winerror", "libloaderapi", "errhandlingapi", "dbghelp", "processthreadsapi", "basetsd"] } 22 | 23 | [target.'cfg(unix)'.dependencies] 24 | libc = "0.2" 25 | 26 | [dev-dependencies] 27 | regex = "1.10" 28 | serde = { version = "1.0", features = ["derive"] } 29 | serde_json = "1.0" 30 | example_dylib = { path = "../example-dylib" } 31 | current_platform = "0.2" 32 | 33 | 34 | [features] 35 | default = ["wrapper", "symbor", "derive"] 36 | wrapper = [] 37 | symbor = [] 38 | derive = ["dlopen2_derive"] 39 | doc_cfg = [] 40 | 41 | 42 | [[example]] 43 | name = "raw" 44 | crate-type = ["bin"] 45 | 46 | [[example]] 47 | name = "symbor" 48 | crate-type = ["bin"] 49 | 50 | [[example]] 51 | name = "symbor_api" 52 | crate-type = ["bin"] 53 | 54 | [[example]] 55 | name = "wrapper_api" 56 | crate-type = ["bin"] 57 | 58 | [[example]] 59 | name = "raw_addr_info" 60 | crate-type = ["bin"] 61 | 62 | [[example]] 63 | name = "wrapper_multi_api" 64 | crate-type = ["bin"] 65 | 66 | 67 | [package.metadata.docs.rs] 68 | all-features = true 69 | -------------------------------------------------------------------------------- /dlopen2/examples/README.md: -------------------------------------------------------------------------------- 1 | # dlopen2 examples 2 | 3 | Files in directory perform very similar operations 4 | but they use different APIs. You can compare these 5 | approaches and choose the API that suits your needs. 6 | Operations include calling both Rust and C functions, 7 | access to constant and mutable static data, 8 | modifying mutable data and operations on common data types. 9 | 10 | All examples use an example library that gets built 11 | together with this project. It covers most types 12 | of exported symbols, therefor it allows you to check 13 | if the library actually obtains correctly all kinds 14 | of symbols. 15 | -------------------------------------------------------------------------------- /dlopen2/examples/commons/mod.rs: -------------------------------------------------------------------------------- 1 | use dlopen2::utils::{PLATFORM_FILE_EXTENSION, PLATFORM_FILE_PREFIX}; 2 | use std::env; 3 | use std::os::raw::c_int; 4 | use std::path::PathBuf; 5 | 6 | //Rust when building dependencies adds some weird numbers to file names 7 | // find the file using this pattern: 8 | //const FILE_PATTERN: &str = concat!(PLATFORM_FILE_PREFIX, "example.*\\.", PLATFORM_FILE_EXTENSION); 9 | 10 | use serde::Deserialize; 11 | #[derive(Deserialize)] 12 | struct Manifest { 13 | workspace_root: String, 14 | } 15 | 16 | pub fn example_lib_path() -> PathBuf { 17 | let file_pattern = format!( 18 | r"{}example.*\.{}", 19 | PLATFORM_FILE_PREFIX, PLATFORM_FILE_EXTENSION 20 | ); 21 | let file_regex = regex::Regex::new(file_pattern.as_ref()).unwrap(); 22 | //build path to the example library that covers most cases 23 | let output = std::process::Command::new(env!("CARGO")) 24 | .arg("metadata") 25 | .arg("--format-version=1") 26 | .output() 27 | .unwrap(); 28 | let manifest: Manifest = serde_json::from_slice(&output.stdout).unwrap(); 29 | let mut lib_path = PathBuf::from(manifest.workspace_root); 30 | lib_path.extend(["target", "debug", "deps"].iter()); 31 | let entry = lib_path.read_dir().unwrap().find(|e| match *e { 32 | Ok(ref entry) => file_regex.is_match(entry.file_name().to_str().unwrap()), 33 | Err(ref err) => panic!("Could not read cargo debug directory: {}", err), 34 | }); 35 | lib_path.push(entry.unwrap().unwrap().file_name()); 36 | println!("Library path: {}", lib_path.to_str().unwrap()); 37 | lib_path 38 | } 39 | 40 | #[allow(dead_code)] //not all examples use this and this generates warnings 41 | #[repr(C)] 42 | pub struct SomeData { 43 | pub first: c_int, 44 | pub second: c_int, 45 | } 46 | -------------------------------------------------------------------------------- /dlopen2/examples/raw.rs: -------------------------------------------------------------------------------- 1 | mod commons; 2 | 3 | use commons::{example_lib_path, SomeData}; 4 | use dlopen2::raw::Library; 5 | use std::ffi::CStr; 6 | use std::os::raw::{c_char, c_int}; 7 | 8 | fn main() { 9 | let lib_path = example_lib_path(); 10 | let lib = Library::open(lib_path).expect("Could not open library"); 11 | 12 | //get several symbols and play around 13 | let rust_fun_print_something: fn() = 14 | unsafe { lib.symbol_cstr(c"rust_fun_print_something") }.unwrap(); 15 | rust_fun_print_something(); 16 | 17 | let rust_fun_add_one: fn(i32) -> i32 = unsafe { lib.symbol_cstr(c"rust_fun_add_one") }.unwrap(); 18 | println!(" 5+1={}", rust_fun_add_one(5)); 19 | 20 | let c_fun_print_something_else: unsafe extern "C" fn() = 21 | unsafe { lib.symbol_cstr(c"c_fun_print_something_else") }.unwrap(); 22 | unsafe { c_fun_print_something_else() }; 23 | 24 | let c_fun_add_two: unsafe extern "C" fn(c_int) -> c_int = 25 | unsafe { lib.symbol_cstr(c"c_fun_add_two") }.unwrap(); 26 | println!("2+2={}", unsafe { c_fun_add_two(2) }); 27 | 28 | let rust_i32: &i32 = unsafe { lib.symbol_cstr(c"rust_i32") }.unwrap(); 29 | println!("const rust i32 value: {}", rust_i32); 30 | 31 | let rust_i32_mut: &mut i32 = unsafe { lib.symbol_cstr(c"rust_i32_mut") }.unwrap(); 32 | println!("mutable rust i32 value: {}", rust_i32_mut); 33 | 34 | *rust_i32_mut = 55; 35 | //for a change use pointer to obtain its value 36 | let rust_i32_ptr: *const i32 = unsafe { lib.symbol_cstr(c"rust_i32_mut") }.unwrap(); 37 | println!("after change: {}", unsafe { *rust_i32_ptr }); 38 | 39 | //the same with C 40 | let c_int: &c_int = unsafe { lib.symbol_cstr(c"c_int") }.unwrap(); 41 | println!("c_int={}", c_int); 42 | 43 | //now static c struct 44 | 45 | let c_struct: &SomeData = unsafe { lib.symbol_cstr(c"c_struct") }.unwrap(); 46 | println!( 47 | "c struct first: {}, second:{}", 48 | c_struct.first, c_struct.second 49 | ); 50 | 51 | //let's play with strings 52 | 53 | let rust_str: &&str = unsafe { lib.symbol_cstr(c"rust_str") }.unwrap(); 54 | println!("Rust says: {}", *rust_str); 55 | 56 | let c_const_char_ptr: *const c_char = unsafe { lib.symbol_cstr(c"c_const_char_ptr") }.unwrap(); 57 | let converted = unsafe { CStr::from_ptr(c_const_char_ptr) } 58 | .to_str() 59 | .unwrap(); 60 | println!("And now C says: {}", converted); 61 | } 62 | -------------------------------------------------------------------------------- /dlopen2/examples/raw_addr_info.rs: -------------------------------------------------------------------------------- 1 | mod commons; 2 | 3 | use commons::example_lib_path; 4 | use dlopen2::raw::{AddressInfoObtainer, Library}; 5 | use std::os::raw::c_int; 6 | 7 | fn main() { 8 | let lib_path = example_lib_path(); 9 | let lib = Library::open(&lib_path).expect("Could not open library"); 10 | let c_fun_add_two: unsafe extern "C" fn(c_int) -> c_int = 11 | unsafe { lib.symbol("c_fun_add_two") }.unwrap(); 12 | let aio = AddressInfoObtainer::new(); 13 | let ai = unsafe { aio.obtain(c_fun_add_two as *const ()) }.unwrap(); 14 | println!("{:?}", &ai); 15 | assert_eq!(&ai.dll_path, lib_path.to_str().unwrap()); 16 | let os = ai.overlapping_symbol.unwrap(); 17 | assert_eq!(os.name, "c_fun_add_two"); 18 | assert_eq!(os.addr, c_fun_add_two as *const ()) 19 | } 20 | -------------------------------------------------------------------------------- /dlopen2/examples/symbor.rs: -------------------------------------------------------------------------------- 1 | mod commons; 2 | 3 | use commons::{example_lib_path, SomeData}; 4 | use dlopen2::symbor::Library; 5 | use std::ffi::CStr; 6 | use std::os::raw::{c_char, c_int}; 7 | 8 | fn main() { 9 | let lib_path = example_lib_path(); 10 | let mut lib = Library::open(lib_path).expect("Could not open library"); 11 | 12 | let rust_fun_print_something = 13 | unsafe { lib.symbol_cstr::(c"rust_fun_print_something") }.unwrap(); 14 | rust_fun_print_something(); 15 | 16 | let rust_fun_add_one = 17 | unsafe { lib.symbol_cstr:: i32>(c"rust_fun_add_one") }.unwrap(); 18 | println!(" 5+1={}", rust_fun_add_one(5)); 19 | 20 | let c_fun_print_something_else = 21 | unsafe { lib.symbol_cstr::(c"c_fun_print_something_else") } 22 | .unwrap(); 23 | unsafe { c_fun_print_something_else() }; 24 | 25 | let c_fun_add_two = 26 | unsafe { lib.symbol_cstr:: c_int>(c"c_fun_add_two") } 27 | .unwrap(); 28 | println!("2+2={}", unsafe { c_fun_add_two(2) }); 29 | 30 | let rust_i32: &i32 = unsafe { lib.reference_cstr(c"rust_i32") }.unwrap(); 31 | println!("const rust i32 value: {}", rust_i32); 32 | 33 | let rust_i32_mut: &mut i32 = unsafe { lib.reference_mut_cstr(c"rust_i32_mut") }.unwrap(); 34 | println!("mutable rust i32 value: {}", rust_i32_mut); 35 | 36 | *rust_i32_mut = 55; 37 | 38 | //for a change use pointer to obtain its value 39 | let rust_i32_ptr = unsafe { lib.symbol_cstr::<*const i32>(c"rust_i32_mut") }.unwrap(); 40 | println!("after change: {}", unsafe { **rust_i32_ptr }); 41 | 42 | //the same with C 43 | let c_int: &c_int = unsafe { lib.reference_cstr(c"c_int") }.unwrap(); 44 | println!("c_int={}", c_int); 45 | 46 | //now static c struct 47 | let c_struct: &SomeData = unsafe { lib.reference_cstr(c"c_struct") }.unwrap(); 48 | println!( 49 | "c struct first: {}, second:{}", 50 | c_struct.first, c_struct.second 51 | ); 52 | 53 | //let's play with strings 54 | let rust_str: &&str = unsafe { lib.reference_cstr(c"rust_str") }.unwrap(); 55 | println!("Rust says: {}", *rust_str); 56 | 57 | let c_const_char_ptr = 58 | unsafe { lib.symbol_cstr::<*const c_char>(c"c_const_char_ptr") }.unwrap(); 59 | let converted = unsafe { CStr::from_ptr(*c_const_char_ptr) } 60 | .to_str() 61 | .unwrap(); 62 | println!("And now C says: {}", converted); 63 | } 64 | -------------------------------------------------------------------------------- /dlopen2/examples/symbor_api.rs: -------------------------------------------------------------------------------- 1 | mod commons; 2 | 3 | use commons::{example_lib_path, SomeData}; 4 | use dlopen2::symbor::{Library, PtrOrNull, Ref, RefMut, SymBorApi, Symbol}; 5 | use std::ffi::CStr; 6 | use std::os::raw::{c_char, c_int}; 7 | 8 | #[derive(SymBorApi)] 9 | struct Api<'a> { 10 | pub rust_fun_print_something: Symbol<'a, fn()>, 11 | pub rust_fun_add_one: Symbol<'a, fn(i32) -> i32>, 12 | pub c_fun_print_something_else: Symbol<'a, unsafe extern "C" fn()>, 13 | pub c_fun_add_two: Symbol<'a, unsafe extern "C" fn(c_int) -> c_int>, 14 | pub rust_i32: Ref<'a, i32>, 15 | pub rust_i32_mut: RefMut<'a, i32>, 16 | #[dlopen2_name = "rust_i32_mut"] 17 | pub rust_i32_ptr: Symbol<'a, *const i32>, 18 | pub c_int: Ref<'a, c_int>, 19 | pub c_struct: Ref<'a, SomeData>, 20 | pub rust_str: Ref<'a, &'static str>, 21 | pub c_const_char_ptr: PtrOrNull<'a, c_char>, 22 | } 23 | 24 | fn main() { 25 | let lib_path = example_lib_path(); 26 | let lib = Library::open(lib_path).expect("Could not open library"); 27 | let mut api = unsafe { Api::load(&lib) }.expect("Could not load the API"); 28 | 29 | (api.rust_fun_print_something)(); 30 | 31 | println!(" 5+1={}", (api.rust_fun_add_one)(5)); 32 | 33 | unsafe { (api.c_fun_print_something_else)() }; 34 | 35 | println!("2+2={}", unsafe { (api.c_fun_add_two)(2) }); 36 | 37 | println!("const rust i32 value: {}", *api.rust_i32); 38 | 39 | println!("mutable rust i32 value: {}", *api.rust_i32_mut); 40 | 41 | *api.rust_i32_mut = 55; 42 | 43 | //for a change use pointer to obtain its value 44 | println!("after change: {}", unsafe { **api.rust_i32_ptr }); 45 | 46 | //the same with C 47 | println!("c_int={}", *api.c_int); 48 | 49 | //now static c struct 50 | println!( 51 | "c struct first: {}, second:{}", 52 | api.c_struct.first, api.c_struct.second 53 | ); 54 | 55 | //let's play with strings 56 | println!("Rust says: {}", *api.rust_str); 57 | 58 | let converted = unsafe { CStr::from_ptr(*api.c_const_char_ptr) } 59 | .to_str() 60 | .unwrap(); 61 | println!("And now C says: {}", converted); 62 | } 63 | -------------------------------------------------------------------------------- /dlopen2/examples/wrapper_api.rs: -------------------------------------------------------------------------------- 1 | use dlopen2::wrapper::{Container, WrapperApi}; 2 | use std::ffi::CStr; 3 | use std::os::raw::{c_char, c_int}; 4 | 5 | mod commons; 6 | use commons::{example_lib_path, SomeData}; 7 | 8 | #[derive(WrapperApi)] 9 | struct Api<'a> { 10 | rust_fun_print_something: fn(), 11 | rust_fun_add_one: fn(arg: i32) -> i32, 12 | c_fun_print_something_else: unsafe extern "C" fn(), 13 | c_fun_add_two: unsafe extern "C" fn(arg: c_int) -> c_int, 14 | rust_i32: &'a i32, 15 | rust_i32_mut: &'a mut i32, 16 | #[dlopen2_name = "rust_i32_mut"] 17 | rust_i32_ptr: *const i32, 18 | c_int: &'a c_int, 19 | c_struct: &'a SomeData, 20 | rust_str: &'a &'static str, 21 | c_const_char_ptr: *const c_char, 22 | } 23 | 24 | //those methods won't be generated 25 | impl<'a> Api<'a> { 26 | fn rust_i32_ptr(&self) -> *const i32 { 27 | self.rust_i32_ptr 28 | } 29 | 30 | fn c_const_str(&self) -> &CStr { 31 | unsafe { CStr::from_ptr(self.c_const_char_ptr) } 32 | } 33 | } 34 | 35 | fn main() { 36 | let lib_path = example_lib_path(); 37 | let mut cont: Container = 38 | unsafe { Container::load(lib_path) }.expect("Could not open library or load symbols"); 39 | 40 | cont.rust_fun_print_something(); 41 | println!(" 5+1={}", cont.rust_fun_add_one(5)); 42 | unsafe { cont.c_fun_print_something_else() }; 43 | println!("2+2={}", unsafe { cont.c_fun_add_two(2) }); 44 | println!("const rust i32 value: {}", *cont.rust_i32()); 45 | println!("mutable rust i32 value: {}", *cont.rust_i32_mut()); 46 | *cont.rust_i32_mut_mut() = 55; 47 | println!("after change: {}", unsafe { *cont.rust_i32_ptr() }); 48 | //the same with C 49 | println!("c_int={}", *cont.c_int()); 50 | //now static c struct 51 | 52 | println!( 53 | "c struct first: {}, second:{}", 54 | cont.c_struct().first, 55 | cont.c_struct().second 56 | ); 57 | //let's play with strings 58 | 59 | println!("Rust says: {}", *cont.rust_str()); 60 | let converted = cont.c_const_str().to_str().unwrap(); 61 | println!("And now C says: {}", converted); 62 | } 63 | -------------------------------------------------------------------------------- /dlopen2/examples/wrapper_multi_api.rs: -------------------------------------------------------------------------------- 1 | mod commons; 2 | use commons::example_lib_path; 3 | 4 | use dlopen2::wrapper::{Container, WrapperApi, WrapperMultiApi}; 5 | use std::os::raw::c_int; 6 | 7 | //Define 3 APIs: 8 | 9 | #[derive(WrapperApi)] 10 | struct Working1<'a> { 11 | rust_fun_print_something: fn(), 12 | c_fun_add_two: unsafe extern "C" fn(arg: c_int) -> c_int, 13 | rust_i32_mut: &'a mut i32, 14 | } 15 | 16 | #[derive(WrapperApi)] 17 | struct Working2<'a> { 18 | rust_fun_add_one: fn(arg: i32) -> i32, 19 | c_fun_print_something_else: extern "C" fn(), 20 | rust_i32: &'a i32, 21 | } 22 | 23 | //this one wont' work in the example 24 | #[derive(WrapperApi)] 25 | struct NotWorking<'a> { 26 | some_rust_fun: fn(arg: i32) -> i32, 27 | some_c_fun: extern "C" fn(), 28 | some_rust_num: &'a u32, 29 | } 30 | 31 | //Now define a multi wrapper that wraps sub APIs into one bigger API. 32 | //This example assumes that the first API is obligatory and the other two are optional. 33 | 34 | #[derive(WrapperMultiApi)] 35 | struct Api<'a> { 36 | pub obligatory: Working1<'a>, 37 | pub optional1: Option>, 38 | pub optional2: Option>, 39 | } 40 | 41 | fn main() { 42 | let lib_path = example_lib_path(); 43 | let api: Container = unsafe { Container::load(lib_path) }.expect("Could not open library"); 44 | //use obligatory API: 45 | api.obligatory.rust_fun_print_something(); 46 | println!("4+2={}", unsafe { api.obligatory.c_fun_add_two(4) }); 47 | println!("static i32={}", api.obligatory.rust_i32_mut()); 48 | 49 | match api.optional1 { 50 | Some(ref opt) => { 51 | println!("First optional API loaded!"); 52 | println!("3+1={}", opt.rust_fun_add_one(3)); 53 | opt.c_fun_print_something_else(); 54 | println!("static value is {}", opt.rust_i32()) 55 | } 56 | None => println!("Could not load the first optional API"), 57 | } 58 | 59 | match api.optional2 { 60 | Some(ref opt) => { 61 | opt.some_c_fun(); 62 | println!("Second optional API loaded"); 63 | println!("result of some function: {}", opt.some_rust_fun(3)); 64 | println!("static value is {}", opt.some_rust_num()); 65 | } 66 | None => println!("Could not load the second optional API"), 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /dlopen2/src/err.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | use std::error::Error as ErrorTrait; 3 | use std::ffi::NulError; 4 | use std::fmt::{Display, Formatter, Result as FmtResult}; 5 | use std::io::Error as IoError; 6 | 7 | /// This is a library-specific error that is returned by all calls to all APIs. 8 | #[derive(Debug)] 9 | pub enum Error { 10 | /// Provided string could not be coverted into `std::ffi::CString` because it contained null 11 | /// character. 12 | NullCharacter(NulError), 13 | /// The library could not be opened. 14 | OpeningLibraryError(IoError), 15 | /// The symbol could not be obtained. 16 | SymbolGettingError(IoError), 17 | /// Value of the symbol was null. 18 | NullSymbol, 19 | /// Address could not be matched to a dynamic link library 20 | AddrNotMatchingDll(IoError), 21 | } 22 | 23 | impl ErrorTrait for Error { 24 | fn source(&self) -> Option<&(dyn ErrorTrait + 'static)> { 25 | use self::Error::*; 26 | match *self { 27 | NullCharacter(ref val) => Some(val), 28 | OpeningLibraryError(_) | SymbolGettingError(_) | NullSymbol | AddrNotMatchingDll(_) => { 29 | None 30 | } 31 | } 32 | } 33 | } 34 | 35 | impl Display for Error { 36 | fn fmt(&self, f: &mut Formatter) -> FmtResult { 37 | use self::Error::*; 38 | 39 | match self { 40 | NullCharacter(_) => write!(f, "String had a null character"), 41 | OpeningLibraryError(msg) => write!(f, "Could not open library: {msg}"), 42 | SymbolGettingError(msg) => { 43 | write!(f, "Could not obtain symbol from the library: {msg}") 44 | } 45 | NullSymbol => write!(f, "The symbol is NULL"), 46 | AddrNotMatchingDll(_) => write!(f, "Address does not match any dynamic link library"), 47 | } 48 | } 49 | } 50 | 51 | impl From for Error { 52 | fn from(val: NulError) -> Error { 53 | Error::NullCharacter(val) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /dlopen2/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | Library for opening and working with dynamic link libraries (also known as shared object). 4 | 5 | # Overview 6 | 7 | This library is an effort to make use of dynamic link libraries in Rust simple. 8 | Previously existing solutions were either unsafe, provided huge overhead of required writing too much code to achieve simple things. 9 | I hope that this library will help you to quickly get what you need and avoid errors. 10 | 11 | # Quick example 12 | 13 | ```no_run 14 | use dlopen2::wrapper::{Container, WrapperApi}; 15 | 16 | #[derive(WrapperApi)] 17 | struct Api<'a> { 18 | example_rust_fun: fn(arg: i32) -> u32, 19 | example_c_fun: unsafe extern "C" fn(), 20 | example_reference: &'a mut i32, 21 | // A function or field may not always exist in the library. 22 | example_c_fun_option: Option, 23 | example_reference_option: Option<&'a mut i32>, 24 | } 25 | 26 | fn main() { 27 | let mut cont: Container = 28 | unsafe { Container::load("libexample.so") }.expect("Could not open library or load symbols"); 29 | cont.example_rust_fun(5); 30 | unsafe { cont.example_c_fun() }; 31 | *cont.example_reference_mut() = 5; 32 | 33 | // Optional functions return Some(result) if the function is present or None if absent. 34 | unsafe { cont.example_c_fun_option() }; 35 | // Optional fields are Some(value) if present and None if absent. 36 | if let Some(example_reference) = cont.example_reference_option() { 37 | *example_reference = 5; 38 | } 39 | } 40 | ``` 41 | 42 | # Features 43 | 44 | ## Main features 45 | 46 | * Supports majority of platforms and is platform independent. 47 | * Is consistent with Rust error handling mechanism and makes making mistakes much more difficult. 48 | * Is very lightweight. It mostly uses zero cost wrappers to create safer abstractions over platform API. 49 | * Is thread safe. 50 | * Is object-oriented programming friendly. 51 | * Has a low-level API that provides full flexibility of using libraries. 52 | * Has two high-level APIs that protect against dangling symbols - each in its own way. 53 | * High level APIs support automatic loading of symbols into structures. You only need to define a 54 | structure that represents an API. The rest happens automatically and requires only minimal amount of code. 55 | * Automatic loading of symbols helps you to follow the DRY paradigm. 56 | 57 | ## Comparison with other libraries 58 | 59 | | Feature | dlopen2 | [libloading](https://github.com/nagisa/rust_libloading) | [sharedlib](https://github.com/Tyleo/sharedlib) | 60 | |------------------------------------|------------|---------------------------------------------------------|-------------------------------------------------| 61 | | Basic functionality | Yes | Yes | Yes | 62 | | Multiplatform | Yes | Yes | Yes | 63 | | Dangling symbol prevention | Yes | Yes | Yes | 64 | | 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)** | 65 | | Loading of symbols into structures | Yes | **No** | **No** | 66 | | Overhead | Minimal | Minimal | **Some overhead** | 67 | | Low-level, unsafe API | Yes | Yes | Yes | 68 | | Object-oriented friendly | Yes | **No** | Yes | 69 | | Load from the program itself | Yes | **No** | **No** | 70 | | Obtaining address information (dladdr) | Yes | **Unix only** | **No** | 71 | 72 | ## Safety 73 | 74 | Please note that while Rust aims at being 100% safe language, it does not yet provide mechanisms that would allow me to create a 100% safe library, so I had to settle on 99%. 75 | Also the nature of dynamic link libraries requires casting obtained pointers into types that are defined on the application side. And this cannot be safe. 76 | Having said that I still think that this library provides the best approach and greatest safety possible in Rust. 77 | 78 | # Usage: 79 | 80 | Cargo.toml: 81 | 82 | ```toml 83 | [dependencies] 84 | dlopen2 = "0.6" 85 | ``` 86 | 87 | # Documentation 88 | 89 | [Cargo documentation](https://docs.rs/dlopen2) 90 | 91 | [Examples](../examples) 92 | 93 | [Changelog](https://github.com/OpenByteDev/dlopen2/releases) 94 | 95 | # License 96 | This code is licensed under the [MIT](../LICENSE) license. 97 | 98 | # Acknowledgement 99 | 100 | Special thanks to [Simonas Kazlauskas](https://github.com/nagisa) whose [libloading](https://github.com/nagisa/rust_libloading) became code base for my project. 101 | 102 | # Comparison of APIs: 103 | 104 | * [**raw**](./raw/index.html) - a low-level API. It is mainly intended to give you full flexibility 105 | if you decide to create you own custom solution for handling dynamic link libraries. 106 | For typical operations you probably should use one of high-level APIs. 107 | 108 | * [**symbor**](./symbor/index.html) - a high-level API. It prevents dangling symbols by creating 109 | zero cost structural wrappers around symbols obtained from the library. These wrappers use 110 | Rust borrowing mechanism to make sure that the library will never get released before obtained 111 | symbols. 112 | 113 | * [**wrapper**](./wrapper/index.html) - a high-level API. It prevents dangling symbols by creating 114 | zero cost functional wrappers around symbols obtained from the library. These wrappers prevent 115 | accidental copying of raw symbols from library API. Dangling symbols are prevented by keeping 116 | library and its API in one structure - this makes sure that symbols and library are released 117 | together. 118 | 119 | Additionally both high-level APIs provide a way to automatically load symbols into a structure using 120 | Rust reflection mechanism. Decision which API should be used is a matter of taste - please check 121 | documentation of both of them and use the one that you prefer. 122 | At the moment none seems to have any reasonable advantage over the other. 123 | 124 | */ 125 | 126 | #![allow(clippy::missing_safety_doc, clippy::needless_doctest_main)] 127 | #![cfg_attr(feature = "doc_cfg", feature(doc_cfg))] 128 | 129 | mod err; 130 | pub mod raw; 131 | #[cfg(feature = "symbor")] 132 | #[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "symbor")))] 133 | pub mod symbor; 134 | pub mod utils; 135 | #[cfg(feature = "wrapper")] 136 | #[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "wrapper")))] 137 | pub mod wrapper; 138 | pub use err::Error; 139 | -------------------------------------------------------------------------------- /dlopen2/src/raw/common.rs: -------------------------------------------------------------------------------- 1 | use super::super::err::Error; 2 | use std::ffi::{CStr, CString, OsStr}; 3 | 4 | //choose the right platform implementation here 5 | #[cfg(unix)] 6 | use super::unix::{ 7 | addr_info_cleanup, addr_info_init, addr_info_obtain, close_lib, get_sym, open_lib, open_self, 8 | }; 9 | #[cfg(windows)] 10 | use super::windows::{ 11 | addr_info_cleanup, addr_info_init, addr_info_obtain, close_lib, get_sym, open_lib, open_self, 12 | }; 13 | 14 | #[cfg(unix)] 15 | pub use super::unix::Handle; 16 | #[cfg(windows)] 17 | pub use super::windows::Handle; 18 | 19 | use std::mem::{size_of, transmute_copy}; 20 | 21 | /** 22 | Main interface for opening and working with a dynamic link library. 23 | 24 | **Note:** Several methods have their "*_cstr" equivalents. This is because all native OS 25 | interfaces actually use C-strings. If you pass 26 | [`CStr`](https://doc.rust-lang.org/std/ffi/struct.CStr.html) 27 | as an argument, Library doesn't need to perform additional conversion from Rust string to 28 | C-string.. This makes `*_cstr" functions slightly more optimal than their normal equivalents. 29 | It is recommended that you use 30 | [const-cstr](https://github.com/abonander/const-cstr) crate to create statically allocated 31 | C-strings. 32 | 33 | **Note:** The handle to the library gets released when the library object gets dropped. 34 | Unless your application opened the library multiple times, this is the moment when symbols 35 | obtained from the library become dangling symbols. 36 | */ 37 | #[derive(Debug)] 38 | pub struct Library { 39 | handle: Handle, 40 | } 41 | 42 | impl Library { 43 | /** 44 | Open a dynamic library. 45 | 46 | **Note:** different platforms search for libraries in different directories. 47 | Therefore this function cannot be 100% platform independent. 48 | However it seems that all platforms support the full path and 49 | searching in default os directories if you provide only the file name. 50 | Please refer to your operating system guide for precise information about the directories 51 | where the operating system searches for dynamic link libraries. 52 | 53 | # Example 54 | 55 | ```no_run 56 | use dlopen2::raw::Library; 57 | 58 | fn main() { 59 | //use full path 60 | let lib = Library::open("/lib/i386-linux-gnu/libm.so.6").unwrap(); 61 | //use only file name 62 | let lib = Library::open("libm.so.6").unwrap(); 63 | } 64 | ``` 65 | */ 66 | pub fn open(name: S) -> Result 67 | where 68 | S: AsRef, 69 | { 70 | Ok(Self { 71 | handle: unsafe { open_lib(name.as_ref(), None) }?, 72 | }) 73 | } 74 | 75 | /** 76 | Open a dynamic library with flags 77 | 78 | **Note:** different platforms search for libraries in different directories. 79 | Therefore this function cannot be 100% platform independent. 80 | However it seems that all platforms support the full path and 81 | searching in default os directories if you provide only the file name. 82 | Please refer to your operating system guide for precise information about the directories 83 | where the operating system searches for dynamic link libraries. 84 | 85 | Currently, flags only impact loading of libraries on unix-like platforms. 86 | 87 | # Example 88 | 89 | ```no_run 90 | use dlopen2::raw::Library; 91 | 92 | fn main() { 93 | //use full path 94 | let lib = Library::open_with_flags("/lib/i386-linux-gnu/libm.so.6", None).unwrap(); 95 | //use only file name 96 | let lib = Library::open("libm.so.6").unwrap(); 97 | } 98 | ``` 99 | */ 100 | pub fn open_with_flags(name: S, flags: Option) -> Result 101 | where 102 | S: AsRef, 103 | { 104 | Ok(Self { 105 | handle: unsafe { open_lib(name.as_ref(), flags) }?, 106 | }) 107 | } 108 | 109 | /** 110 | Open the main program itself as a library. 111 | 112 | This allows a shared library to load symbols of the program it was loaded 113 | into. 114 | */ 115 | pub fn open_self() -> Result { 116 | Ok(Self { 117 | handle: unsafe { open_self() }?, 118 | }) 119 | } 120 | 121 | /** 122 | Obtains a symbol from the opened library. 123 | 124 | **Note:** the `T` template type needs to have a size of a pointer. 125 | Because Rust does not support static casts at the moment, the size of the type 126 | is checked in runtime and causes panic if it doesn't match. 127 | 128 | **Note:** It is legal for a library to export null symbols. 129 | However this is something that almost nobody expects. 130 | Therefore allowing it here would bring many problems, especially if user obtains references 131 | or functions. 132 | This method checks the address value and returns `Error::NullSymbol` error if the value is null. 133 | If your code does require obtaining symbols with null value, please do something like this: 134 | 135 | # Example 136 | 137 | ```no_run 138 | use dlopen2::raw::Library; 139 | use dlopen2::Error; 140 | use std::ptr::null; 141 | fn main(){ 142 | let lib = Library::open("libyourlib.so").unwrap(); 143 | let ptr_or_null: * const i32 = match unsafe{ lib.symbol("symbolname") } { 144 | Ok(val) => val, 145 | Err(err) => match err { 146 | Error::NullSymbol => null(), 147 | _ => panic!("Could not obtain the symbol") 148 | } 149 | }; 150 | //do something with the symbol 151 | } 152 | ``` 153 | */ 154 | pub unsafe fn symbol(&self, name: &str) -> Result { 155 | let cname = CString::new(name)?; 156 | self.symbol_cstr(cname.as_ref()) 157 | } 158 | 159 | /// Equivalent of the `symbol` method but takes `CStr` as a argument. 160 | pub unsafe fn symbol_cstr(&self, name: &CStr) -> Result { 161 | //TODO: convert it to some kind of static assertion (not yet supported in Rust) 162 | //this comparison should be calculated by compiler at compilation time - zero cost 163 | if size_of::() != size_of::<*mut ()>() { 164 | panic!( 165 | "The type passed to dlopen2::Library::symbol() function has a different size than a \ 166 | pointer - cannot transmute" 167 | ); 168 | } 169 | let raw = get_sym(self.handle, name)?; 170 | if raw.is_null() { 171 | Err(Error::NullSymbol) 172 | } else { 173 | Ok(transmute_copy(&raw)) 174 | } 175 | } 176 | 177 | /** 178 | Returns the raw OS handle for the opened library. 179 | 180 | This is `HMODULE` on Windows and `*mut c_void` on Unix systems. Don't use unless absolutely necessary. 181 | */ 182 | pub unsafe fn into_raw(&self) -> Handle { 183 | self.handle 184 | } 185 | } 186 | 187 | impl Drop for Library { 188 | fn drop(&mut self) { 189 | self.handle = close_lib(self.handle); 190 | } 191 | } 192 | 193 | unsafe impl Sync for Library {} 194 | unsafe impl Send for Library {} 195 | 196 | /// Container for information about overlapping symbol from dynamic load library. 197 | #[derive(Debug)] 198 | pub struct OverlappingSymbol { 199 | /// Overlapping symbol name 200 | pub name: String, 201 | /// Overlapping symbol address 202 | pub addr: *const (), 203 | } 204 | 205 | /// Container for information about an address obtained from dynamic load library. 206 | #[derive(Debug)] 207 | pub struct AddressInfo { 208 | /// Path to the library that is the source of this symbol. 209 | pub dll_path: String, 210 | /// Base address of the library that is the source of this symbol. 211 | pub dll_base_addr: *const (), 212 | /// Information about the overlapping symbol from the dynamic load library. 213 | /// 214 | /// The information is optional since the given address may not overlap with any symbol. 215 | pub overlapping_symbol: Option, 216 | } 217 | 218 | /// Obtains information about an address previously loaded from a dynamic load library. 219 | pub struct AddressInfoObtainer {} 220 | 221 | impl Default for AddressInfoObtainer { 222 | fn default() -> Self { 223 | Self::new() 224 | } 225 | } 226 | 227 | impl AddressInfoObtainer { 228 | pub fn new() -> AddressInfoObtainer { 229 | unsafe { addr_info_init() }; 230 | AddressInfoObtainer {} 231 | } 232 | 233 | /** 234 | Obtains information about an address previously loaded from a dynamic load library. 235 | 236 | # Example 237 | 238 | ```no_run 239 | use dlopen2::raw::{Library, AddressInfoObtainer}; 240 | fn main() { 241 | let lib = Library::open("libyourlib.so").unwrap(); 242 | let ptr: * const i32 = unsafe{ lib.symbol("symbolname") }.unwrap(); 243 | 244 | // now we can obtain information about the symbol - library, base address etc. 245 | let aio = AddressInfoObtainer::new(); 246 | let addr_info = unsafe { aio.obtain(ptr as * const ()) }.unwrap(); 247 | println!("Library path: {}", &addr_info.dll_path); 248 | println!("Library base address: {:?}", addr_info.dll_base_addr); 249 | if let Some(os) = addr_info.overlapping_symbol{ 250 | println!("Overlapping symbol name: {}", &os.name); 251 | println!("Overlapping symbol address: {:?}", os.addr); 252 | } 253 | 254 | } 255 | ``` 256 | */ 257 | pub unsafe fn obtain(&self, addr: *const ()) -> Result { 258 | addr_info_obtain(addr) 259 | } 260 | } 261 | 262 | impl Drop for AddressInfoObtainer { 263 | fn drop(&mut self) { 264 | unsafe { addr_info_cleanup() } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /dlopen2/src/raw/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Low-level API for opening and getting raw symbols from dynamic link libraries. 3 | 4 | As a low-level API it returns raw pointers, references and functions from loaded libraries. 5 | This means that this API does not provide any protection against problems with dangling symbols. 6 | You may consider using other APIs to achieve better safety. 7 | However this API is the most flexible one and you may find is useful when creating your custom 8 | approach to loading dynamic link libraries. 9 | 10 | # Example 11 | ```no_run 12 | use dlopen2::raw::Library; 13 | fn main(){ 14 | let lib = Library::open("libexample.so").unwrap(); 15 | let fun_add_one: unsafe extern "C" fn(i32)->i32 = unsafe{lib.symbol("add_one")}.unwrap(); 16 | println!("1+1= {}", unsafe{fun_add_one(1)}); 17 | 18 | drop(lib); 19 | //warning! fun_add_one is now a dangling symbol and use of it may crash your application. 20 | } 21 | ``` 22 | */ 23 | 24 | //! 25 | 26 | mod common; 27 | #[cfg(test)] 28 | mod tests; 29 | #[cfg(unix)] 30 | mod unix; 31 | #[cfg(windows)] 32 | mod windows; 33 | 34 | pub use self::common::{AddressInfo, AddressInfoObtainer, Handle, Library, OverlappingSymbol}; 35 | -------------------------------------------------------------------------------- /dlopen2/src/raw/tests.rs: -------------------------------------------------------------------------------- 1 | use super::super::err::Error; 2 | #[cfg(unix)] 3 | use super::unix::{close_lib, get_sym, open_lib}; 4 | #[cfg(windows)] 5 | use super::windows::{close_lib, get_sym, open_lib}; 6 | use std::ffi::CStr; 7 | 8 | #[cfg(windows)] 9 | const EXISTING_LIB: &str = "kernel32.dll"; 10 | #[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))] 11 | const EXISTING_LIB: &str = "libm.so.6"; 12 | #[cfg(any(target_os = "macos", target_os = "ios"))] 13 | const EXISTING_LIB: &str = "libm.dylib"; 14 | const NOT_EXISTING_LIB: &str = "notexisting.ext"; 15 | #[cfg(windows)] 16 | const EXISTING_SYM: &CStr = c"GetLastError"; 17 | #[cfg(unix)] 18 | const EXISTING_SYM: &CStr = c"cos"; 19 | const NOT_EXISTING_SYM: &CStr = c"notexisting"; 20 | 21 | //This is an example of opening and closing a library 22 | //It's going to work only on Windows but this is what it is supposed to do 23 | #[test] 24 | fn load_get_close() { 25 | unsafe { 26 | let handle = open_lib(EXISTING_LIB.as_ref(), None).expect("Could not open library"); 27 | let sym = get_sym(handle, EXISTING_SYM).expect("Could not get symbol"); 28 | assert!(!sym.is_null()); 29 | assert!(close_lib(handle).is_null()); 30 | } 31 | } 32 | 33 | #[test] 34 | fn open_err() { 35 | unsafe { 36 | match open_lib(NOT_EXISTING_LIB.as_ref(), None) { 37 | Ok(_) => panic!("Library should not get opened"), 38 | Err(err) => match err { 39 | Error::OpeningLibraryError(_) => (), 40 | _ => panic!("Invalid error kind"), 41 | }, 42 | } 43 | } 44 | } 45 | 46 | #[test] 47 | fn get_err() { 48 | unsafe { 49 | let handle = open_lib(EXISTING_LIB.as_ref(), None).expect("Could not open library"); 50 | match get_sym(handle, NOT_EXISTING_SYM) { 51 | Ok(_) => panic!("Should not get the symbol"), 52 | Err(err) => match err { 53 | Error::SymbolGettingError(_) => (), 54 | _ => panic!("Invalid error kind"), 55 | }, 56 | } 57 | assert!(close_lib(handle).is_null()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /dlopen2/src/raw/unix.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::let_unit_value)] 2 | 3 | use super::super::err::Error; 4 | use super::common::{AddressInfo, OverlappingSymbol}; 5 | use libc::{dladdr, dlclose, dlerror, dlopen, dlsym, Dl_info, RTLD_LAZY, RTLD_LOCAL}; 6 | use std::ffi::{CStr, OsStr}; 7 | use std::io::{Error as IoError, ErrorKind}; 8 | use std::os::raw::{c_int, c_void}; 9 | use std::os::unix::ffi::OsStrExt; 10 | use std::ptr::{null, null_mut}; 11 | 12 | const DEFAULT_FLAGS: c_int = RTLD_LOCAL | RTLD_LAZY; 13 | 14 | // calls to dlerror are not thread-safe on some platforms, 15 | // so we guard them with a mutex if required 16 | #[cfg(not(any(target_os = "linux", target_os = "macos")))] 17 | use { 18 | once_cell::sync::Lazy, 19 | std::sync::{LockResult, Mutex, MutexGuard}, 20 | }; 21 | #[cfg(not(any(target_os = "linux", target_os = "macos")))] 22 | fn lock_dlerror_mutex() -> LockResult> { 23 | static DLERROR_MUTEX: Lazy> = Lazy::new(|| Mutex::new(())); 24 | 25 | DLERROR_MUTEX.lock() 26 | } 27 | 28 | #[cfg(any(target_os = "linux", target_os = "macos"))] 29 | fn lock_dlerror_mutex() {} 30 | 31 | pub type Handle = *mut c_void; 32 | 33 | #[inline] 34 | pub unsafe fn get_sym(handle: Handle, name: &CStr) -> Result<*mut (), Error> { 35 | let _lock = lock_dlerror_mutex(); 36 | //clear the dlerror in order to be able to distinguish between NULL pointer and error 37 | let _ = dlerror(); 38 | let symbol = dlsym(handle, name.as_ptr()); 39 | //This can be either error or just the library has a NULL pointer - legal 40 | if symbol.is_null() { 41 | let msg = dlerror(); 42 | if !msg.is_null() { 43 | return Err(Error::SymbolGettingError(IoError::other( 44 | CStr::from_ptr(msg).to_string_lossy().to_string(), 45 | ))); 46 | } 47 | } 48 | Ok(symbol as *mut ()) 49 | } 50 | 51 | #[inline] 52 | pub unsafe fn open_self() -> Result { 53 | let _lock = lock_dlerror_mutex(); 54 | let handle = dlopen(null(), DEFAULT_FLAGS); 55 | if handle.is_null() { 56 | Err(Error::OpeningLibraryError(IoError::other( 57 | CStr::from_ptr(dlerror()).to_string_lossy().to_string(), 58 | ))) 59 | } else { 60 | Ok(handle) 61 | } 62 | } 63 | 64 | #[inline] 65 | pub unsafe fn open_lib(name: &OsStr, flags: Option) -> Result { 66 | let mut v: Vec = Vec::new(); 67 | //as_bytes i a unix-specific extension 68 | let cstr = if !name.is_empty() && name.as_bytes()[name.len() - 1] == 0 { 69 | //don't need to convert 70 | CStr::from_bytes_with_nul_unchecked(name.as_bytes()) 71 | } else { 72 | //need to convert 73 | v.extend_from_slice(name.as_bytes()); 74 | v.push(0); 75 | CStr::from_bytes_with_nul_unchecked(v.as_slice()) 76 | }; 77 | let _lock = lock_dlerror_mutex(); 78 | let handle = dlopen(cstr.as_ptr(), flags.unwrap_or(DEFAULT_FLAGS)); 79 | if handle.is_null() { 80 | Err(Error::OpeningLibraryError(IoError::other( 81 | CStr::from_ptr(dlerror()).to_string_lossy().to_string(), 82 | ))) 83 | } else { 84 | Ok(handle) 85 | } 86 | } 87 | 88 | #[inline] 89 | pub unsafe fn addr_info_init() {} 90 | #[inline] 91 | pub unsafe fn addr_info_cleanup() {} 92 | 93 | use std::mem::MaybeUninit; 94 | #[inline] 95 | pub fn addr_info_obtain(addr: *const ()) -> Result { 96 | // let mut dlinfo: Dl_info = unsafe{uninitialized()}; 97 | let mut dlinfo = MaybeUninit::::uninit(); 98 | let result = unsafe { dladdr(addr as *const c_void, dlinfo.as_mut_ptr()) }; 99 | if result == 0 { 100 | Err(Error::AddrNotMatchingDll(IoError::new( 101 | ErrorKind::NotFound, 102 | String::new(), 103 | ))) 104 | } else { 105 | let dlinfo = unsafe { dlinfo.assume_init() }; 106 | let os = if dlinfo.dli_saddr.is_null() || dlinfo.dli_sname.is_null() { 107 | None 108 | } else { 109 | Some(OverlappingSymbol { 110 | addr: dlinfo.dli_saddr as *const (), 111 | name: unsafe { 112 | CStr::from_ptr(dlinfo.dli_sname) 113 | .to_string_lossy() 114 | .into_owned() 115 | }, 116 | }) 117 | }; 118 | Ok(AddressInfo { 119 | dll_path: unsafe { 120 | CStr::from_ptr(dlinfo.dli_fname) 121 | .to_string_lossy() 122 | .into_owned() 123 | }, 124 | dll_base_addr: dlinfo.dli_fbase as *const (), 125 | overlapping_symbol: os, 126 | }) 127 | } 128 | } 129 | 130 | #[inline] 131 | pub fn close_lib(handle: Handle) -> Handle { 132 | let result = unsafe { dlclose(handle) }; 133 | if result != 0 { 134 | panic!("Call to dlclose() failed"); 135 | } 136 | null_mut() 137 | } 138 | -------------------------------------------------------------------------------- /dlopen2/src/raw/windows.rs: -------------------------------------------------------------------------------- 1 | use crate::utils; 2 | 3 | use super::super::err::Error; 4 | use super::common::{AddressInfo, OverlappingSymbol}; 5 | use once_cell::sync::{Lazy, OnceCell}; 6 | use std::ffi::{CStr, OsStr, OsString}; 7 | use std::io::Error as IoError; 8 | use std::mem::size_of; 9 | use std::os::windows::ffi::{OsStrExt, OsStringExt}; 10 | use std::ptr::null_mut; 11 | use std::slice; 12 | use std::sync::atomic::{AtomicBool, Ordering}; 13 | use std::sync::Mutex; 14 | use winapi::shared::basetsd::DWORD64; 15 | use winapi::shared::minwindef::{DWORD, HMODULE, TRUE}; 16 | use winapi::shared::winerror::ERROR_CALL_NOT_IMPLEMENTED; 17 | use winapi::um::dbghelp::{SymFromAddrW, SymGetModuleBase64, SymInitializeW, SYMBOL_INFOW}; 18 | use winapi::um::errhandlingapi::{GetLastError, SetErrorMode, SetThreadErrorMode}; 19 | use winapi::um::libloaderapi::{ 20 | FreeLibrary, GetModuleFileNameW, GetModuleHandleExW, GetProcAddress, LoadLibraryW, 21 | }; 22 | use winapi::um::processthreadsapi::GetCurrentProcess; 23 | use winapi::um::winnt::WCHAR; 24 | 25 | static USE_ERRORMODE: AtomicBool = AtomicBool::new(false); 26 | 27 | const PATH_MAX: DWORD = 256; 28 | const MAX_SYMBOL_LEN: usize = 256; 29 | 30 | struct SetErrorModeData { 31 | pub count: u32, 32 | pub previous: DWORD, 33 | } 34 | 35 | static SET_ERR_MODE_DATA: Lazy> = Lazy::new(|| { 36 | Mutex::new(SetErrorModeData { 37 | count: 0, 38 | previous: 0, 39 | }) 40 | }); 41 | static SYM_MUTEX: OnceCell> = OnceCell::new(); 42 | 43 | pub type Handle = HMODULE; 44 | 45 | /* 46 | Windows has an ugly feature: by default not finding the given library opens a window 47 | and passes control to the user. 48 | To fix this wee need to change thread/process error mode for the moment when the function is called 49 | and then revert it to the previous value. 50 | 51 | Since Windows 7 the SetThreadErrorMode function is supported. It sets error mode for the given 52 | thread. Older systems require calling SetErrorMode. This function sets error mode for the whole 53 | process. 54 | 55 | https://msdn.microsoft.com/pl-pl/library/windows/desktop/dd553630(v=vs.85).aspx 56 | */ 57 | 58 | const ERROR_MODE: DWORD = 1; // app handles everything 59 | 60 | enum ErrorModeGuard { 61 | ThreadPreviousValue(DWORD), 62 | DoNothing, 63 | Process, 64 | } 65 | 66 | impl ErrorModeGuard { 67 | fn new() -> Result { 68 | if !USE_ERRORMODE.load(Ordering::Acquire) { 69 | let mut previous: DWORD = 0; 70 | if unsafe { SetThreadErrorMode(ERROR_MODE, &mut previous) } == 0 { 71 | //error. On some systems SetThreadErrorMode may not be implemented 72 | let error = unsafe { GetLastError() }; 73 | if error == ERROR_CALL_NOT_IMPLEMENTED { 74 | USE_ERRORMODE.store(true, Ordering::Release); 75 | } else { 76 | //this is an actual error 77 | //SetErrorMode never fails. Shouldn't we use it now? 78 | return Err(IoError::from_raw_os_error(error as i32)); 79 | } 80 | } else { 81 | return Ok(if previous == ERROR_MODE { 82 | ErrorModeGuard::DoNothing 83 | } else { 84 | ErrorModeGuard::ThreadPreviousValue(previous) 85 | }); 86 | } 87 | } 88 | //several threads may be opening libraries at the same time. 89 | //we need to make sure that only the first one sets the erro mode 90 | //and only the last reverts it to the original value 91 | 92 | //poisoning should never happen 93 | let mut lock = SET_ERR_MODE_DATA.lock().expect("Mutex got poisoned"); 94 | if lock.count == 0 { 95 | lock.previous = unsafe { SetErrorMode(ERROR_MODE) }; 96 | if lock.previous == ERROR_MODE { 97 | return Ok(ErrorModeGuard::DoNothing); 98 | } 99 | } 100 | lock.count += 1; 101 | Ok(ErrorModeGuard::Process) 102 | } 103 | } 104 | 105 | impl Drop for ErrorModeGuard { 106 | fn drop(&mut self) { 107 | match *self { 108 | ErrorModeGuard::DoNothing => (), 109 | ErrorModeGuard::Process => { 110 | //poisoning should never happen 111 | let mut lock = SET_ERR_MODE_DATA.lock().expect("Mutex got poisoned"); 112 | lock.count -= 1; 113 | if lock.count == 0 { 114 | unsafe { SetErrorMode(lock.previous) }; 115 | } 116 | } 117 | ErrorModeGuard::ThreadPreviousValue(previous) => unsafe { 118 | SetThreadErrorMode(previous, null_mut()); 119 | }, 120 | } 121 | } 122 | } 123 | 124 | unsafe fn get_win_error() -> IoError { 125 | let error = GetLastError(); 126 | if error == 0 { 127 | IoError::other("Could not obtain information about the error") 128 | } else { 129 | IoError::from_raw_os_error(error as i32) 130 | } 131 | } 132 | 133 | #[inline] 134 | pub unsafe fn get_sym(handle: Handle, name: &CStr) -> Result<*mut (), Error> { 135 | let symbol = GetProcAddress(handle, name.as_ptr()); 136 | if symbol.is_null() { 137 | Err(Error::SymbolGettingError(get_win_error())) 138 | } else { 139 | Ok(symbol as *mut ()) 140 | } 141 | } 142 | 143 | #[inline] 144 | pub unsafe fn open_self() -> Result { 145 | let mut handle: Handle = null_mut(); 146 | if GetModuleHandleExW(0, null_mut(), &mut handle) == 0 { 147 | Err(Error::OpeningLibraryError(get_win_error())) 148 | } else { 149 | Ok(handle) 150 | } 151 | } 152 | 153 | #[inline] 154 | pub unsafe fn open_lib(name: &OsStr, _flags: Option) -> Result { 155 | let wide_name: Vec = name.encode_wide().chain(Some(0)).collect(); 156 | let _guard = match ErrorModeGuard::new() { 157 | Ok(val) => val, 158 | Err(err) => return Err(Error::OpeningLibraryError(err)), 159 | }; 160 | let handle = LoadLibraryW(wide_name.as_ptr()); 161 | if handle.is_null() { 162 | Err(Error::OpeningLibraryError(get_win_error())) 163 | } else { 164 | Ok(handle) 165 | } 166 | } 167 | 168 | #[inline] 169 | pub unsafe fn addr_info_init() { 170 | // calls to Sym* functions are not thread safe. 171 | SYM_MUTEX.get_or_init(|| { 172 | let process_handle = GetCurrentProcess(); 173 | let _result = SymInitializeW(process_handle, null_mut(), TRUE); 174 | Mutex::new(()) 175 | }); 176 | } 177 | 178 | #[inline] 179 | pub unsafe fn addr_info_obtain(addr: *const ()) -> Result { 180 | let process_handle = GetCurrentProcess(); 181 | 182 | let mut buffer = utils::maybe_uninit_uninit_array::(); 183 | let mut symbol_buffer = utils::maybe_uninit_uninit_array::< 184 | u8, 185 | { size_of::() + MAX_SYMBOL_LEN * size_of::() }, 186 | >(); 187 | let (module_base, path_len, symbol_info, result) = { 188 | // calls to Sym* functions are not thread safe. 189 | let mut _lock = SYM_MUTEX.get().unwrap().lock().expect("Mutex got poisoned"); 190 | let module_base = SymGetModuleBase64(process_handle, addr as u64); 191 | 192 | if module_base == 0 { 193 | return Err(Error::AddrNotMatchingDll(get_win_error())); 194 | } 195 | 196 | let path_len = GetModuleFileNameW(module_base as HMODULE, buffer[0].as_mut_ptr(), PATH_MAX); 197 | if path_len == 0 { 198 | return Err(Error::AddrNotMatchingDll(get_win_error())); 199 | } 200 | let symbol_info: *mut SYMBOL_INFOW = symbol_buffer.as_mut_ptr() as *mut SYMBOL_INFOW; 201 | 202 | (*symbol_info).SizeOfStruct = size_of::() as DWORD; 203 | (*symbol_info).MaxNameLen = MAX_SYMBOL_LEN as DWORD; 204 | let mut displacement: DWORD64 = 0; 205 | let result = SymFromAddrW( 206 | process_handle, 207 | addr as DWORD64, 208 | &mut displacement, 209 | symbol_info, 210 | ); 211 | (module_base, path_len, symbol_info, result) 212 | }; 213 | 214 | let os = if result == TRUE { 215 | let name_len = (*symbol_info).NameLen as usize; 216 | let name_slice = slice::from_raw_parts((*symbol_info).Name.as_ptr(), name_len); 217 | let name = OsString::from_wide(name_slice) 218 | .to_string_lossy() 219 | .into_owned(); 220 | //winapi doesn't have implementation of the SymSetOptions() for now 221 | //we need to manually strip off the namespace of the symbol. 222 | let name = match name.find("::") { 223 | None => name, 224 | Some(idx) => name[idx + 2..].to_string(), 225 | }; 226 | Some(OverlappingSymbol { 227 | name, 228 | addr, // on Windows there is no overlappping, just a straight match 229 | }) 230 | } else { 231 | None 232 | }; 233 | Ok({ 234 | AddressInfo { 235 | dll_path: OsString::from_wide(utils::maybe_uninit_slice_assume_init_ref( 236 | &buffer[0..(path_len as usize)], 237 | )) 238 | .to_string_lossy() 239 | .into_owned(), 240 | dll_base_addr: module_base as *const (), 241 | overlapping_symbol: os, 242 | } 243 | }) 244 | } 245 | 246 | #[inline] 247 | pub unsafe fn addr_info_cleanup() {} 248 | 249 | #[inline] 250 | pub fn close_lib(handle: Handle) -> Handle { 251 | if unsafe { FreeLibrary(handle) } == 0 { 252 | //this should not happen 253 | panic!("FreeLibrary() failed, the error is {}", unsafe { 254 | get_win_error() 255 | }); 256 | } 257 | null_mut() 258 | } 259 | -------------------------------------------------------------------------------- /dlopen2/src/symbor/api.rs: -------------------------------------------------------------------------------- 1 | use super::super::err::Error; 2 | use super::library::Library; 3 | /** 4 | Trait for automatic loading of symbols from library. 5 | 6 | This trait is intended to be used together with the `derive` macro. 7 | To use it you need to define a structure, create several fields that 8 | implement the `FromRawResult` trait and then simply use the automatically 9 | generated `load(&Library)` function to load all symbols from previously opened library. 10 | 11 | ```no_run 12 | use dlopen2::symbor::{Library, Symbol, SymBorApi, PtrOrNull, RefMut, PtrOrNullMut}; 13 | use std::os::raw::{c_double, c_char}; 14 | 15 | #[derive(SymBorApi)] 16 | struct Example<'a> { 17 | pub simple_fun: Symbol<'a, unsafe extern "C" fn()>, 18 | pub complex_fun: Symbol<'a, unsafe extern "C" fn(c_double)->c_double>, 19 | pub optional_fun: Option>, 20 | pub nullable_ptr: PtrOrNullMut<'a, c_char>, 21 | pub mut_ref_i32: Symbol<'a, &'a mut i32>, 22 | #[dlopen2_name="mut_ref_i32"] 23 | pub the_same_mut_ref_i32: RefMut<'a, i32>, 24 | pub not_nullable_ptr: Symbol<'a, * mut c_double> 25 | } 26 | 27 | fn main(){ 28 | let lib = Library::open("example.dll").expect("Could not open library"); 29 | let mut api = unsafe{Example::load(&lib)}.expect("Could not load symbols"); 30 | unsafe{(api.simple_fun)()}; 31 | let _ = unsafe{(api.complex_fun)(1.0)}; 32 | match api.optional_fun { 33 | Some(fun) => unsafe {fun()}, 34 | None => println!("Optional function could not be loaded"), 35 | }; 36 | if api.nullable_ptr.is_null(){ 37 | println!("Library has a null symbol"); 38 | } 39 | //while Symbol is good for everything, RefMut requires one less dereference to use 40 | **api.mut_ref_i32 =34; 41 | *api.the_same_mut_ref_i32 =35; 42 | unsafe{**api.not_nullable_ptr = 55.0}; 43 | unsafe{**api.nullable_ptr = 0}; 44 | } 45 | ``` 46 | 47 | Please notice several supported features: 48 | 49 | * By default `SymBorApi` uses the field name to obtain a symbol from the library. 50 | You can override the symbol name using the `dlopen2_name` attribute. 51 | * All kind of objects from the `symbor` module implement the Deref or DerefMut trait. 52 | This means that you can use them as if you would use primitive types that they wrap. 53 | * You can obtain optional symbols. This is very useful when you are dealing with 54 | different versions of libraries and the new versions support more functions. 55 | If it is not possible to obtain the given symbol, the option is set to `None', 56 | otherwise it contains the obtained symbol. 57 | * Both `Symbol` and `Ref` or `RefMut` can be used to obtain references to statically 58 | allocated objects. But `Ref` and `RefMut` are just easier to use - they require 59 | less dereferences to access the final value. 60 | Actually they behave like a normal reference does, it just that they implement the 61 | `FromRawResult` interface that allows them to be used inside structures that implement 62 | the `SymBorApi` trait. 63 | 64 | */ 65 | pub trait SymBorApi<'a> 66 | where 67 | Self: Sized, 68 | { 69 | unsafe fn load(lib: &'a Library) -> Result; 70 | } 71 | -------------------------------------------------------------------------------- /dlopen2/src/symbor/container.rs: -------------------------------------------------------------------------------- 1 | use crate::raw; 2 | 3 | use super::super::Error; 4 | use super::api::SymBorApi; 5 | use super::Library; 6 | use std::ffi::OsStr; 7 | use std::mem::transmute; 8 | use std::ops::{Deref, DerefMut}; 9 | 10 | /** 11 | Container for both dynamic link library handle and its API. 12 | 13 | This structure solves an important issue: object oriented programming where the given 14 | structure has two objects and one of the objects has a reference to the second one. 15 | Normally you can't put `Library` and a structure that implements `SymBorApi` into one structure. 16 | This structure allows you to do it. 17 | 18 | #Example 19 | 20 | ```no_run 21 | use dlopen2::symbor::{Library, Symbol, Ref, PtrOrNull, SymBorApi, Container}; 22 | 23 | #[derive(SymBorApi)] 24 | struct ExampleApi<'a> { 25 | pub fun: Symbol<'a, unsafe extern "C" fn(i32) -> i32>, 26 | pub glob_i32: Ref<'a, i32>, 27 | pub maybe_c_str: PtrOrNull<'a, u8>, 28 | } 29 | 30 | fn main(){ 31 | let cont: Container = unsafe{Container::load("libexample.so")} 32 | .expect("Could not load library or symbols"); 33 | println!("fun(4)={}", unsafe{(cont.fun)(4)}); 34 | println!("glob_i32={}", *cont.glob_i32); 35 | println!("The pointer is null={}", cont.maybe_c_str.is_null()); 36 | } 37 | ``` 38 | */ 39 | pub struct Container 40 | where 41 | T: SymBorApi<'static>, 42 | { 43 | #[allow(dead_code)] 44 | lib: Library, 45 | api: T, 46 | } 47 | 48 | impl Container 49 | where 50 | T: SymBorApi<'static>, 51 | { 52 | /// Open dynamic link library and load symbols. 53 | pub unsafe fn load(name: S) -> Result 54 | where 55 | S: AsRef, 56 | { 57 | let lib = Library::open(name)?; 58 | //this is cheating of course 59 | //but it is safe because Library and api is placed in the same structure 60 | //and therefore it is released at the same time. 61 | let static_ref: &'static Library = transmute(&lib); 62 | let api = T::load(static_ref)?; 63 | Ok(Self { api, lib }) 64 | } 65 | /// Load all symbols from the program itself. 66 | /// 67 | /// This allows a shared library to load symbols of the program it was 68 | /// loaded into. 69 | pub unsafe fn load_self() -> Result { 70 | let lib = Library::open_self()?; 71 | //this is cheating of course 72 | //but it is safe because Library and api is placed in the same structure 73 | //and therefore it is released at the same time. 74 | let static_ref: &'static Library = transmute(&lib); 75 | let api = T::load(static_ref)?; 76 | Ok(Self { api, lib }) 77 | } 78 | 79 | /** 80 | Returns the raw OS handle for the opened library. 81 | 82 | This is `HMODULE` on Windows and `*mut c_void` on Unix systems. Don't use unless absolutely necessary. 83 | */ 84 | pub unsafe fn into_raw(&self) -> raw::Handle { 85 | self.lib.into_raw() 86 | } 87 | } 88 | 89 | impl Deref for Container 90 | where 91 | T: SymBorApi<'static>, 92 | { 93 | type Target = T; 94 | fn deref(&self) -> &T { 95 | &self.api 96 | } 97 | } 98 | 99 | impl DerefMut for Container 100 | where 101 | T: SymBorApi<'static>, 102 | { 103 | fn deref_mut(&mut self) -> &mut T { 104 | &mut self.api 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /dlopen2/src/symbor/from_raw.rs: -------------------------------------------------------------------------------- 1 | use super::super::err::Error; 2 | use super::ptr_or_null::PtrOrNull; 3 | pub type RawResult<'a> = Result, Error>; 4 | 5 | /// Allows conversion of raw symbol result into the given symbol. 6 | /// 7 | /// This trait needs to be implemented by all members of structures that implement 8 | /// the `SymBorApi` trait. It is used to covert raw result obtained from library 9 | /// into the given object accessible to the user. 10 | /// 11 | /// **Note:** `Option where T: FromRawResult` also implements `FromRawResult`. 12 | /// This allows you to use options in structures implementing `SymBorApi`. If 13 | /// the symbol is found, the variable contains `Some(symbol)`, otherwise `None`. 14 | /// 15 | /// **Note:** You probably won't need to use it directly. 16 | pub trait FromRawResult 17 | where 18 | Self: Sized, 19 | { 20 | unsafe fn from_raw_result(raw: RawResult) -> Result; 21 | } 22 | -------------------------------------------------------------------------------- /dlopen2/src/symbor/library.rs: -------------------------------------------------------------------------------- 1 | use crate::raw; 2 | 3 | use super::super::raw::Library as RawLib; 4 | use super::ptr_or_null::PtrOrNull; 5 | use super::ptr_or_null_mut::PtrOrNullMut; 6 | use super::symbol::Symbol; 7 | use std::ffi::CStr; 8 | use std::ffi::{CString, OsStr}; 9 | use std::ptr::{null, null_mut}; 10 | 11 | use super::super::err::Error; 12 | 13 | /** 14 | Safe wrapper around dynamic link library handle. 15 | 16 | Methods of `Library` return only types that make the library borrowed. Therefore the problem with 17 | dangling symbols is prevented. 18 | 19 | **Note:**: It is recommended that you use certain methods in certain situations: 20 | 21 | * `symbol()` - for obtaining functions and pointers (but only if you can't use references 22 | instead of pointers and you do not accept null value of a pointer). 23 | * `reference()` and `reference_mut()` - for obtaining access to 24 | statically allocated objects - either constant or mutable. 25 | * `ptr_or_null()` and `ptr_or_null_mut()` - for obtaining pointers if you accept null values of 26 | pointers (in 99% of cases you should rather use previously mentioned methods). 27 | 28 | #Example 29 | 30 | ```no_run 31 | use dlopen2::symbor::Library; 32 | 33 | fn main(){ 34 | let lib = Library::open("libexample.dylib").unwrap(); 35 | let fun = unsafe{lib.symbol::("function")}.unwrap(); 36 | unsafe{fun()}; 37 | let glob_val: &mut u32 = unsafe{lib.reference_mut("glob_val")}.unwrap(); 38 | *glob_val = 42; 39 | let ptr_or_null = unsafe{lib.ptr_or_null::<()>("void_ptr")}.unwrap(); 40 | println!("Pointer is null: {}", ptr_or_null.is_null()); 41 | } 42 | ``` 43 | */ 44 | pub struct Library { 45 | lib: RawLib, 46 | } 47 | 48 | impl Library { 49 | /// Open dynamic link library using provided file name or path. 50 | pub fn open(name: S) -> Result 51 | where 52 | S: AsRef, 53 | { 54 | Ok(Library { 55 | lib: RawLib::open(name)?, 56 | }) 57 | } 58 | 59 | /// Open the program itself as library. 60 | /// 61 | /// This allows a shared library to load symbols of the program it was 62 | /// loaded into. 63 | pub fn open_self() -> Result { 64 | Ok(Library { 65 | lib: RawLib::open_self()?, 66 | }) 67 | } 68 | 69 | /// Obtain a symbol from library. 70 | /// 71 | /// This method is the most general one and allows obtaining basically everything assuming 72 | /// that the value of the given symbol cannot be null (use `ptr_or_null()` for this case). 73 | /// However the `reference()` and `reference_mut()` methods return a native reference and they 74 | /// are more programmer friendly when you try accessing statically allocated data in 75 | /// the library. 76 | pub unsafe fn symbol(&self, name: &str) -> Result, Error> { 77 | Ok(Symbol::new(self.lib.symbol(name)?)) 78 | } 79 | 80 | /// Equivalent of the `symbol()` method but takes `CStr` as a argument. 81 | pub unsafe fn symbol_cstr(&self, name: &CStr) -> Result, Error> { 82 | Ok(Symbol::new(self.lib.symbol_cstr(name)?)) 83 | } 84 | 85 | /// Obtain a const pointer from library. 86 | /// 87 | /// **Note:** This method is only recommended for data 88 | /// that can't be accessed as a reference and that can have a null pointer value 89 | /// (so not in 99% of cases). 90 | pub unsafe fn ptr_or_null(&self, name: &str) -> Result, Error> { 91 | let cname = CString::new(name)?; 92 | self.ptr_or_null_cstr(cname.as_ref()) 93 | } 94 | 95 | /// Equivalent of the `pointer()` method but takes `CStr` as a argument. 96 | pub unsafe fn ptr_or_null_cstr(&self, name: &CStr) -> Result, Error> { 97 | let raw_ptr = match self.lib.symbol_cstr(name) { 98 | Ok(val) => val, 99 | Err(err) => match err { 100 | Error::NullSymbol => null(), 101 | _ => return Err(err), 102 | }, 103 | }; 104 | Ok(PtrOrNull::new(raw_ptr)) 105 | } 106 | 107 | /// Obtain a mutable pointer from library. 108 | /// 109 | /// **Note:** This method is only recommended for data 110 | /// that can't be accessed as a reference and that can have a null pointer value 111 | /// (so not in 99% of cases). 112 | pub unsafe fn ptr_or_null_mut(&mut self, name: &str) -> Result, Error> { 113 | let cname = CString::new(name)?; 114 | self.ptr_or_null_mut_cstr(cname.as_ref()) 115 | } 116 | 117 | /// Equivalent of the `pointer_mut()` method but takes `CStr` as a argument. 118 | pub unsafe fn ptr_or_null_mut_cstr( 119 | &mut self, 120 | name: &CStr, 121 | ) -> Result, Error> { 122 | let raw_ptr = match self.lib.symbol_cstr(name) { 123 | Ok(val) => val, 124 | Err(err) => match err { 125 | Error::NullSymbol => null_mut(), 126 | _ => return Err(err), 127 | }, 128 | }; 129 | Ok(PtrOrNullMut::new(raw_ptr)) 130 | } 131 | 132 | /// Obtain const reference to statically allocated data in the library. 133 | pub unsafe fn reference(&self, name: &str) -> Result<&T, Error> { 134 | self.lib.symbol(name) 135 | } 136 | 137 | /// Equivalent of the `reference()` method but takes `CStr` as a argument. 138 | pub unsafe fn reference_cstr(&self, name: &CStr) -> Result<&T, Error> { 139 | self.lib.symbol_cstr(name) 140 | } 141 | 142 | /// Obtain mutable reference to statically allocated data in the library. 143 | pub unsafe fn reference_mut(&mut self, name: &str) -> Result<&mut T, Error> { 144 | self.lib.symbol(name) 145 | } 146 | 147 | /// Equivalent of the `reference_mut()` method but takes `CStr` as a argument. 148 | pub unsafe fn reference_mut_cstr(&mut self, name: &CStr) -> Result<&mut T, Error> { 149 | self.lib.symbol_cstr(name) 150 | } 151 | 152 | /** 153 | Returns the raw OS handle for the opened library. 154 | 155 | This is `HMODULE` on Windows and `*mut c_void` on Unix systems. Don't use unless absolutely necessary. 156 | */ 157 | pub unsafe fn into_raw(&self) -> raw::Handle { 158 | self.lib.into_raw() 159 | } 160 | } 161 | 162 | unsafe impl Send for Library {} 163 | unsafe impl Sync for Library {} 164 | -------------------------------------------------------------------------------- /dlopen2/src/symbor/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | High-level and safe API for opening and getting symbols from dynamic libraries. 3 | It is based on symbol borrowing mechanism and supports automatic loading of symbols into structures. 4 | 5 | This API uses Rust borrowing mechanism to prevent problems with dangling symbols 6 | that take place when the library gets closed but the symbols still exist and are used. 7 | 8 | #Example of a dangling symbol prevention 9 | ```no_run 10 | use dlopen2::symbor::Library; 11 | fn main(){ 12 | let lib = Library::open("libexample.dylib").unwrap(); 13 | let fun = unsafe{lib.symbol::f64>("some_symbol_name")}.unwrap(); 14 | println!("fun(1.0) = {}", unsafe{fun(1.0)}); 15 | 16 | //this would not compile because fun is a symbol borrowed from lib 17 | //drop(lib); 18 | } 19 | ``` 20 | **Note:** All kind of objects from the `symbor` module implement the Deref or DerefMut trait. 21 | This means that you can use them as if you would use primitive types that they wrap. 22 | 23 | It also allows automatic loading of symbols into a structure. 24 | This is especially handy if you have a huge API with multiple symbols: 25 | 26 | # Example of automatic symbol loading 27 | 28 | ```no_run 29 | use dlopen2::symbor::{Library, Symbol, Ref, PtrOrNull, SymBorApi}; 30 | 31 | #[derive(SymBorApi)] 32 | struct ExampleApi<'a> { 33 | pub fun: Symbol<'a, unsafe extern "C" fn(i32) -> i32>, 34 | pub glob_i32: Ref<'a, i32>, 35 | pub maybe_c_str: PtrOrNull<'a, u8>, 36 | pub opt_fun: Option> 37 | } 38 | 39 | fn main(){ 40 | let lib = Library::open("example.dll").expect("Could not open library"); 41 | let api = unsafe{ExampleApi::load(&lib)}.expect("Could not load symbols"); 42 | println!("fun(4)={}", unsafe{(api.fun)(4)}); 43 | println!("glob_i32={}", *api.glob_i32); 44 | println!("The pointer is null={}", api.maybe_c_str.is_null()); 45 | match api.opt_fun { 46 | Some(fun) => fun(), 47 | None => println!("Optional function not found in the library") 48 | } 49 | 50 | //this would not compile: 51 | //drop(lib); 52 | } 53 | ``` 54 | 55 | **Note:** You can obtain optional symbols (`Option>`). 56 | This is very useful when you are dealing with 57 | different versions of libraries and the newer versions support more functions. 58 | If it is not possible to obtain the given symbol, the option is set to `None', 59 | otherwise it contains the obtained symbol. 60 | 61 | Unfortunately in Rust it is not possible to create an API for dynamic link libraries that would 62 | be 100% safe. This API aims to be 99% safe by providing zero cost wrappers around raw symbols. 63 | However it is possible to make a mistake if you dereference safe wrappers into raw symbols. 64 | 65 | #Example of a mistake - dangling symbol 66 | 67 | ```no_run 68 | use dlopen2::symbor::Library; 69 | fn main(){ 70 | let raw_fun = { 71 | let lib = Library::open("libexample.dylib").unwrap(); 72 | let safe_fun = unsafe{ 73 | lib.symbol::f64>("some_symbol_name") 74 | }.unwrap(); 75 | *safe_fun 76 | }; 77 | 78 | //raw_fun is now a dangling symbol 79 | } 80 | ``` 81 | 82 | Original idea for this solution comes from Simonas Kazlauskas and his crate 83 | [libloading](https://github.com/nagisa/rust_libloading). 84 | Many improvements were added to solve several issues. 85 | 86 | */ 87 | 88 | mod api; 89 | mod container; 90 | mod from_raw; 91 | mod library; 92 | mod option; 93 | mod ptr_or_null; 94 | mod ptr_or_null_mut; 95 | mod reference; 96 | mod reference_mut; 97 | mod symbol; 98 | 99 | pub use self::api::SymBorApi; 100 | pub use self::container::Container; 101 | pub use self::from_raw::FromRawResult; 102 | pub use self::library::Library; 103 | pub use self::ptr_or_null::PtrOrNull; 104 | pub use self::ptr_or_null_mut::PtrOrNullMut; 105 | pub use self::reference::Ref; 106 | pub use self::reference_mut::RefMut; 107 | pub use self::symbol::Symbol; 108 | 109 | #[cfg(feature = "derive")] 110 | pub use dlopen2_derive::SymBorApi; 111 | -------------------------------------------------------------------------------- /dlopen2/src/symbor/option.rs: -------------------------------------------------------------------------------- 1 | use super::super::err::Error; 2 | use super::from_raw::{FromRawResult, RawResult}; 3 | 4 | impl FromRawResult for Option 5 | where 6 | T: FromRawResult, 7 | { 8 | unsafe fn from_raw_result(raw_result: RawResult) -> Result, Error> { 9 | match T::from_raw_result(raw_result) { 10 | Ok(val) => Ok(Some(val)), 11 | Err(_) => Ok(None), 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /dlopen2/src/symbor/ptr_or_null.rs: -------------------------------------------------------------------------------- 1 | use super::super::err::Error; 2 | use super::from_raw::{FromRawResult, RawResult}; 3 | use std::marker::PhantomData; 4 | use std::ops::Deref; 5 | 6 | /// Safe wrapper around const pointer. 7 | /// 8 | /// It is recommended only for obtaining pointers that can have null value. 9 | #[derive(Debug, Clone, Copy)] 10 | pub struct PtrOrNull<'lib, T: 'lib> { 11 | pointer: *const T, 12 | pd: PhantomData<&'lib T>, 13 | } 14 | 15 | impl<'lib, T> PtrOrNull<'lib, T> { 16 | pub fn new(pointer: *const T) -> PtrOrNull<'lib, T> { 17 | PtrOrNull { 18 | pointer, 19 | pd: PhantomData, 20 | } 21 | } 22 | } 23 | 24 | impl<'lib, T> FromRawResult for PtrOrNull<'lib, T> { 25 | unsafe fn from_raw_result(raw_result: RawResult) -> Result { 26 | match raw_result { 27 | Ok(ptr) => Ok(PtrOrNull { 28 | pointer: *ptr as *const T, 29 | pd: PhantomData, 30 | }), 31 | Err(err) => Err(err), 32 | } 33 | } 34 | } 35 | 36 | impl<'lib, T> Deref for PtrOrNull<'lib, T> { 37 | type Target = *const T; 38 | fn deref(&self) -> &*const T { 39 | &self.pointer 40 | } 41 | } 42 | 43 | unsafe impl<'lib, T: Send> Send for PtrOrNull<'lib, T> {} 44 | unsafe impl<'lib, T: Sync> Sync for PtrOrNull<'lib, T> {} 45 | -------------------------------------------------------------------------------- /dlopen2/src/symbor/ptr_or_null_mut.rs: -------------------------------------------------------------------------------- 1 | use super::super::err::Error; 2 | use super::from_raw::{FromRawResult, RawResult}; 3 | use std::marker::PhantomData; 4 | use std::ops::Deref; 5 | 6 | /// Safe wrapper around mutable pointer. 7 | /// 8 | /// It is recommended only for obtaining pointers that can have null value. 9 | #[derive(Debug, Clone, Copy)] 10 | pub struct PtrOrNullMut<'lib, T: 'lib> { 11 | pointer: *mut T, 12 | pd: PhantomData<&'lib T>, 13 | } 14 | 15 | impl<'lib, T> PtrOrNullMut<'lib, T> { 16 | pub fn new(pointer: *mut T) -> PtrOrNullMut<'lib, T> { 17 | PtrOrNullMut { 18 | pointer, 19 | pd: PhantomData, 20 | } 21 | } 22 | } 23 | 24 | impl<'lib, T> FromRawResult for PtrOrNullMut<'lib, T> { 25 | unsafe fn from_raw_result(raw_result: RawResult) -> Result { 26 | match raw_result { 27 | Ok(ptr) => Ok(PtrOrNullMut { 28 | pointer: *ptr as *mut T, 29 | pd: PhantomData, 30 | }), 31 | Err(err) => Err(err), 32 | } 33 | } 34 | } 35 | 36 | impl<'lib, T> Deref for PtrOrNullMut<'lib, T> { 37 | type Target = *mut T; 38 | fn deref(&self) -> &*mut T { 39 | &self.pointer 40 | } 41 | } 42 | 43 | unsafe impl<'lib, T: Send> Send for PtrOrNullMut<'lib, T> {} 44 | unsafe impl<'lib, T: Sync> Sync for PtrOrNullMut<'lib, T> {} 45 | -------------------------------------------------------------------------------- /dlopen2/src/symbor/reference.rs: -------------------------------------------------------------------------------- 1 | use super::super::err::Error; 2 | use super::from_raw::{FromRawResult, RawResult}; 3 | 4 | use std::ops::Deref; 5 | 6 | /// Safe wrapper around cont reference. 7 | /// 8 | /// This type is intended to be used only inside structures implementing `SymBorApi` trait. 9 | /// In other cases you can as well use normal Rust reference. 10 | #[derive(Debug, Clone, Copy)] 11 | pub struct Ref<'lib, T: 'lib> { 12 | reference: &'lib T, 13 | } 14 | 15 | impl<'lib, T> Ref<'lib, T> { 16 | pub fn new(reference: &'lib T) -> Ref<'lib, T> { 17 | Ref { reference } 18 | } 19 | } 20 | 21 | impl<'lib, T> FromRawResult for Ref<'lib, T> { 22 | unsafe fn from_raw_result(raw_result: RawResult) -> Result { 23 | match raw_result { 24 | Ok(ptr) => { 25 | if ptr.is_null() { 26 | Err(Error::NullSymbol) 27 | } else { 28 | Ok(Ref { 29 | reference: &*(*ptr as *const T), 30 | }) 31 | } 32 | } 33 | Err(err) => Err(err), 34 | } 35 | } 36 | } 37 | 38 | impl<'lib, T> Deref for Ref<'lib, T> { 39 | type Target = T; 40 | fn deref(&self) -> &T { 41 | self.reference 42 | } 43 | } 44 | 45 | unsafe impl<'lib, T: Send> Send for Ref<'lib, T> {} 46 | unsafe impl<'lib, T: Sync> Sync for Ref<'lib, T> {} 47 | -------------------------------------------------------------------------------- /dlopen2/src/symbor/reference_mut.rs: -------------------------------------------------------------------------------- 1 | use super::super::err::Error; 2 | use super::from_raw::{FromRawResult, RawResult}; 3 | 4 | use std::ops::{Deref, DerefMut}; 5 | 6 | /// Safe wrapper around mutable reference. 7 | /// 8 | /// This type is intended to be used only inside structures implementing `SymBorApi` trait. 9 | /// In other cases you can as well use normal Rust reference. 10 | #[derive(Debug)] 11 | pub struct RefMut<'lib, T: 'lib> { 12 | reference: &'lib mut T, 13 | } 14 | 15 | impl<'lib, T> RefMut<'lib, T> { 16 | pub fn new(reference: &'lib mut T) -> RefMut<'lib, T> { 17 | RefMut { reference } 18 | } 19 | } 20 | 21 | impl<'lib, T> FromRawResult for RefMut<'lib, T> { 22 | unsafe fn from_raw_result(raw_result: RawResult) -> Result { 23 | match raw_result { 24 | Ok(ptr) => { 25 | if ptr.is_null() { 26 | Err(Error::NullSymbol) 27 | } else { 28 | Ok(RefMut { 29 | reference: &mut *(*ptr as *mut T), 30 | }) 31 | } 32 | } 33 | Err(err) => Err(err), 34 | } 35 | } 36 | } 37 | 38 | impl<'lib, T> Deref for RefMut<'lib, T> { 39 | type Target = T; 40 | fn deref(&self) -> &T { 41 | self.reference 42 | } 43 | } 44 | 45 | impl<'lib, T> DerefMut for RefMut<'lib, T> { 46 | //type Target = T; 47 | fn deref_mut(&mut self) -> &mut T { 48 | self.reference 49 | } 50 | } 51 | 52 | unsafe impl<'lib, T: Send> Send for RefMut<'lib, T> {} 53 | unsafe impl<'lib, T: Sync> Sync for RefMut<'lib, T> {} 54 | -------------------------------------------------------------------------------- /dlopen2/src/symbor/symbol.rs: -------------------------------------------------------------------------------- 1 | use super::super::err::Error; 2 | use super::from_raw::{FromRawResult, RawResult}; 3 | use std::marker::PhantomData; 4 | use std::mem::transmute_copy; 5 | use std::ops::{Deref, DerefMut}; 6 | 7 | /// Safe wrapper around a symbol obtained from `Library`. 8 | /// 9 | /// This is the most generic type, valid for obtaining functions, references and pointers. 10 | /// It does not accept null value of the library symbol. Other types may provide 11 | /// more specialized functionality better for some use cases. 12 | #[derive(Debug, Clone, Copy)] 13 | pub struct Symbol<'lib, T: 'lib> { 14 | symbol: T, 15 | pd: PhantomData<&'lib T>, 16 | } 17 | 18 | impl<'lib, T> Symbol<'lib, T> { 19 | pub fn new(symbol: T) -> Symbol<'lib, T> { 20 | Symbol { 21 | symbol, 22 | pd: PhantomData, 23 | } 24 | } 25 | } 26 | 27 | impl<'lib, T> FromRawResult for Symbol<'lib, T> { 28 | unsafe fn from_raw_result(raw_result: RawResult) -> Result { 29 | match raw_result { 30 | Ok(ptr) => { 31 | if ptr.is_null() { 32 | Err(Error::NullSymbol) 33 | } else { 34 | let raw: *const () = *ptr; 35 | Ok(Symbol { 36 | symbol: transmute_copy(&raw), 37 | pd: PhantomData, 38 | }) 39 | } 40 | } 41 | Err(err) => Err(err), 42 | } 43 | } 44 | } 45 | 46 | impl<'lib, T> Deref for Symbol<'lib, T> { 47 | type Target = T; 48 | fn deref(&self) -> &T { 49 | &self.symbol 50 | } 51 | } 52 | 53 | impl<'lib, T> DerefMut for Symbol<'lib, T> { 54 | //type Target = T; 55 | fn deref_mut(&mut self) -> &mut T { 56 | &mut self.symbol 57 | } 58 | } 59 | 60 | unsafe impl<'lib, T: Send> Send for Symbol<'lib, T> {} 61 | unsafe impl<'lib, T: Sync> Sync for Symbol<'lib, T> {} 62 | -------------------------------------------------------------------------------- /dlopen2/src/utils.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Utilities for working with dynamic link libraries. 3 | */ 4 | 5 | use std::{ 6 | ffi::{OsStr, OsString}, 7 | mem::MaybeUninit, 8 | }; 9 | 10 | //library naming patterns 11 | /* Naming pattern goes as follows: 12 | Windows *.dll 13 | Apple lib*.dylib 14 | Unix lib*.so 15 | */ 16 | 17 | /// This is a platform-specific file prefix. 18 | /// 19 | /// In Unix-based systems the convention is to start the file name with "lib". 20 | /// Windows does not have such a convention. 21 | #[cfg(unix)] 22 | pub const PLATFORM_FILE_PREFIX: &str = "lib"; 23 | /// This is a platform-specific file prefix. 24 | /// 25 | /// In Unix-based systems the convention is to start the file name with "lib". 26 | /// Windows does not have such a convention. 27 | #[cfg(windows)] 28 | pub const PLATFORM_FILE_PREFIX: &str = ""; 29 | 30 | /// Dynamic link library file extension specific to the platform. 31 | #[cfg(any(target_os = "macos", target_os = "ios"))] 32 | pub const PLATFORM_FILE_EXTENSION: &str = "dylib"; 33 | /// Dynamic link library file extension specific to the platform. 34 | #[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))] 35 | pub const PLATFORM_FILE_EXTENSION: &str = "so"; 36 | /// Dynamic link library file extension specific to the platform. 37 | #[cfg(windows)] 38 | pub const PLATFORM_FILE_EXTENSION: &str = "dll"; 39 | 40 | /// Crates a platform-specific file name from provided core file name. 41 | /// 42 | /// For example on Ubuntu it converts "example" argument into "libexample.so". 43 | pub fn platform_file_name(core_name: S) -> OsString 44 | where 45 | S: AsRef, 46 | { 47 | //here we operate on OStr and OsString and there are no formatting functions for them - sad 48 | let mut result = OsString::new(); 49 | result.reserve_exact( 50 | core_name.as_ref().len() + PLATFORM_FILE_EXTENSION.len() + PLATFORM_FILE_PREFIX.len() + 1, 51 | ); 52 | result.push(PLATFORM_FILE_PREFIX); 53 | result.push(core_name); 54 | result.push("."); 55 | result.push(PLATFORM_FILE_EXTENSION); 56 | result 57 | } 58 | 59 | pub const unsafe fn maybe_uninit_slice_assume_init_ref(slice: &[MaybeUninit]) -> &[T] { 60 | &*(slice as *const [MaybeUninit] as *const [T]) 61 | } 62 | 63 | pub fn maybe_uninit_uninit_array() -> [MaybeUninit; LEN] { 64 | unsafe { MaybeUninit::<[MaybeUninit; LEN]>::uninit().assume_init() } 65 | } 66 | -------------------------------------------------------------------------------- /dlopen2/src/wrapper/api.rs: -------------------------------------------------------------------------------- 1 | use super::super::err::Error; 2 | use super::super::raw::Library; 3 | 4 | /** 5 | Trait for defining library API. 6 | 7 | This trait is intended to be used with `#[derive(WrapperApi)]` macro defined in the 8 | `dlopen2_derive` crate. It forces several restrictions on types that implement it: 9 | 10 | * Only structures can implement this trait. 11 | * All fields need to be private. 12 | * Only functions, references and pointers are allowed. 13 | * You can't define a type using `type Fun =fn();` and use it in the structure. This is a limitation 14 | of the Rust reflection mechanism. Only raw functions, references and pointers are allowed. 15 | * All arguments of functions need to be named. 16 | 17 | 18 | The `derive` macro not only generates implementation of `load()` function, but it also generates 19 | safe wrappers around the loaded symbols. These wrappers are named exactly like the field that 20 | they wrap. Wrappers of functions have the same arguments like original functions and wrappers of 21 | references are just simple accessors in the form of `(&self) -> &FieldType` or 22 | `_mut(&mut self) -> &mut FieldType`. 23 | Wrappers are not generated only for: 24 | 25 | * Pointers - there is no safe way of preventing dangling symbols if a user has a direct access to 26 | pointers. The recommended approach here is to either use references instead of pointers or 27 | to manually create safe wrappers. For example C `const char *` can be manually converted into 28 | `& std::ffi::CStr`. 29 | * Variadic functions. Rust doesn't have any mechanism that allows creating safe wrappers around 30 | them. You need to handle them manually. 31 | 32 | #Example 33 | 34 | ```no_run 35 | use dlopen2::wrapper::{WrapperApi, Container}; 36 | use std::os::raw::{c_char}; 37 | use std::ffi::CStr; 38 | 39 | #[derive(WrapperApi)] 40 | struct Example<'a> { 41 | #[dlopen2_name="function"] 42 | do_something: extern "C" fn(), 43 | add_one: unsafe extern "C" fn (arg: i32) -> i32, 44 | global_count: &'a mut u32, 45 | c_string: * const c_char, 46 | #[dlopen2_allow_null] 47 | maybe_null_ptr: * const (), 48 | } 49 | 50 | //wrapper for c_string won't be generated, implement it here 51 | impl<'a> Example<'a> { 52 | pub fn c_string(&self) -> &CStr { 53 | unsafe {CStr::from_ptr(self.c_string)} 54 | } 55 | } 56 | 57 | fn main () { 58 | let mut cont: Container = unsafe { Container::load("libexample.dylib")}.unwrap(); 59 | cont.do_something(); 60 | let _result = unsafe { cont.add_one(5) }; 61 | *cont.global_count_mut() += 1; 62 | println!("C string: {}", cont.c_string().to_str().unwrap()) 63 | } 64 | ``` 65 | 66 | **Note**: `WrapperApi` should only be used together with `Container` structure, never to create 67 | a standalone object. API and library handle need to be kept together to prevent dangling symbols. 68 | 69 | **Note:** By default obtained symbol name is the field name. You can change this by 70 | assigning the "dlopen2_name" attribute to the given field. 71 | 72 | **Note:** By default `Error::NullSymbol` is returned if the loaded symbol name has a null value. 73 | While null is a valid value of a exported symbol, it is usually not expected by users of libraries. 74 | If in your scenario null is an acceptable value, you should assign 75 | "dlopen2_allow_null" attribute to the given field. Of course this makes sense only if the field 76 | is of pointer type. 77 | */ 78 | pub trait WrapperApi 79 | where 80 | Self: Sized, 81 | { 82 | /// Load symbols from provided library. 83 | unsafe fn load(lib: &Library) -> Result; 84 | } 85 | -------------------------------------------------------------------------------- /dlopen2/src/wrapper/container.rs: -------------------------------------------------------------------------------- 1 | use crate::raw; 2 | 3 | use super::super::raw::Library; 4 | use super::super::Error; 5 | use super::api::WrapperApi; 6 | use std::ffi::OsStr; 7 | use std::ops::{Deref, DerefMut}; 8 | 9 | /** 10 | Container for both a dynamic load library handle and its API. 11 | 12 | Keeping both library and its symbols together makes it safe to use it because symbols are released 13 | together with the library. `Container` also doesn't have any external lifetimes - this makes it 14 | easy to use `Container` inside structures. 15 | 16 | #Example 17 | 18 | ```no_run 19 | use dlopen2::wrapper::{Container, WrapperApi}; 20 | use std::os::raw::{c_char}; 21 | use std::ffi::CStr; 22 | 23 | #[derive(WrapperApi)] 24 | struct Example<'a> { 25 | do_something: extern "C" fn(), 26 | add_one: unsafe extern "C" fn (arg: i32) -> i32, 27 | global_count: &'a mut u32, 28 | c_string: * const c_char 29 | } 30 | 31 | //wrapper for c_string won't be generated, implement it here 32 | impl<'a> Example<'a> { 33 | pub fn c_string(&self) -> &CStr { 34 | unsafe {CStr::from_ptr(self.c_string)} 35 | } 36 | } 37 | 38 | fn main () { 39 | let mut container: Container = unsafe { Container::load("libexample.dylib")}.unwrap(); 40 | container.do_something(); 41 | let _result = unsafe { container.add_one(5) }; 42 | *container.global_count_mut() += 1; 43 | println!("C string: {}", container.c_string().to_str().unwrap()) 44 | } 45 | ``` 46 | */ 47 | pub struct Container 48 | where 49 | T: WrapperApi, 50 | { 51 | #[allow(dead_code)] 52 | //this is not dead code because destructor of Library deallocates the library 53 | lib: Library, 54 | api: T, 55 | } 56 | 57 | impl Container 58 | where 59 | T: WrapperApi, 60 | { 61 | /// Open the library using provided file name or path and load all symbols. 62 | pub unsafe fn load(name: S) -> Result, Error> 63 | where 64 | S: AsRef, 65 | { 66 | let lib = Library::open(name)?; 67 | let api = T::load(&lib)?; 68 | Ok(Self { lib, api }) 69 | } 70 | /// Load all symbols from the program itself. 71 | /// 72 | /// This allows a shared library to load symbols of the program it was 73 | /// loaded into. 74 | pub unsafe fn load_self() -> Result, Error> { 75 | let lib = Library::open_self()?; 76 | let api = T::load(&lib)?; 77 | Ok(Self { lib, api }) 78 | } 79 | 80 | /// Returns the raw OS handle for the opened library. 81 | /// 82 | /// This is `HMODULE` on Windows and `*mut c_void` on Unix systems. Don't use unless absolutely necessary. 83 | pub unsafe fn into_raw(&self) -> raw::Handle { 84 | self.lib.into_raw() 85 | } 86 | 87 | /// Same as load(), except specify flags used by libc::dlopen 88 | pub unsafe fn load_with_flags(name: S, flags: Option) -> Result, Error> 89 | where 90 | S: AsRef, 91 | { 92 | let lib = Library::open_with_flags(name, flags)?; 93 | let api = T::load(&lib)?; 94 | Ok(Self { lib, api }) 95 | } 96 | } 97 | 98 | impl Deref for Container 99 | where 100 | T: WrapperApi, 101 | { 102 | type Target = T; 103 | fn deref(&self) -> &T { 104 | &self.api 105 | } 106 | } 107 | 108 | impl DerefMut for Container 109 | where 110 | T: WrapperApi, 111 | { 112 | fn deref_mut(&mut self) -> &mut T { 113 | &mut self.api 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /dlopen2/src/wrapper/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | High-level and safe API for opening and getting symbols from dynamic link libraries. 3 | It is based on wrapping private symbols with public functions to prevent direct access 4 | and supports automatic loading of symbols into structures. 5 | 6 | This API solves the problem with dangling symbols by wrapping raw symbols with public functions. 7 | User of API does not have direct access to raw symbols and therefore symbols cannot be copied. 8 | Symbols and library handle are kept in one `Container` structure and therefore both the the library 9 | and symbols get released at the same time. 10 | 11 | #Example 12 | 13 | ```no_run 14 | use dlopen2::wrapper::{Container, WrapperApi}; 15 | 16 | #[derive(WrapperApi)] 17 | struct Example<'a> { 18 | do_something: extern "C" fn(), 19 | add_one: unsafe extern "C" fn (arg: i32) -> i32, 20 | global_count: &'a mut u32, 21 | } 22 | 23 | fn main () { 24 | let mut container: Container = unsafe { Container::load("libexample.dylib")}.unwrap(); 25 | container.do_something(); 26 | let _result = unsafe { container.add_one(5) }; 27 | *container.global_count_mut() += 1; 28 | 29 | //symbols are released together with library handle 30 | //this prevents dangling symbols 31 | drop(container); 32 | } 33 | ``` 34 | 35 | Unfortunately in Rust it is not possible to create an API for dynamic link libraries that would 36 | be 100% safe. This API aims to be 99% safe by providing zero cost functional wrappers around 37 | raw symbols. However it is possible to make a mistake if you create API as a standalone object 38 | (not in container): 39 | 40 | #Example of a mistake - dangling symbol 41 | 42 | ```no_run 43 | use dlopen2::wrapper::{Container, WrapperApi}; 44 | use dlopen2::raw::Library; 45 | 46 | #[derive(WrapperApi)] 47 | struct Example<'a> { 48 | do_something: extern "C" fn(), 49 | add_one: unsafe extern "C" fn (arg: i32) -> i32, 50 | global_count: &'a mut u32, 51 | } 52 | 53 | fn main () { 54 | let lib = Library::open("libexample.dylib").unwrap(); 55 | let mut api = unsafe{Example::load(&lib)}; 56 | drop(lib); 57 | 58 | //api has now dangling symbols 59 | 60 | } 61 | ``` 62 | 63 | To prevent this mistake don't use structures implementing `WrapperApi` directly, but rather use 64 | `Container` as in the first example. 65 | 66 | **Note:** This API has a broad support for optional symbols (this solves the issue with multiple 67 | versions of one dynamic link library that have different sets of symbols). Please refer to the 68 | documentation of 69 | [`OptionalContainer`](./struct.OptionalContainer.html) 70 | and 71 | [`WrapperMultiApi`](./trait.WrapperMultiApi.html). 72 | */ 73 | 74 | mod api; 75 | mod container; 76 | mod multi_api; 77 | mod option; 78 | mod optional; 79 | pub use self::api::WrapperApi; 80 | pub use self::container::Container; 81 | pub use self::multi_api::WrapperMultiApi; 82 | pub use self::optional::OptionalContainer; 83 | 84 | #[cfg(feature = "derive")] 85 | pub use dlopen2_derive::{WrapperApi, WrapperMultiApi}; 86 | -------------------------------------------------------------------------------- /dlopen2/src/wrapper/multi_api.rs: -------------------------------------------------------------------------------- 1 | use super::api::WrapperApi; 2 | 3 | /** 4 | Allows creation of complex, optional APIs. 5 | 6 | Real life dynamic link libraries often come in multiple versions. Sometimes additional functions 7 | are added for the specific operating system, sometimes the library gets extended and new versions 8 | export more symbols. Often the API can have multiple versions. This trait helps creating 9 | library APIs with multiple optional parts. 10 | 11 | `WrapperMultiApi` is intended to be used together with the derive macro. You should create a new 12 | structure where all fields implement the `WrapperApi` trait (this includes `Option` where 13 | `T` implements `WrapperApi`). The derive macro will generate required implementation. 14 | 15 | **Note**: `WrapperMultiApi` should only be used together with `Container` structure, never to create 16 | a standalone object. API and library handle need to be kept together to prevent dangling symbols. 17 | 18 | ```no_run 19 | use dlopen2::wrapper::{Container, WrapperApi, WrapperMultiApi}; 20 | 21 | //Define 3 APIs: 22 | 23 | #[derive(WrapperApi)] 24 | struct Obligatory{ 25 | some_fun: unsafe extern "C" fn() 26 | } 27 | 28 | #[derive(WrapperApi)] 29 | struct Optional1<'a>{ 30 | static_val: &'a i32 31 | } 32 | 33 | #[derive(WrapperApi)] 34 | struct Optional2{ 35 | another_fun: unsafe extern "C" fn() 36 | } 37 | 38 | //Now define a multi wrapper that wraps sub APIs into one bigger API. 39 | //This example assumes that the first API is obligatory and the other two are optional. 40 | 41 | #[derive(WrapperMultiApi)] 42 | struct Api<'a>{ 43 | pub obligatory: Obligatory, 44 | pub optional1: Option>, 45 | pub optional2: Option 46 | } 47 | 48 | fn main(){ 49 | let mut container: Container = unsafe { 50 | Container::load("libexample.so") 51 | }.expect("Could not open library or load symbols"); 52 | 53 | //use obligatory API: 54 | unsafe{container.obligatory.some_fun()}; 55 | 56 | //use first optional API: 57 | if let Some(ref opt) = container.optional1{ 58 | let _val = *opt.static_val(); 59 | } 60 | 61 | //use second optional API: 62 | if let Some(ref opt) = container.optional2{ 63 | unsafe {opt.another_fun()}; 64 | } 65 | } 66 | ``` 67 | */ 68 | pub trait WrapperMultiApi: WrapperApi {} 69 | -------------------------------------------------------------------------------- /dlopen2/src/wrapper/option.rs: -------------------------------------------------------------------------------- 1 | use super::super::raw::Library; 2 | use super::super::Error; 3 | use super::api::WrapperApi; 4 | 5 | impl WrapperApi for Option 6 | where 7 | T: WrapperApi, 8 | { 9 | unsafe fn load(lib: &Library) -> Result { 10 | match T::load(lib) { 11 | Ok(val) => Ok(Some(val)), 12 | Err(_) => Ok(None), 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dlopen2/src/wrapper/optional.rs: -------------------------------------------------------------------------------- 1 | use super::super::raw::Library; 2 | use super::super::Error; 3 | use super::api::WrapperApi; 4 | use std::ffi::OsStr; 5 | use std::ops::{Deref, DerefMut}; 6 | 7 | /** 8 | Container for a library handle and both obligatory and optional APIs inside one structure. 9 | 10 | A common problem with dynamic link libraries is that they often have different versions and some 11 | of those versions have broader API than others. This structure allows you to use two APIs at the 12 | same time - one obligatory and one optional. If symbols of the optional API are found in the 13 | library, the optional API gets loaded. Otherwise the `optional()` method will return `None`. 14 | 15 | #Example 16 | 17 | ```no_run 18 | use dlopen2::wrapper::{OptionalContainer, WrapperApi}; 19 | 20 | #[derive(WrapperApi)] 21 | struct Obligatory<'a> { 22 | do_something: extern "C" fn(), 23 | global_count: &'a mut u32, 24 | } 25 | 26 | #[derive(WrapperApi)] 27 | struct Optional{ 28 | add_one: unsafe extern "C" fn (arg: i32) -> i32, 29 | c_string: * const u8 30 | } 31 | 32 | fn main () { 33 | let mut container: OptionalContainer = unsafe { 34 | OptionalContainer::load("libexample.dylib") 35 | }.unwrap(); 36 | container.do_something(); 37 | *container.global_count_mut() += 1; 38 | 39 | match container.optional(){ 40 | &Some(ref opt) => { 41 | let _result = unsafe { opt.add_one(5) }; 42 | println!("First byte of C string is {}", unsafe{*opt.c_string}); 43 | }, 44 | &None => println!("The optional API was not loaded!") 45 | } 46 | } 47 | ``` 48 | 49 | **Note:** For more complex cases (multiple versions of API) you can use 50 | [`WrapperMultiApi`](./trait.WrapperMultiApi.html). 51 | */ 52 | pub struct OptionalContainer 53 | where 54 | Api: WrapperApi, 55 | Optional: WrapperApi, 56 | { 57 | #[allow(dead_code)] 58 | //this is not dead code because destructor of Library deallocates the library 59 | lib: Library, 60 | api: Api, 61 | optional: Option, 62 | } 63 | 64 | impl OptionalContainer 65 | where 66 | Api: WrapperApi, 67 | Optional: WrapperApi, 68 | { 69 | /// Opens the library using provided file name or path and loads all symbols (including optional 70 | /// if it is possible). 71 | pub unsafe fn load(name: S) -> Result, Error> 72 | where 73 | S: AsRef, 74 | { 75 | let lib = Library::open(name)?; 76 | let api = Api::load(&lib)?; 77 | let optional = Optional::load(&lib).ok(); 78 | Ok(Self { lib, api, optional }) 79 | } 80 | 81 | /// Opens the library using provided file name or path and flags, and loads all symbols (including optional 82 | /// if it is possible). 83 | pub unsafe fn load_with_flags( 84 | name: S, 85 | flags: Option, 86 | ) -> Result, Error> 87 | where 88 | S: AsRef, 89 | { 90 | let lib = Library::open_with_flags(name, flags)?; 91 | let api = Api::load(&lib)?; 92 | let optional = Optional::load(&lib).ok(); 93 | Ok(Self { lib, api, optional }) 94 | } 95 | 96 | /// Load all symbols (including optional if it is possible) from the 97 | /// program itself. 98 | /// 99 | /// This allows a shared library to load symbols of the program it was 100 | /// loaded into. 101 | pub unsafe fn load_self() -> Result, Error> { 102 | let lib = Library::open_self()?; 103 | let api = Api::load(&lib)?; 104 | let optional = Optional::load(&lib).ok(); 105 | Ok(Self { lib, api, optional }) 106 | } 107 | 108 | /// Gives access to the optional API - constant version. 109 | pub fn optional(&self) -> &Option { 110 | &self.optional 111 | } 112 | 113 | /// Gives access to the optional API - constant version. 114 | pub fn optional_mut(&mut self) -> &mut Option { 115 | &mut self.optional 116 | } 117 | } 118 | 119 | impl Deref for OptionalContainer 120 | where 121 | Api: WrapperApi, 122 | Optional: WrapperApi, 123 | { 124 | type Target = Api; 125 | fn deref(&self) -> &Api { 126 | &self.api 127 | } 128 | } 129 | 130 | impl DerefMut for OptionalContainer 131 | where 132 | Api: WrapperApi, 133 | Optional: WrapperApi, 134 | { 135 | fn deref_mut(&mut self) -> &mut Api { 136 | &mut self.api 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /dlopen2/tests/commons/mod.rs: -------------------------------------------------------------------------------- 1 | use dlopen2::utils::{PLATFORM_FILE_EXTENSION, PLATFORM_FILE_PREFIX}; 2 | use serde::Deserialize; 3 | use std::fs; 4 | use std::os::raw::c_int; 5 | use std::path::{Path, PathBuf}; 6 | 7 | #[derive(Deserialize)] 8 | struct Manifest { 9 | workspace_root: String, 10 | } 11 | pub fn example_lib_path() -> PathBuf { 12 | // Rust when building dependencies adds some weird numbers to file names 13 | // find the file using this pattern: 14 | let file_pattern = format!( 15 | r"{}example.*\.{}", 16 | PLATFORM_FILE_PREFIX, PLATFORM_FILE_EXTENSION 17 | ); 18 | let file_regex = regex::Regex::new(file_pattern.as_ref()).unwrap(); 19 | 20 | // find the directory with dependencies - there shold be our 21 | // example library 22 | let output = std::process::Command::new(env!("CARGO")) 23 | .arg("metadata") 24 | .arg("--format-version=1") 25 | .output() 26 | .unwrap(); 27 | let manifest: Manifest = serde_json::from_slice(&output.stdout).unwrap(); 28 | let workspace_root = PathBuf::from(manifest.workspace_root); 29 | 30 | let deps_dirs = [ 31 | workspace_root.join("target").join("debug").join("deps"), 32 | workspace_root 33 | .join("target") 34 | .join(current_platform::CURRENT_PLATFORM) 35 | .join("debug") 36 | .join("deps"), 37 | ]; 38 | 39 | // unfortunately rust has no strict pattern of naming dependencies in this directory 40 | // this is partially platform dependent as there was a bug reported that while the code runs 41 | // well on Linux, Windows, it stopped working on a new version of Mac. 42 | // The only way to handle this correctly is by traversing the directory recursively and 43 | // finding a match. 44 | 45 | let mut lib_path = None; 46 | for deps_dir in deps_dirs { 47 | let new_path = match recursive_find(deps_dir.as_path(), &file_regex) { 48 | None => continue, 49 | Some(p) => p, 50 | }; 51 | 52 | match &lib_path { 53 | None => lib_path = Some(new_path), 54 | Some(old_path) => { 55 | let new_meta = std::fs::metadata(&new_path).unwrap(); 56 | let old_meta = std::fs::metadata(old_path).unwrap(); 57 | if new_meta.modified().unwrap() > old_meta.modified().unwrap() { 58 | lib_path = Some(new_path); 59 | } 60 | } 61 | } 62 | } 63 | 64 | let lib_path = lib_path.expect("Could not find the example library"); 65 | println!("Library path: {}", lib_path.to_str().unwrap()); 66 | lib_path 67 | } 68 | 69 | fn recursive_find(path: &Path, file_regex: ®ex::Regex) -> Option { 70 | if path.is_dir() { 71 | match fs::read_dir(path) { 72 | Err(_) => None, 73 | Ok(dir) => { 74 | for entry in dir.filter_map(Result::ok) { 75 | if let Some(p) = recursive_find(&entry.path(), file_regex) { 76 | return Some(p); 77 | } 78 | } 79 | None 80 | } 81 | } 82 | } else if file_regex.is_match(path.file_name().unwrap().to_str().unwrap()) { 83 | Some(path.to_path_buf()) 84 | } else { 85 | None 86 | } 87 | } 88 | 89 | #[repr(C)] 90 | pub struct SomeData { 91 | pub first: c_int, 92 | pub second: c_int, 93 | } 94 | -------------------------------------------------------------------------------- /dlopen2/tests/raw.rs: -------------------------------------------------------------------------------- 1 | use dlopen2::raw::{AddressInfoObtainer, Library}; 2 | use std::ffi::CStr; 3 | use std::os::raw::{c_char, c_int}; 4 | 5 | mod commons; 6 | use commons::{example_lib_path, SomeData}; 7 | 8 | #[test] 9 | fn open_play_close_raw() { 10 | let lib_path = example_lib_path(); 11 | let lib = Library::open(lib_path).expect("Could not open library"); 12 | let rust_fun_print_something: fn() = 13 | unsafe { lib.symbol_cstr(c"rust_fun_print_something") }.unwrap(); 14 | rust_fun_print_something(); //should not crash 15 | let rust_fun_add_one: fn(i32) -> i32 = unsafe { lib.symbol_cstr(c"rust_fun_add_one") }.unwrap(); 16 | assert_eq!(rust_fun_add_one(5), 6); 17 | let c_fun_print_something_else: unsafe extern "C" fn() = 18 | unsafe { lib.symbol_cstr(c"c_fun_print_something_else") }.unwrap(); 19 | unsafe { c_fun_print_something_else() }; //should not crash 20 | let c_fun_add_two: unsafe extern "C" fn(c_int) -> c_int = 21 | unsafe { lib.symbol_cstr(c"c_fun_add_two") }.unwrap(); 22 | assert_eq!(unsafe { c_fun_add_two(2) }, 4); 23 | let rust_i32: &i32 = unsafe { lib.symbol_cstr(c"rust_i32") }.unwrap(); 24 | assert_eq!(43, *rust_i32); 25 | let rust_i32_mut: &mut i32 = unsafe { lib.symbol_cstr(c"rust_i32_mut") }.unwrap(); 26 | assert_eq!(42, *rust_i32_mut); 27 | *rust_i32_mut = 55; //should not crash 28 | //for a change use pointer to obtain its value 29 | let rust_i32_ptr: *const i32 = unsafe { lib.symbol_cstr(c"rust_i32_mut") }.unwrap(); 30 | assert_eq!(55, unsafe { *rust_i32_ptr }); 31 | //the same with C 32 | let c_int: &c_int = unsafe { lib.symbol_cstr(c"c_int") }.unwrap(); 33 | assert_eq!(45, *c_int); 34 | //now static c struct 35 | 36 | let c_struct: &SomeData = unsafe { lib.symbol_cstr(c"c_struct") }.unwrap(); 37 | assert_eq!(1, c_struct.first); 38 | assert_eq!(2, c_struct.second); 39 | //let's play with strings 40 | 41 | let rust_str: &&str = unsafe { lib.symbol_cstr(c"rust_str") }.unwrap(); 42 | assert_eq!("Hello!", *rust_str); 43 | let c_const_char_ptr: *const c_char = unsafe { lib.symbol_cstr(c"c_const_char_ptr") }.unwrap(); 44 | let converted = unsafe { CStr::from_ptr(c_const_char_ptr) } 45 | .to_str() 46 | .unwrap(); 47 | assert_eq!(converted, "Hi!"); 48 | 49 | ::std::mem::forget(lib); 50 | } 51 | 52 | #[test] 53 | fn example_address_info() { 54 | let lib_path = example_lib_path(); 55 | let lib = Library::open(&lib_path).expect("Could not open library"); 56 | let c_fun_add_two: unsafe extern "C" fn(c_int) -> c_int = 57 | unsafe { lib.symbol("c_fun_add_two") }.unwrap(); 58 | let aio = AddressInfoObtainer::new(); 59 | let ai = unsafe { aio.obtain(c_fun_add_two as *const ()) }.unwrap(); 60 | assert_eq!(&ai.dll_path, lib_path.to_str().unwrap()); 61 | let os = ai.overlapping_symbol.unwrap(); 62 | assert_eq!(os.name, "c_fun_add_two"); 63 | assert_eq!(os.addr, c_fun_add_two as *const ()) 64 | } 65 | 66 | // https://github.com/OpenByteDev/dlopen2/issues/3 67 | #[test] 68 | #[cfg(windows)] 69 | fn double_sym_init_does_not_panic() { 70 | let lib_path = example_lib_path(); 71 | let library = Library::open(&lib_path).expect("Could not open library"); 72 | let pointer: *const () = unsafe { library.symbol("c_fun_add_two") }.unwrap(); 73 | 74 | let _ = std::panic::catch_unwind(|| panic!()); // this generates a backtrace. Backtrace::capture() probably works too 75 | 76 | // Panics because SymInitializeW returns an error 77 | unsafe { AddressInfoObtainer::new().obtain(pointer) }.unwrap(); 78 | } 79 | -------------------------------------------------------------------------------- /dlopen2/tests/symbor.rs: -------------------------------------------------------------------------------- 1 | use dlopen2::symbor::Library; 2 | use std::ffi::CStr; 3 | use std::os::raw::{c_char, c_int}; 4 | 5 | mod commons; 6 | use commons::{example_lib_path, SomeData}; 7 | 8 | #[test] 9 | fn open_play_close_symbor() { 10 | let lib_path = example_lib_path(); 11 | let mut lib = Library::open(lib_path).expect("Could not open library"); 12 | let rust_fun_print_something = 13 | unsafe { lib.symbol_cstr::(c"rust_fun_print_something") }.unwrap(); 14 | rust_fun_print_something(); //should not crash 15 | let rust_fun_add_one = 16 | unsafe { lib.symbol_cstr:: i32>(c"rust_fun_add_one") }.unwrap(); 17 | assert_eq!(rust_fun_add_one(5), 6); 18 | 19 | let c_fun_print_something_else = 20 | unsafe { lib.symbol_cstr::(c"c_fun_print_something_else") } 21 | .unwrap(); 22 | unsafe { c_fun_print_something_else() }; //should not crash 23 | let c_fun_add_two = 24 | unsafe { lib.symbol_cstr:: c_int>(c"c_fun_add_two") } 25 | .unwrap(); 26 | assert_eq!(unsafe { c_fun_add_two(2) }, 4); 27 | let rust_i32: &i32 = unsafe { lib.reference_cstr(c"rust_i32") }.unwrap(); 28 | assert_eq!(43, *rust_i32); 29 | let rust_i32_mut: &mut i32 = unsafe { lib.reference_mut_cstr(c"rust_i32_mut") }.unwrap(); 30 | assert_eq!(42, *rust_i32_mut); 31 | *rust_i32_mut = 55; //should not crash 32 | //for a change use pointer to obtain its value 33 | let rust_i32_ptr = unsafe { lib.symbol_cstr::<*const i32>(c"rust_i32_mut") }.unwrap(); 34 | assert_eq!(55, unsafe { **rust_i32_ptr }); 35 | //the same with C 36 | let c_int: &c_int = unsafe { lib.reference_cstr(c"c_int") }.unwrap(); 37 | assert_eq!(45, *c_int); 38 | //now static c struct 39 | 40 | let c_struct: &SomeData = unsafe { lib.reference_cstr(c"c_struct") }.unwrap(); 41 | assert_eq!(1, c_struct.first); 42 | assert_eq!(2, c_struct.second); 43 | //let's play with strings 44 | 45 | let rust_str: &&str = unsafe { lib.reference_cstr(c"rust_str") }.unwrap(); 46 | assert_eq!("Hello!", *rust_str); 47 | let c_const_char_ptr = 48 | unsafe { lib.symbol_cstr::<*const c_char>(c"c_const_char_ptr") }.unwrap(); 49 | let converted = unsafe { CStr::from_ptr(*c_const_char_ptr) } 50 | .to_str() 51 | .unwrap(); 52 | assert_eq!(converted, "Hi!"); 53 | } 54 | -------------------------------------------------------------------------------- /dlopen2/tests/symbor_api.rs: -------------------------------------------------------------------------------- 1 | use dlopen2::symbor::{Library, PtrOrNull, Ref, RefMut, SymBorApi, Symbol}; 2 | use std::ffi::CStr; 3 | use std::os::raw::{c_char, c_int}; 4 | 5 | mod commons; 6 | use commons::{example_lib_path, SomeData}; 7 | 8 | #[derive(SymBorApi)] 9 | struct Api<'a> { 10 | pub rust_fun_print_something: Symbol<'a, fn()>, 11 | pub rust_fun_add_one: Symbol<'a, fn(i32) -> i32>, 12 | pub c_fun_print_something_else: Symbol<'a, unsafe extern "C" fn()>, 13 | pub c_fun_add_two: Symbol<'a, unsafe extern "C" fn(c_int) -> c_int>, 14 | pub rust_i32: Ref<'a, i32>, 15 | pub rust_i32_mut: RefMut<'a, i32>, 16 | #[dlopen2_name = "rust_i32_mut"] 17 | pub rust_i32_ptr: Symbol<'a, *const i32>, 18 | pub c_int: Ref<'a, c_int>, 19 | pub c_struct: Ref<'a, SomeData>, 20 | pub rust_str: Ref<'a, &'static str>, 21 | pub c_const_char_ptr: PtrOrNull<'a, c_char>, 22 | } 23 | 24 | #[test] 25 | fn open_play_close_symbor_api() { 26 | let lib_path = example_lib_path(); 27 | let lib = Library::open(lib_path).expect("Could not open library"); 28 | let mut api = unsafe { Api::load(&lib) }.expect("Could not load symbols"); 29 | (api.rust_fun_print_something)(); //should not crash 30 | assert_eq!((api.rust_fun_add_one)(5), 6); 31 | unsafe { (api.c_fun_print_something_else)() }; //should not crash 32 | assert_eq!(unsafe { (api.c_fun_add_two)(2) }, 4); 33 | assert_eq!(43, *api.rust_i32); 34 | assert_eq!(42, *api.rust_i32_mut); 35 | *api.rust_i32_mut = 55; //should not crash 36 | assert_eq!(55, unsafe { **api.rust_i32_ptr }); 37 | //the same with C 38 | assert_eq!(45, *api.c_int); 39 | //now static c struct 40 | 41 | assert_eq!(1, api.c_struct.first); 42 | assert_eq!(2, api.c_struct.second); 43 | //let's play with strings 44 | 45 | assert_eq!("Hello!", *api.rust_str); 46 | let converted = unsafe { CStr::from_ptr(*api.c_const_char_ptr) } 47 | .to_str() 48 | .unwrap(); 49 | assert_eq!(converted, "Hi!"); 50 | } 51 | -------------------------------------------------------------------------------- /dlopen2/tests/wrapper_api.rs: -------------------------------------------------------------------------------- 1 | use dlopen2::wrapper::{Container, WrapperApi}; 2 | use std::ffi::CStr; 3 | use std::os::raw::{c_char, c_int}; 4 | 5 | mod commons; 6 | use commons::{example_lib_path, SomeData}; 7 | 8 | #[derive(WrapperApi)] 9 | struct Api<'a> { 10 | rust_fun_print_something: fn(), 11 | rust_fun_add_one: fn(arg: i32) -> i32, 12 | c_fun_print_something_else: unsafe extern "C" fn(), 13 | #[dlopen2_name = "c_fun_print_something_else"] 14 | c_fun_print_something_else_optional: Option, 15 | c_fun_add_two: Option c_int>, 16 | c_fun_add_two_not_found: Option, 17 | rust_i32: &'a i32, 18 | rust_i32_mut: &'a mut i32, 19 | #[dlopen2_name = "rust_i32_mut"] 20 | rust_i32_ptr: *const i32, 21 | #[dlopen2_name = "rust_i32"] 22 | rust_i32_optional: Option<&'a i32>, 23 | rust_i32_not_found: Option<&'a i32>, 24 | c_int: &'a c_int, 25 | c_struct: &'a SomeData, 26 | rust_str: &'a &'static str, 27 | c_const_char_ptr: *const c_char, 28 | } 29 | 30 | //those methods won't be generated 31 | impl<'a> Api<'a> { 32 | fn rust_i32_ptr(&self) -> *const i32 { 33 | self.rust_i32_ptr 34 | } 35 | 36 | fn c_const_str(&self) -> &CStr { 37 | unsafe { CStr::from_ptr(self.c_const_char_ptr) } 38 | } 39 | } 40 | 41 | #[test] 42 | fn open_play_close_wrapper_api() { 43 | let lib_path = example_lib_path(); 44 | let mut cont: Container = 45 | unsafe { Container::load(lib_path) }.expect("Could not open library or load symbols"); 46 | 47 | cont.rust_fun_print_something(); // should not crash 48 | assert_eq!(cont.rust_fun_add_one(5), 6); 49 | unsafe { cont.c_fun_print_something_else() }; // should not crash 50 | unsafe { cont.c_fun_print_something_else_optional() }; 51 | assert!(cont.has_c_fun_print_something_else_optional()); 52 | assert_eq!(unsafe { cont.c_fun_add_two(2) }, Some(4)); 53 | assert!(!cont.has_c_fun_add_two_not_found()); 54 | assert_eq!(unsafe { cont.c_fun_add_two_not_found(2) }, None); 55 | assert_eq!(43, *cont.rust_i32()); 56 | assert_eq!(42, *cont.rust_i32_mut_mut()); 57 | *cont.rust_i32_mut_mut() = 55; // should not crash 58 | assert_eq!(55, unsafe { *cont.rust_i32_ptr() }); 59 | assert_eq!(cont.rust_i32_optional(), Some(&43)); 60 | assert_eq!(cont.rust_i32_not_found(), None); 61 | 62 | // the same with C 63 | assert_eq!(45, *cont.c_int()); 64 | 65 | // now static c struct 66 | assert_eq!(1, cont.c_struct().first); 67 | assert_eq!(2, cont.c_struct().second); 68 | 69 | // let's play with strings 70 | assert_eq!("Hello!", *cont.rust_str()); 71 | let converted = cont.c_const_str().to_str().unwrap(); 72 | assert_eq!(converted, "Hi!"); 73 | } 74 | -------------------------------------------------------------------------------- /example-dylib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example_dylib" 3 | version = "0.2.0" 4 | authors = ["Szymon Wieloch ", 5 | "OpenByte "] 6 | description = "Example dynamic link library for executing tests of libraries that load and operate on dynamic link libraries" 7 | license = "MIT" 8 | edition = "2021" 9 | 10 | [lib] 11 | name = "example" 12 | crate-type = ["cdylib"] 13 | -------------------------------------------------------------------------------- /example-dylib/src/lib.rs: -------------------------------------------------------------------------------- 1 | //!An example dynamically loadable library. 2 | //! 3 | //! This crate creates a dynamic library that can be used for testing purposes. 4 | //! It exports multiple symbols with different types and abis. 5 | //! It's main purpose is to be used in tests of dynlib crate. 6 | 7 | use std::os::raw::{c_char, c_int}; 8 | 9 | //FUNCTIONS 10 | #[no_mangle] 11 | pub fn rust_fun_print_something() { 12 | println!("something"); 13 | } 14 | 15 | #[no_mangle] 16 | pub fn rust_fun_add_one(arg: i32) -> i32 { 17 | arg + 1 18 | } 19 | 20 | #[no_mangle] 21 | pub extern "C" fn c_fun_print_something_else() { 22 | println!("something else"); 23 | } 24 | 25 | #[no_mangle] 26 | pub extern "C" fn c_fun_add_two(arg: c_int) -> c_int { 27 | arg + 2 28 | } 29 | 30 | #[allow(unused_variables)] 31 | #[no_mangle] 32 | pub extern "C" fn c_fun_variadic(txt: *const c_char) { 33 | //pretend to be variadic - impossible to do in Rust code 34 | } 35 | 36 | //STATIC DATA 37 | #[no_mangle] 38 | pub static mut rust_i32_mut: i32 = 42; 39 | #[no_mangle] 40 | pub static rust_i32: i32 = 43; 41 | 42 | #[no_mangle] 43 | pub static mut c_int_mut: c_int = 44; 44 | #[no_mangle] 45 | pub static c_int: c_int = 45; 46 | 47 | #[repr(C)] 48 | pub struct SomeData { 49 | first: c_int, 50 | second: c_int, 51 | } 52 | 53 | #[no_mangle] 54 | pub static c_struct: SomeData = SomeData { 55 | first: 1, 56 | second: 2, 57 | }; 58 | 59 | //STATIC STRINGS 60 | 61 | //exporting str directly is not so easy - it is not Sized! 62 | //you can only export a reference to str and this requires double dereference 63 | #[no_mangle] 64 | pub static rust_str: &str = "Hello!"; 65 | 66 | #[no_mangle] 67 | pub static c_const_char_ptr: [u8; 4] = [b'H', b'i', b'!', 0]; 68 | --------------------------------------------------------------------------------