From 657cd9f583dbaf5513075a6a0c0a46ebe09cefcd Mon Sep 17 00:00:00 2001 From: Martin Olivier <58467105+martin-olivier@users.noreply.github.com> Date: Tue, 14 Jun 2022 21:38:10 +0200 Subject: [PATCH] Version 2.0.0 (#48) dylib class is now RAII compliant breaking changes for dylib ctor os prefix support (lib under unix) handle_error exception is now called load_error return type of get_function is now a function pointer instead of an std::function Signed-off-by: Martin Olivier <58467105+martin-olivier@users.noreply.github.com> Co-authored-by: Eyal Rozenberg --- .codecov.yml | 2 - .github/workflows/CI.yml | 28 ++-- .gitignore | 15 +- CMakeLists.txt | 10 +- README.md | 155 +++++++++----------- example/CMakeLists.txt | 8 +- example/README.md | 56 ++++---- example/lib.cpp | 35 +++-- example/main.cpp | 25 ++-- include/dylib.hpp | 299 ++++++++++++++++++--------------------- tests/lib.cpp | 21 ++- tests/tests.cpp | 219 ++++++++-------------------- 12 files changed, 373 insertions(+), 500 deletions(-) delete mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index b52d0c4..0000000 --- a/.codecov.yml +++ /dev/null @@ -1,2 +0,0 @@ -ignore: - - "test" diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d5e222d..7329224 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -19,19 +19,25 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Generate Project Files + - name: Generate project files run: cmake . -B build -DBUILD_TESTS=ON - - name: Build Dynamic Library and Unit Tests + - name: Build dynamic library and unit tests run: cmake --build build - - name: Run Unit Tests + - name: Run unit tests working-directory: build run: ctest - - name: Send Coverage to codecov.io + - name: Generate code coverage if: ${{ matrix.os == 'ubuntu-latest' }} - run: bash <(curl -s https://codecov.io/bash) + run: gcov -abcfu build/CMakeFiles/unit_tests.dir/tests/tests.cpp.gcda + + - name: Send coverage to codecov.io + if: ${{ matrix.os == 'ubuntu-latest' }} + uses: codecov/codecov-action@v2 + with: + files: dylib.hpp.gcov memory_check: name: memory check @@ -40,19 +46,19 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Update Packages + - name: Update packages run: sudo apt update - - name: Install Valgrind + - name: Install valgrind run: sudo apt install -y valgrind - - name: Generate Tests Build File + - name: Generate tests build file run: cmake . -B build -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug - - name: Build Unit Tests + - name: Build unit tests run: cmake --build build - - name: Run Unit Tests with Valgrind + - name: Run unit tests with valgrind working-directory: build run: valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./unit_tests @@ -67,4 +73,4 @@ jobs: run: pip install cpplint - name: Run cpplint - run: cpplint --linelength=120 --filter=-whitespace/indent include/dylib.hpp \ No newline at end of file + run: cpplint --linelength=140 --filter=-whitespace/indent,-whitespace/parens include/dylib.hpp \ No newline at end of file diff --git a/.gitignore b/.gitignore index ba663d8..f56f781 100644 --- a/.gitignore +++ b/.gitignore @@ -6,16 +6,13 @@ *~ \#*# -# Binary files - -*.o -*.so -*.dll -*.dylib -unit_tests - # Build Folders cmake-build-* build -Debug \ No newline at end of file + +# Coverage files + +*.gcda +*.gcno +*.gcov \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cb9c01..7968a7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,17 +34,17 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) endif() add_library(dynamic_lib SHARED tests/lib.cpp) - set_target_properties(dynamic_lib PROPERTIES PREFIX "") - target_link_libraries(dynamic_lib PRIVATE dylib) enable_testing() + if(UNIX AND NOT APPLE) + add_compile_options(-fprofile-arcs -ftest-coverage) + endif() + add_executable(unit_tests tests/tests.cpp) - target_link_libraries(unit_tests PRIVATE gtest_main) - target_link_libraries(unit_tests PRIVATE dylib) + target_link_libraries(unit_tests PRIVATE gtest_main dylib) if(UNIX AND NOT APPLE) - add_compile_options(--coverage) target_link_libraries(unit_tests PRIVATE gcov) endif() diff --git a/README.md b/README.md index dba3d31..56fa9f4 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,29 @@ -# Dylib - C++ cross-platform dynamic library loader -[![Dylib](https://img.shields.io/badge/Dylib-v1.8.3-blue.svg)](https://github.com/martin-olivier/dylib/releases/tag/v1.8.3) -[![MIT license](https://img.shields.io/badge/License-MIT-orange.svg)](https://github.com/martin-olivier/dylib/blob/main/LICENSE) -[![CPP Version](https://img.shields.io/badge/C++-11_and_above-darkgreen.svg)](https://isocpp.org/) - -[![GitHub watchers](https://img.shields.io/github/watchers/martin-olivier/dylib?style=social)](https://github.com/martin-olivier/dylib/watchers/) -[![GitHub forks](https://img.shields.io/github/forks/martin-olivier/dylib?style=social)](https://github.com/martin-olivier/dylib/network/members/) -[![GitHub stars](https://img.shields.io/github/stars/martin-olivier/dylib?style=social)](https://github.com/martin-olivier/dylib/stargazers/) - -[![workflow](https://github.com/martin-olivier/dylib/actions/workflows/CI.yml/badge.svg)](https://github.com/martin-olivier/dylib/actions/workflows/CI.yml) -[![codecov](https://codecov.io/gh/martin-olivier/dylib/branch/main/graph/badge.svg?token=4V6A9B7PII)](https://codecov.io/gh/martin-olivier/dylib) - -[![GitHub download](https://img.shields.io/github/downloads/martin-olivier/dylib/total?style=for-the-badge)](https://github.com/martin-olivier/dylib/releases/download/v1.8.3/dylib.hpp) +

+ dylib +

+ +

+ + version + + + license + + + cppversion + +

+ +

+ + ci + + + codecov + +

+ +# Dylib The goal of this C++ library is to load dynamic libraries (.so, .dll, .dylib) and access its functions and global variables at runtime. @@ -28,51 +41,48 @@ include(FetchContent) FetchContent_Declare( dylib GIT_REPOSITORY "https://github.com/martin-olivier/dylib" - GIT_TAG "v1.8.3" + GIT_TAG "v2.0.0" ) FetchContent_MakeAvailable(dylib) ``` -You can also click [HERE](https://github.com/martin-olivier/dylib/releases/download/v1.8.3/dylib.hpp) to download the `dylib` header file. +You can also click [HERE](https://github.com/martin-olivier/dylib/releases/download/v2.0.0/dylib.hpp) to download the `dylib` header file. # Documentation -## Dylib class +## Constructor -The `dylib` class can load a dynamic library at runtime: +The `dylib` class can load a dynamic library from the system library path ```c++ -dylib lib("./dynamic_lib.so"); -``` -The `dylib` class can detect the file extension of the actual os using `dylib::extension`: -```c++ -dylib lib("./dynamic_lib", dylib::extension); +// Load "foo" library from the system library path + +dylib lib("foo"); ``` -or +The `dylib` class can also load a dynamic library from a specific path ```c++ -dylib lib; -lib.open("./dynamic_lib", dylib::extension); -``` - -## Open and close +// Load "foo" lib from relative path "./libs" -`open` -Load a dynamic library into the object. If a dynamic library was already opened, it will be unloaded and replaced +dylib lib("./libs", "foo"); -`close` -Unload the dynamic library currently loaded in the object. This function will be automatically called by the class destructor -```c++ -// Load ./dynamic_lib.so +// Load "foo" lib from full path "/usr/lib" -dylib lib("./dynamic_lib.so"); +dylib lib("/usr/lib", "foo"); +``` -// Unload ./dynamic_lib.so and load ./other_dynamic_lib.so +The `dylib` class will automatically add os decorations to the library name, but you can disable that by setting `decorations` parameter to false +```c++ +// Windows -> "foo.dll" +// MacOS: -> "libfoo.dylib" +// Linux: -> "libfoo.so" -lib.open("./other_dynamic_lib.so"); +dylib lib("foo"); -// Unload ./other_dynamic_lib.so +// Windows -> "foo.lib" +// MacOS: -> "foo.lib" +// Linux: -> "foo.lib" -lib.close(); +dylib lib("foo.lib", false); ``` ## Get a function or a variable @@ -83,28 +93,25 @@ Get a function from the dynamic library currently loaded in the object `get_variable` Get a global variable from the dynamic library currently loaded in the object ```c++ -// Load ./dynamic_lib.so +// Load "foo" dynamic library -dylib lib("./dynamic_lib.so"); +dylib lib("foo"); -// Get the function adder (get_function will return std::function) +// Get the function "adder" (get_function will return T*) auto adder = lib.get_function("adder"); -// Get the global variable pi_value +// Get the variable "pi_value" (get_variable will return T&) -const double &pi = lib.get_variable("pi_value"); +double pi = lib.get_variable("pi_value"); -// Use the function adder with pi_value +// Use the function "adder" with "pi_value" double result = adder(pi, pi); ``` ## Miscellaneous tools -`operator bool` -Returns true if a dynamic library is currently loaded in the object, false otherwise - `has_symbol` Returns true if the symbol passed as parameter exists in the dynamic library, false otherwise @@ -113,72 +120,50 @@ Returns the dynamic library handle ```c++ void example(dylib &lib) { - if (lib) - std::cout << "Something is currently loaded in the dylib object" << std::endl; - if (lib.has_symbol("GetModule")) std::cout << "GetModule symbol has been found" << std::endl; dylib::native_handle_type handle = lib.native_handle(); + void *sym = dlsym(handle, "GetModule"); } ``` -## Dylib exceptions +## Exceptions -`handle_error` +`load_error` This exception is raised when the library failed to load or the library encountered symbol resolution issues `symbol_error` -This exception is raised when the library failed to load a symbol. -This usually happens when you forgot to put `DYLIB_API` before a library function or variable +This exception is raised when the library failed to load a symbol Those exceptions inherit from `dylib::exception` ```c++ try { - dylib lib("./dynamic_lib.so"); - const double &pi_value = lib.get_variable("pi_value"); + dylib lib("foo"); + double pi_value = lib.get_variable("pi_value"); std::cout << pi_value << std::endl; } -catch (const dylib::exception &e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; +catch (const dylib::load_error &e) { + // failed loading "foo" lib +} +catch (const dylib::symbol_error &e) { + // failed loading "pi_value" symbol } -return EXIT_SUCCESS; ``` # Example A full example about the usage of the `dylib` library is available [HERE](example) -# Tips +# Tests -## Remove the lib prefix - -> If you use CMake to build a dynamic library, running the below CMake rule will allow you to remove the prefix `lib` for macOS and linux, ensuring that the library shares the same name on all the different OS: - -```cmake -set_target_properties(target PROPERTIES PREFIX "") -``` - -| | Without CMake rule | With CMake rule | -|:-------:|:----------------------|:----------------| -| Linux | ***lib***malloc.so | malloc.so | -| MacOS | ***lib***malloc.dylib | malloc.dylib | -| Windows | malloc.dll | malloc.dll | - -## Build and run unit tests - -To build the unit tests, enter the following commands: +To build unit tests, enter the following commands: ```sh cmake . -B build -DBUILD_TESTS=ON cmake --build build ``` -To run the unit tests, enter the following command: +To run unit tests, enter the following command inside "build" directory: ```sh -# on unix -./unit_tests - -# on windows, in "Debug" folder -./unit_tests.exe +ctest ``` diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index cfec843..409c607 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -5,8 +5,8 @@ project(dylib_example) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) # dylib fetch @@ -15,7 +15,7 @@ include(FetchContent) FetchContent_Declare( dylib GIT_REPOSITORY "https://github.com/martin-olivier/dylib" - GIT_TAG "v1.8.3" + GIT_TAG "v2.0.0" ) FetchContent_MakeAvailable(dylib) @@ -23,8 +23,6 @@ FetchContent_MakeAvailable(dylib) # build lib.cpp into a shared library add_library(dynamic_lib SHARED lib.cpp) -set_target_properties(dynamic_lib PROPERTIES PREFIX "") -target_link_libraries(dynamic_lib PRIVATE dylib) # build main.cpp into an executable diff --git a/example/README.md b/example/README.md index e826aa9..fea2997 100644 --- a/example/README.md +++ b/example/README.md @@ -7,18 +7,27 @@ The functions and variables of our forthcoming dynamic library are located insid // lib.cpp #include -#include "dylib.hpp" -DYLIB_API double pi_value = 3.14159; -DYLIB_API void *ptr = (void *)1; +#if defined(_WIN32) || defined(_WIN64) +#define LIB_EXPORT __declspec(dllexport) +#else +#define LIB_EXPORT +#endif + +extern "C" { + +LIB_EXPORT double pi_value = 3.14159; +LIB_EXPORT void *ptr = (void *)1; -DYLIB_API double adder(double a, double b) { +LIB_EXPORT double adder(double a, double b) { return a + b; } -DYLIB_API void print_hello() { +LIB_EXPORT void print_hello() { std::cout << "Hello" << std::endl; } + +} // extern "C" ``` The code that will load functions and global variables of our dynamic library at runtime is located inside [main.cpp](main.cpp) @@ -29,25 +38,20 @@ The code that will load functions and global variables of our dynamic library at #include "dylib.hpp" int main() { - try { - dylib lib("./dynamic_lib", dylib::extension); + dylib lib("./", "dynamic_lib"); + + auto adder = lib.get_function("adder"); + std::cout << adder(5, 10) << std::endl; - auto adder = lib.get_function("adder"); - std::cout << adder(5, 10) << std::endl; + auto printer = lib.get_function("print_hello"); + printer(); - auto printer = lib.get_function("print_hello"); - printer(); + double pi_value = lib.get_variable("pi_value"); + std::cout << pi_value << std::endl; - double pi_value = lib.get_variable("pi_value"); - std::cout << pi_value << std::endl; + void *ptr = lib.get_variable("ptr"); + if (ptr == (void *)1) std::cout << 1 << std::endl; - void *ptr = lib.get_variable("ptr"); - if (ptr == (void *)1) std::cout << 1 << std::endl; - } - catch (const dylib::exception &e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } return EXIT_SUCCESS; } ``` @@ -68,8 +72,8 @@ project(dylib_example) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) # dylib fetch @@ -78,7 +82,7 @@ include(FetchContent) FetchContent_Declare( dylib GIT_REPOSITORY "https://github.com/martin-olivier/dylib" - GIT_TAG "v1.8.3" + GIT_TAG "v2.0.0" ) FetchContent_MakeAvailable(dylib) @@ -86,8 +90,6 @@ FetchContent_MakeAvailable(dylib) # build lib.cpp into a shared library add_library(dynamic_lib SHARED lib.cpp) -set_target_properties(dynamic_lib PROPERTIES PREFIX "") -target_link_libraries(dynamic_lib PRIVATE dylib) # build main.cpp into an executable @@ -104,10 +106,10 @@ cmake --build build Let's run our code: ```sh -# on unix +# on unix, run the following command inside "build" folder ./dylib_example -# on windows, in "Debug" folder +# on windows, run the following command inside "build/Debug" folder ./dylib_example.exe ``` diff --git a/example/lib.cpp b/example/lib.cpp index ec303b6..25786a7 100644 --- a/example/lib.cpp +++ b/example/lib.cpp @@ -1,13 +1,22 @@ -#include -#include "dylib.hpp" - -DYLIB_API double pi_value = 3.14159; -DYLIB_API void *ptr = (void *)1; - -DYLIB_API double adder(double a, double b) { - return a + b; -} - -DYLIB_API void print_hello() { - std::cout << "Hello" << std::endl; -} \ No newline at end of file +#include + +#if defined(_WIN32) || defined(_WIN64) +#define LIB_EXPORT __declspec(dllexport) +#else +#define LIB_EXPORT +#endif + +extern "C" { + +LIB_EXPORT double pi_value = 3.14159; +LIB_EXPORT void *ptr = (void *)1; + +LIB_EXPORT double adder(double a, double b) { + return a + b; +} + +LIB_EXPORT void print_hello() { + std::cout << "Hello" << std::endl; +} + +} // extern "C" diff --git a/example/main.cpp b/example/main.cpp index a54859c..92912d8 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -2,24 +2,19 @@ #include "dylib.hpp" int main() { - try { - dylib lib("./dynamic_lib", dylib::extension); + dylib lib("./", "dynamic_lib"); - auto adder = lib.get_function("adder"); - std::cout << adder(5, 10) << std::endl; + auto adder = lib.get_function("adder"); + std::cout << adder(5, 10) << std::endl; - auto printer = lib.get_function("print_hello"); - printer(); + auto printer = lib.get_function("print_hello"); + printer(); - double pi_value = lib.get_variable("pi_value"); - std::cout << pi_value << std::endl; + double pi_value = lib.get_variable("pi_value"); + std::cout << pi_value << std::endl; + + void *ptr = lib.get_variable("ptr"); + if (ptr == (void *)1) std::cout << 1 << std::endl; - void *ptr = lib.get_variable("ptr"); - if (ptr == (void *)1) std::cout << 1 << std::endl; - } - catch (const dylib::exception &e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } return EXIT_SUCCESS; } \ No newline at end of file diff --git a/include/dylib.hpp b/include/dylib.hpp index c52d1b9..f389f11 100644 --- a/include/dylib.hpp +++ b/include/dylib.hpp @@ -1,80 +1,82 @@ /** - * \file dylib.hpp - * \brief C++ cross-platform dynamic library loader - * \link https://github.com/martin-olivier/dylib - * \author Martin Olivier - * \version 1.8.3 + * @file dylib.hpp + * @version 2.0.0 + * @brief C++ cross-platform wrapper around dynamic loading of shared libraries + * @link https://github.com/martin-olivier/dylib * - * MIT License - * Copyright (c) 2022 Martin Olivier + * @author Martin Olivier + * @copyright (c) 2022 Martin Olivier + * + * This library is released under MIT license */ #pragma once #include -#include -#include +#include #include + #if defined(_WIN32) || defined(_WIN64) #define WIN32_LEAN_AND_MEAN -#define DYLIB_API extern "C" __declspec(dllexport) #include #undef WIN32_LEAN_AND_MEAN #else -#define DYLIB_API extern "C" #include #endif +#if defined(_WIN32) || defined(_WIN64) +#define DYLIB_WIN_MAC_OTHER(win_def, mac_def, other_def) win_def +#define DYLIB_WIN_OTHER(win_def, other_def) win_def +#elif defined(__APPLE__) +#define DYLIB_WIN_MAC_OTHER(win_def, mac_def, other_def) mac_def +#define DYLIB_WIN_OTHER(win_def, other_def) other_def +#else +#define DYLIB_WIN_MAC_OTHER(win_def, mac_def, other_def) other_def +#define DYLIB_WIN_OTHER(win_def, other_def) other_def +#endif + /** * The dylib class can hold a dynamic library instance and interact with it * by getting its symbols like functions or global variables */ class dylib { public: -#if defined(_WIN32) || defined(_WIN64) - static constexpr const char *extension = ".dll"; - using native_handle_type = HINSTANCE; -#elif defined(__APPLE__) - static constexpr const char *extension = ".dylib"; - using native_handle_type = void *; -#else - static constexpr const char *extension = ".so"; - using native_handle_type = void *; -#endif + struct filename_components { + static constexpr const char *prefix = DYLIB_WIN_OTHER("", "lib"); + static constexpr const char *suffix = DYLIB_WIN_MAC_OTHER(".dll", ".dylib", ".so"); + }; + using native_handle_type = DYLIB_WIN_OTHER(HINSTANCE, void *); + + static_assert(std::is_pointer::value, "Expecting HINSTANCE to be a pointer"); /** - * This exception is raised when the dylib class encountered an error + * This exception is raised when the library failed to load a dynamic library or a symbol * - * @return the error message by calling what() member function + * @param message the error message */ - class exception : public std::exception { - protected: - const std::string m_error; + class exception : public std::runtime_error { public: - explicit exception(std::string &&message) : m_error(std::move(message)) {} - const char *what() const noexcept override {return m_error.c_str();} + explicit exception(const std::string &message) : std::runtime_error(message) {} }; /** - * This exception is raised when the library failed to load - * or encountered symbol resolution issues + * This exception is raised when the library failed to load or encountered symbol resolution issues * * @param message the error message */ - class handle_error : public exception { + class load_error : public exception { public: - explicit handle_error(std::string &&message) : exception(std::move(message)) {} + explicit load_error(const std::string &message) : exception(message) {} }; /** - * This exception is raised when the library failed to load a symbol. - * This usually happens when you forgot to put before a library function or variable + * This exception is raised when the library failed to load a symbol * * @param message the error message */ class symbol_error : public exception { public: - explicit symbol_error(std::string &&message) : exception(std::move(message)) {} + explicit symbol_error(const std::string &message) : exception(message) {} }; dylib(const dylib&) = delete; @@ -85,108 +87,96 @@ class dylib { } dylib& operator=(dylib &&other) noexcept { - if (this != &other) { - close(); - m_handle = other.m_handle; - other.m_handle = nullptr; - } + if (this != &other) + std::swap(m_handle, other.m_handle); return *this; } - dylib() noexcept = default; - /** - * Creates a dynamic library instance + * @brief Loads a dynamic library + * + * @throws dylib::load_error if the library could not be opened (including + * the case of the library file not being found) * - * @param path path to the dynamic library to load - * @param ext use dylib::extension to specify the os extension (optional parameter) + * @param dir_path the directory path where is located the dynamic library you want to load + * @param name the name of the dynamic library to load + * @param decorations add os decorations to the library name */ - explicit dylib(const char *path) { - open(path); - } + ///@{ + dylib(const char *dir_path, const char *name, bool decorations = true) { + if (!dir_path || !name) + throw std::invalid_argument("Null parameter"); - explicit dylib(const std::string &path) { - open(path.c_str()); - } + std::string final_name = name; + std::string final_path = dir_path; - dylib(const std::string &path, const char *ext) { - open(path, ext); - } + if (decorations) + final_name = filename_components::prefix + final_name + filename_components::suffix; - ~dylib() { - close(); - } + if (final_path != "" && final_path.find_last_of('/') != final_path.size() - 1) + final_path += '/'; + + m_handle = _open((final_path + final_name).c_str()); - /** - * Load a dynamic library into the object. - * If a dynamic library was already opened, it will be unload and replaced - * - * @param path the path of the dynamic library to load - * @param ext use dylib::extension to detect the current os extension (optional parameter) - */ - void open(const char *path) { - close(); - if (!path) - throw handle_error(get_handle_error("(nullptr)")); - m_handle = open_lib(path); if (!m_handle) - throw handle_error(get_handle_error(path)); + throw load_error("Could not load library \"" + final_path + final_name + "\":\n" + _get_error_description()); } - void open(const std::string &path) { - open(path.c_str()); - } + dylib(const std::string &dir_path, const std::string &name, bool decorations = true) + : dylib(dir_path.c_str(), name.c_str(), decorations) {} - void open(const std::string &path, const char *ext) { - if (!ext) - throw handle_error("dylib: failed to load \"" + path + "\", bad extension: (nullptr)"); - open(path + ext); + dylib(const std::string &dir_path, const char *name, bool decorations = true) + : dylib(dir_path.c_str(), name, decorations) {} + + dylib(const char *dir_path, const std::string &name, bool decorations = true) + : dylib(dir_path, name.c_str(), decorations) {} + + explicit dylib(const std::string &name, bool decorations = true) + : dylib("", name.c_str(), decorations) {} + + explicit dylib(const char *name, bool decorations = true) + : dylib("", name, decorations) {} + ///@} + + + ~dylib() { + if (m_handle) + _close(m_handle); } /** * Get a function from the dynamic library currently loaded in the object + * + * @throws dylib::symbol_error if the symbol could not be found * - * @param T the template argument must be the function prototype. - * it must be the same pattern as the template of std::function - * @param name the symbol name of the function to get from the dynamic library + * @param T the template argument must be the function prototype to get + * @param name the symbol name of a function to get from the dynamic library * - * @return std::function that contains the function + * @return a pointer to the requested function */ template - std::function get_function(const char *name) const { - if (!name) - throw symbol_error(get_symbol_error("(nullptr)")); - if (!m_handle) - throw handle_error(get_missing_handle_error(name)); - auto sym = get_symbol(name); - if (!sym) - throw symbol_error(get_symbol_error(name)); - return reinterpret_cast(sym); + T *get_function(const char *name) const { + return reinterpret_cast(locate_symbol(name)); } template - std::function get_function(const std::string &name) const { + T *get_function(const std::string &name) const { return get_function(name.c_str()); } /** - * Get a global variable from the dynamic library currently loaded in the object + * Get a variable from the dynamic library currently loaded in the object + * + * @throws dylib::symbol_error if the symbol could not be found * - * @param T type of the global variable - * @param name the name of the global variable to get from the dynamic library + * @param T the template argument must be the type of the variable to get + * @param name the symbol name of a variable to get from the dynamic library * - * @return global variable of type + * @return a reference to the requested variable */ template T &get_variable(const char *name) const { - if (!name) - throw symbol_error(get_symbol_error("(nullptr)")); - if (!m_handle) - throw handle_error(get_missing_handle_error(name)); - auto sym = get_symbol(name); - if (!sym) - throw symbol_error(get_symbol_error(name)); - return *reinterpret_cast(sym); + return *reinterpret_cast(locate_symbol(name)); } template @@ -208,7 +198,7 @@ class dylib { return false; if (!m_handle) return false; - return get_symbol(symbol) != nullptr; + return _get_symbol(m_handle, symbol) != nullptr; } bool has_symbol(const std::string &symbol) const noexcept { @@ -222,77 +212,56 @@ class dylib { return m_handle; } - /** - * @return true if a dynamic library is currently loaded in the object, false otherwise - */ - operator bool() const noexcept { - return m_handle != nullptr; - } - - /** - * Close the dynamic library currently loaded in the object. - * This function will be automatically called by the class destructor - */ - void close() noexcept { - if (m_handle) { - close_lib(); - m_handle = nullptr; - } - } - -private: +protected: native_handle_type m_handle{nullptr}; + + static native_handle_type _open(const char *path) noexcept { #if defined(_WIN32) || defined(_WIN64) - static native_handle_type open_lib(const char *path) noexcept { return LoadLibraryA(path); +#else + return dlopen(path, RTLD_NOW | RTLD_LOCAL); +#endif } - FARPROC get_symbol(const char *name) const noexcept { - return GetProcAddress(m_handle, name); + + void *locate_symbol(const char *name) const { + if (!name) + throw std::invalid_argument("Null parameter"); + if (!m_handle) + throw std::logic_error("The dynamic library handle is null"); + + auto symbol = _get_symbol(m_handle, name); + + if (symbol == nullptr) + throw symbol_error("Could not locate symbol \"" + std::string(name) + "\":\n" + _get_error_description()); + return symbol; + } + + static DYLIB_WIN_OTHER(FARPROC, void *) + _get_symbol(native_handle_type lib, const char *name) noexcept { + return DYLIB_WIN_OTHER(GetProcAddress, dlsym)(lib, name); } - void close_lib() noexcept { - FreeLibrary(m_handle); + + static void _close(native_handle_type lib) noexcept { + DYLIB_WIN_OTHER(FreeLibrary, dlclose)(lib); } - static char *get_error_message() noexcept { - constexpr size_t buf_size = 512; + + static std::string _get_error_description() noexcept { +#if defined(_WIN32) || defined(_WIN64) + constexpr const size_t buf_size = 512; auto error_code = GetLastError(); if (!error_code) - return nullptr; - static char msg[buf_size]; + return "Unknown error (GetLastError failed)"; + char description[512]; auto lang = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); - const DWORD len = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, error_code, lang, msg, buf_size, nullptr); - if (len > 0) - return msg; - return nullptr; - } + const DWORD length = + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, error_code, lang, description, buf_size, nullptr); + return (length == 0) ? "Unknown error (FormatMessage failed)" : description; #else - static native_handle_type open_lib(const char *path) noexcept { - return dlopen(path, RTLD_NOW | RTLD_LOCAL); - } - void *get_symbol(const char *name) const noexcept { - return dlsym(m_handle, name); - } - void close_lib() noexcept { - dlclose(m_handle); - } - static char *get_error_message() noexcept { - return dlerror(); - } + auto description = dlerror(); + return (description == nullptr) ? "Unknown error (dlerror failed)" : description; #endif - static std::string get_handle_error(const std::string &name) { - std::string msg = "dylib: error while loading dynamic library \"" + name + "\""; - auto err = get_error_message(); - if (!err) - return msg; - return msg + '\n' + err; - } - static std::string get_symbol_error(const std::string &name) { - std::string msg = "dylib: error while loading symbol \"" + name + "\""; - auto err = get_error_message(); - if (!err) - return msg; - return msg + '\n' + err; - } - static std::string get_missing_handle_error(const std::string &symbol_name) { - return "dylib: could not get symbol \"" + symbol_name + "\", no dynamic library currently loaded"; } }; + +#undef DYLIB_WIN_MAC_OTHER +#undef DYLIB_WIN_OTHER diff --git a/tests/lib.cpp b/tests/lib.cpp index 44de59b..f68ad48 100644 --- a/tests/lib.cpp +++ b/tests/lib.cpp @@ -1,13 +1,22 @@ #include -#include "dylib.hpp" -DYLIB_API double pi_value = 3.14159; -DYLIB_API void *ptr = (void *)1; +#if defined(_WIN32) || defined(_WIN64) +#define LIB_EXPORT __declspec(dllexport) +#else +#define LIB_EXPORT +#endif -DYLIB_API double adder(double a, double b) { +extern "C" { + +LIB_EXPORT double pi_value = 3.14159; +LIB_EXPORT void *ptr = (void *)1; + +LIB_EXPORT double adder(double a, double b) { return a + b; } -DYLIB_API void print_hello() { +LIB_EXPORT void print_hello() { std::cout << "Hello" << std::endl; -} \ No newline at end of file +} + +} // extern "C" \ No newline at end of file diff --git a/tests/tests.cpp b/tests/tests.cpp index 8dbd68c..3f2fe22 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -30,77 +30,41 @@ class OSRedirector { TEST(exemple, exemple_test) { OSRedirector oss(std::cout); - try { - dylib lib("./dynamic_lib", dylib::extension); + dylib lib("./", "dynamic_lib"); - auto adder = lib.get_function("adder"); - EXPECT_EQ(adder(5, 10), 15); + auto adder = lib.get_function("adder"); + EXPECT_EQ(adder(5, 10), 15); - auto printer = lib.get_function("print_hello"); - printer(); - EXPECT_EQ(oss.getContent(), "Hello\n"); + auto printer = lib.get_function("print_hello"); + printer(); + EXPECT_EQ(oss.getContent(), "Hello\n"); - double pi_value = lib.get_variable("pi_value"); - EXPECT_EQ(pi_value, 3.14159); + double pi_value = lib.get_variable("pi_value"); + EXPECT_EQ(pi_value, 3.14159); - void *ptr = lib.get_variable("ptr"); - EXPECT_EQ(ptr, (void *)1); - } - catch (const dylib::exception &) { - EXPECT_EQ(true, false); - } + void *ptr = lib.get_variable("ptr"); + EXPECT_EQ(ptr, (void *)1); } TEST(ctor, bad_library) { try { - dylib lib("./null.so"); + dylib lib("./", "no_such_library"); EXPECT_EQ(true, false); } - catch (const dylib::exception &e) { + catch (const dylib::load_error &) { EXPECT_EQ(true, true); } } TEST(multiple_handles, basic_test) { - dylib libA("./dynamic_lib", dylib::extension); - dylib libB("./dynamic_lib", dylib::extension); -} - -TEST(dtor, mutiple_open_close) { - try { - dylib lib; - lib.close(); - lib.close(); - lib.open(std::string("./dynamic_lib") + std::string(dylib::extension)); - lib.open("./dynamic_lib", dylib::extension); - EXPECT_EQ(lib.get_function("adder")(1, 1), 2); - lib.close(); - lib.close(); - lib.close(); - auto fn = lib.get_function("adder"); - EXPECT_EQ(true, false); - } - catch (const dylib::exception &) { - EXPECT_EQ(true, true); - } -} - -TEST(get_function, bad_handler) { - try { - dylib lib("./dynamic_lib", dylib::extension); - lib.close(); - auto adder = lib.get_function("adder"); - EXPECT_EQ(true, false); - } - catch (const dylib::handle_error &) { - EXPECT_EQ(true, true); - } + dylib libA("./", "dynamic_lib"); + dylib libB("./", "dynamic_lib"); } TEST(get_function, bad_symbol) { try { - dylib lib("./dynamic_lib", dylib::extension); - auto adder = lib.get_function("unknown"); + dylib lib("./", "dynamic_lib"); + lib.get_function("unknown"); EXPECT_EQ(true, false); } catch (const dylib::symbol_error &) { @@ -108,21 +72,9 @@ TEST(get_function, bad_symbol) { } } -TEST(get_variable, bad_handler) { - try { - dylib lib("./dynamic_lib", dylib::extension); - lib.close(); - lib.get_variable("pi_value"); - EXPECT_EQ(true, false); - } - catch (const dylib::handle_error &) { - EXPECT_EQ(true, true); - } -} - TEST(get_variable, bad_symbol) { try { - dylib lib("./dynamic_lib", dylib::extension); + dylib lib("./", "dynamic_lib"); lib.get_variable("unknown"); EXPECT_EQ(true, false); } @@ -132,99 +84,56 @@ TEST(get_variable, bad_symbol) { } TEST(get_variable, alter_variables) { - try { - dylib lib("./dynamic_lib", dylib::extension); - dylib other(std::move(lib)); - auto &pi = other.get_variable("pi_value"); - EXPECT_EQ(pi, 3.14159); - pi = 123; - auto &pi1 = other.get_variable("pi_value"); - EXPECT_EQ(pi1, 123); + dylib lib("./", "dynamic_lib"); - auto &ptr = other.get_variable("ptr"); - EXPECT_EQ(ptr, (void *)1); - ptr = &lib; - auto &ptr1 = other.get_variable("ptr"); - EXPECT_EQ(ptr1, &lib); - } - catch (const dylib::handle_error &) { - EXPECT_EQ(true, false); - } + auto &pi = lib.get_variable("pi_value"); + EXPECT_EQ(pi, 3.14159); + pi = 123; + auto &pi1 = lib.get_variable("pi_value"); + EXPECT_EQ(pi1, 123); + + auto &ptr = lib.get_variable("ptr"); + EXPECT_EQ(ptr, (void *)1); + ptr = &lib; + auto &ptr1 = lib.get_variable("ptr"); + EXPECT_EQ(ptr1, &lib); } -TEST(bad_arguments, null_pointer) { - try { - dylib lib(nullptr); - EXPECT_EQ(true, false); - } - catch (const dylib::handle_error &) { - EXPECT_EQ(true, true); - } +TEST(invalid_argument, null_pointer) { try { - dylib lib("./dynamic_lib", dylib::extension); - lib.get_function(nullptr); - EXPECT_EQ(true, false); - } - catch (const dylib::symbol_error &) { - EXPECT_EQ(true, true); - } - try { - dylib lib("./dynamic_lib", dylib::extension); - lib.get_variable(nullptr); + dylib(nullptr); EXPECT_EQ(true, false); } - catch (const dylib::symbol_error &) { + catch (const std::invalid_argument &) { EXPECT_EQ(true, true); } try { - dylib lib; + dylib lib("./", "dynamic_lib"); lib.get_function(nullptr); EXPECT_EQ(true, false); } - catch (const dylib::symbol_error &) { + catch (const std::invalid_argument &) { EXPECT_EQ(true, true); } try { - dylib lib; + dylib lib("./", "dynamic_lib"); lib.get_variable(nullptr); EXPECT_EQ(true, false); } - catch (const dylib::symbol_error &) { + catch (const std::invalid_argument &) { EXPECT_EQ(true, true); } } -TEST(bad_arguments, handle_and_ext) { - try { - dylib lib("./badlib", dylib::extension); - EXPECT_EQ(true, false); - } - catch (const dylib::handle_error &) { - EXPECT_EQ(true, true); - } - try { - dylib lib("./dynamic_lib", nullptr); - EXPECT_EQ(true, false); - } - catch (const dylib::handle_error &) { - EXPECT_EQ(true, true); - } -} - -TEST(os_detector, basic_test) { - try { - dylib lib("./dynamic_lib", dylib::extension); - auto pi = lib.get_variable("pi_value"); - EXPECT_EQ(pi, 3.14159); - } - catch (const dylib::exception &) { - EXPECT_EQ(true, false); - } +TEST(manual_decorations, basic_test) { + dylib lib(".", dylib::filename_components::prefix + std::string("dynamic_lib") + dylib::filename_components::suffix, false); + auto pi = lib.get_variable("pi_value"); + EXPECT_EQ(pi, 3.14159); } TEST(std_move, basic_test) { try { - dylib lib("./dynamic_lib", dylib::extension); + dylib lib("./", "dynamic_lib"); dylib other(std::move(lib)); auto pi = other.get_variable("pi_value"); EXPECT_EQ(pi, 3.14159); @@ -234,38 +143,23 @@ TEST(std_move, basic_test) { other.get_variable("pi_value"); EXPECT_EQ(true, false); } - catch (const dylib::handle_error &) { + catch (const std::logic_error &) { EXPECT_EQ(true, true); } } TEST(has_symbol, basic_test) { - dylib lib; - EXPECT_FALSE(lib.has_symbol(nullptr)); - EXPECT_FALSE(lib.has_symbol("pi_value")); - lib.open("./dynamic_lib", dylib::extension); + dylib dummy("./", "dynamic_lib"); + dylib lib(std::move(dummy)); + EXPECT_TRUE(lib.has_symbol("pi_value")); EXPECT_FALSE(lib.has_symbol("bad_symbol")); - lib.close(); - EXPECT_FALSE(lib.has_symbol("pi_value")); -} - -TEST(operator_bool, basic_test) { - dylib lib; - EXPECT_FALSE(lib); - EXPECT_TRUE(!lib); - lib.open("./dynamic_lib", dylib::extension); - EXPECT_TRUE(lib); - EXPECT_FALSE(!lib); - lib.close(); - EXPECT_TRUE(!lib); - EXPECT_FALSE(lib); + EXPECT_FALSE(lib.has_symbol(nullptr)); + EXPECT_FALSE(dummy.has_symbol("pi_value")); } TEST(handle_management, basic_test) { - dylib lib; - EXPECT_EQ(lib.native_handle(), nullptr); - lib.open("./dynamic_lib", dylib::extension); + dylib lib("./", "dynamic_lib"); EXPECT_FALSE(lib.native_handle() == nullptr); auto handle = lib.native_handle(); #if defined(_WIN32) || defined(_WIN64) @@ -276,11 +170,22 @@ TEST(handle_management, basic_test) { EXPECT_FALSE(sym == nullptr); auto res = ((double (*)(double, double))(sym))(10, 10); EXPECT_EQ(res, 20); - lib.close(); - EXPECT_EQ(lib.native_handle(), nullptr); +} + +TEST(system_lib, basic_test) { +#if defined(_WIN32) || defined(_WIN64) + dylib lib("kernel32"); + lib.get_function("GetCurrentThreadId"); +#elif defined(__APPLE__) + dylib lib("ssh2"); + lib.get_function("libssh2_version"); +#else + dylib lib("pthread"); + lib.get_function("pthread_yield"); +#endif } int main(int ac, char **av) { testing::InitGoogleTest(&ac, av); return RUN_ALL_TESTS(); -} \ No newline at end of file +}