From 99aa061f62183d53414097d6f278a1e278d0cb48 Mon Sep 17 00:00:00 2001 From: Martin Olivier Date: Wed, 22 Jun 2022 17:30:52 +0200 Subject: [PATCH] Version 2.1.0 (#57) feat: add_filename_decorations and no_filename_decorations constants feat: get_symbol public member function and 2.1.0 version setup feat: CI now checks both c++11 and c++17 fix: protected dylib members have been renamed fix: timeout issues on windows CI fix: cleaner documentation Signed-off-by: Martin Olivier --- .github/workflows/CI.yml | 124 ++++++++++++++++++++--------------- CMakeLists.txt | 6 +- README.md | 50 +++++++++----- example/CMakeLists.txt | 2 +- example/README.md | 2 +- include/dylib.hpp | 137 ++++++++++++++++++++++++--------------- tests/tests.cpp | 21 +++++- 7 files changed, 216 insertions(+), 126 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7329224..89f0122 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -3,74 +3,96 @@ name: CI on: [push, pull_request] defaults: - run: - shell: bash + run: + shell: bash jobs: - unit_tests: - strategy: - fail-fast: false - matrix: - os: [windows-latest, macos-latest, ubuntu-latest] + unit_tests_cpp11: + strategy: + fail-fast: false + matrix: + os: [windows-latest, macos-latest, ubuntu-latest] - name: ${{ matrix.os }} - runs-on: ${{ matrix.os }} + name: ${{ matrix.os }} (C++11) + runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 + steps: + - uses: actions/checkout@v2 - - name: Generate project files - run: cmake . -B build -DBUILD_TESTS=ON + - name: Generate project files + run: cmake . -B build -DCMAKE_CXX_STANDARD=11 -DBUILD_TESTS=ON - - name: Build dynamic library and unit tests - run: cmake --build build + - name: Build dynamic library and unit tests + run: cmake --build build - - name: Run unit tests - working-directory: build - run: ctest + - name: Run unit tests + working-directory: build + run: ctest - - name: Generate code coverage - if: ${{ matrix.os == 'ubuntu-latest' }} - run: gcov -abcfu build/CMakeFiles/unit_tests.dir/tests/tests.cpp.gcda + unit_tests_cpp17: + strategy: + fail-fast: false + matrix: + os: [windows-latest, macos-latest, ubuntu-latest] - - name: Send coverage to codecov.io - if: ${{ matrix.os == 'ubuntu-latest' }} - uses: codecov/codecov-action@v2 - with: - files: dylib.hpp.gcov + name: ${{ matrix.os }} (C++17) + runs-on: ${{ matrix.os }} - memory_check: - name: memory check - runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 - steps: - - uses: actions/checkout@v2 + - name: Generate project files + run: cmake . -B build -DCMAKE_CXX_STANDARD=17 -DBUILD_TESTS=ON - - name: Update packages - run: sudo apt update + - name: Build dynamic library and unit tests + run: cmake --build build - - name: Install valgrind - run: sudo apt install -y valgrind + - name: Run unit tests + working-directory: build + run: ctest - - name: Generate tests build file - run: cmake . -B build -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug + - name: Generate code coverage + if: ${{ matrix.os == 'ubuntu-latest' }} + run: gcov -abcfu build/CMakeFiles/unit_tests.dir/tests/tests.cpp.gcda - - name: Build unit tests - run: cmake --build build + - name: Send coverage to codecov.io + if: ${{ matrix.os == 'ubuntu-latest' }} + uses: codecov/codecov-action@v2 + with: + files: dylib.hpp.gcov - - name: Run unit tests with valgrind - working-directory: build - run: valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./unit_tests + memory_check: + name: memory check + runs-on: ubuntu-latest - linter: - name: linter - runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 - steps: - - uses: actions/checkout@v2 + - name: Update packages + run: sudo apt update - - name: Install cpplint - run: pip install cpplint + - name: Install valgrind + run: sudo apt install -y valgrind - - name: Run cpplint - run: cpplint --linelength=140 --filter=-whitespace/indent,-whitespace/parens include/dylib.hpp \ No newline at end of file + - name: Generate tests build file + run: cmake . -B build -DCMAKE_CXX_STANDARD=17 -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug + + - name: Build unit tests + run: cmake --build build + + - name: Run unit tests with valgrind + working-directory: build + run: valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./unit_tests + + linter: + name: linter + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install cpplint + run: pip install cpplint + + - name: Run cpplint + run: cpplint --linelength=140 --filter=-whitespace/indent,-whitespace/parens include/dylib.hpp \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 7968a7b..d6ad411 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,9 @@ cmake_minimum_required(VERSION 3.14) project(dylib CXX) -set(CMAKE_CXX_STANDARD 11) +if(NOT "${CMAKE_CXX_STANDARD}") + set(CMAKE_CXX_STANDARD 11) +endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(dylib INTERFACE) @@ -49,6 +51,6 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) endif() include(GoogleTest) - gtest_discover_tests(unit_tests WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) + gtest_discover_tests(unit_tests PROPERTIES TIMEOUT 600 WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) endif() endif() diff --git a/README.md b/README.md index 56fa9f4..ea61009 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@

- dylib + dylib

- - version + + version license @@ -41,13 +41,13 @@ include(FetchContent) FetchContent_Declare( dylib GIT_REPOSITORY "https://github.com/martin-olivier/dylib" - GIT_TAG "v2.0.0" + GIT_TAG "v2.1.0" ) FetchContent_MakeAvailable(dylib) ``` -You can also click [HERE](https://github.com/martin-olivier/dylib/releases/download/v2.0.0/dylib.hpp) to download the `dylib` header file. +You can also click [HERE](https://github.com/martin-olivier/dylib/releases/download/v2.1.0/dylib.hpp) to download the `dylib` header file. # Documentation @@ -61,28 +61,28 @@ dylib lib("foo"); ``` The `dylib` class can also load a dynamic library from a specific path ```c++ -// Load "foo" lib from relative path "./libs" +// Load "foo" library from relative path "./libs" dylib lib("./libs", "foo"); -// Load "foo" lib from full path "/usr/lib" +// Load "foo" library from full path "/usr/lib" dylib lib("/usr/lib", "foo"); ``` -The `dylib` class will automatically add os decorations to the library name, but you can disable that by setting `decorations` parameter to false +The `dylib` class will automatically add the filename decorations of the current os to the library name, but you can disable that by setting `decorations` parameter to `dylib::no_filename_decorations` ```c++ // Windows -> "foo.dll" -// MacOS: -> "libfoo.dylib" -// Linux: -> "libfoo.so" +// MacOS -> "libfoo.dylib" +// Linux -> "libfoo.so" dylib lib("foo"); // Windows -> "foo.lib" -// MacOS: -> "foo.lib" -// Linux: -> "foo.lib" +// MacOS -> "foo.lib" +// Linux -> "foo.lib" -dylib lib("foo.lib", false); +dylib lib("foo.lib", dylib::no_filename_decorations); ``` ## Get a function or a variable @@ -113,15 +113,20 @@ double result = adder(pi, pi); ## Miscellaneous tools `has_symbol` -Returns true if the symbol passed as parameter exists in the dynamic library, false otherwise +Returns true if the symbol passed as parameter exists in the dynamic library, false otherwise + +`get_symbol` +Get a symbol from the dynamic library currently loaded in the object `native_handle` -Returns the dynamic library handle +Returns the dynamic library handle ```c++ void example(dylib &lib) { if (lib.has_symbol("GetModule")) - std::cout << "GetModule symbol has been found" << std::endl; + dylib::native_symbol_type sym = lib.get_symbol("GetModule"); + else + std::cout << "GetModule symbol could not be found" << std::endl; dylib::native_handle_type handle = lib.native_handle(); void *sym = dlsym(handle, "GetModule"); @@ -144,7 +149,7 @@ try { std::cout << pi_value << std::endl; } catch (const dylib::load_error &e) { - // failed loading "foo" lib + // failed loading "foo" library } catch (const dylib::symbol_error &e) { // failed loading "pi_value" symbol @@ -167,3 +172,14 @@ To run unit tests, enter the following command inside "build" directory: ```sh ctest ``` + +# Community + +If you have any question about the usage of the library, do not hesitate to open a [discussion](https://github.com/martin-olivier/dylib/discussions) + +If you want to report a bug or provide a feature, do not hesitate to open an [issue](https://github.com/martin-olivier/dylib/issues) or submit a [pull request](https://github.com/martin-olivier/dylib/pulls) + +> Do not forget to sign your commits and use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) when providing a pull request +```sh +git commit -s -m "feat: ..." +``` \ No newline at end of file diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 409c607..f60ad2f 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -15,7 +15,7 @@ include(FetchContent) FetchContent_Declare( dylib GIT_REPOSITORY "https://github.com/martin-olivier/dylib" - GIT_TAG "v2.0.0" + GIT_TAG "v2.1.0" ) FetchContent_MakeAvailable(dylib) diff --git a/example/README.md b/example/README.md index fea2997..fa76f0c 100644 --- a/example/README.md +++ b/example/README.md @@ -82,7 +82,7 @@ include(FetchContent) FetchContent_Declare( dylib GIT_REPOSITORY "https://github.com/martin-olivier/dylib" - GIT_TAG "v2.0.0" + GIT_TAG "v2.1.0" ) FetchContent_MakeAvailable(dylib) diff --git a/include/dylib.hpp b/include/dylib.hpp index f389f11..62a9b39 100644 --- a/include/dylib.hpp +++ b/include/dylib.hpp @@ -1,6 +1,6 @@ /** * @file dylib.hpp - * @version 2.0.0 + * @version 2.1.0 * @brief C++ cross-platform wrapper around dynamic loading of shared libraries * @link https://github.com/martin-olivier/dylib * @@ -16,6 +16,11 @@ #include #include +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) +#define DYLIB_CPP17 +#include +#endif + #if defined(_WIN32) || defined(_WIN64) #define WIN32_LEAN_AND_MEAN #include @@ -46,8 +51,13 @@ class dylib { static constexpr const char *suffix = DYLIB_WIN_MAC_OTHER(".dll", ".dylib", ".so"); }; using native_handle_type = DYLIB_WIN_OTHER(HINSTANCE, void *); + using native_symbol_type = DYLIB_WIN_OTHER(FARPROC, void *); static_assert(std::is_pointer::value, "Expecting HINSTANCE to be a pointer"); + static_assert(std::is_pointer::value, "Expecting FARPROC to be a pointer"); + + static constexpr bool add_filename_decorations = true; + static constexpr bool no_filename_decorations = false; /** * This exception is raised when the library failed to load a dynamic library or a symbol @@ -103,11 +113,11 @@ class dylib { * @param decorations add os decorations to the library name */ ///@{ - dylib(const char *dir_path, const char *name, bool decorations = true) { - if (!dir_path || !name) + dylib(const char *dir_path, const char *lib_name, bool decorations = add_filename_decorations) { + if (!dir_path || !lib_name) throw std::invalid_argument("Null parameter"); - std::string final_name = name; + std::string final_name = lib_name; std::string final_path = dir_path; if (decorations) @@ -116,32 +126,68 @@ class dylib { if (final_path != "" && final_path.find_last_of('/') != final_path.size() - 1) final_path += '/'; - m_handle = _open((final_path + final_name).c_str()); + m_handle = open((final_path + final_name).c_str()); if (!m_handle) - throw load_error("Could not load library \"" + final_path + final_name + "\":\n" + _get_error_description()); + throw load_error("Could not load library \"" + final_path + final_name + "\"\n" + get_error_description()); } - dylib(const std::string &dir_path, const std::string &name, bool decorations = true) - : dylib(dir_path.c_str(), name.c_str(), decorations) {} + dylib(const std::string &dir_path, const std::string &lib_name, bool decorations = add_filename_decorations) + : dylib(dir_path.c_str(), lib_name.c_str(), decorations) {} - dylib(const std::string &dir_path, const char *name, bool decorations = true) - : dylib(dir_path.c_str(), name, decorations) {} + dylib(const std::string &dir_path, const char *lib_name, bool decorations = add_filename_decorations) + : dylib(dir_path.c_str(), lib_name, decorations) {} - dylib(const char *dir_path, const std::string &name, bool decorations = true) - : dylib(dir_path, name.c_str(), decorations) {} + dylib(const char *dir_path, const std::string &lib_name, bool decorations = add_filename_decorations) + : dylib(dir_path, lib_name.c_str(), decorations) {} - explicit dylib(const std::string &name, bool decorations = true) - : dylib("", name.c_str(), decorations) {} + explicit dylib(const std::string &lib_name, bool decorations = add_filename_decorations) + : dylib("", lib_name.c_str(), decorations) {} - explicit dylib(const char *name, bool decorations = true) - : dylib("", name, decorations) {} - ///@} + explicit dylib(const char *lib_name, bool decorations = add_filename_decorations) + : dylib("", lib_name, decorations) {} + +#ifdef DYLIB_CPP17 + explicit dylib(const std::filesystem::path &lib_path) + : dylib("", lib_path.string().c_str(), no_filename_decorations) {} + dylib(const std::filesystem::path &dir_path, const std::string &lib_name, bool decorations = add_filename_decorations) + : dylib(dir_path.string().c_str(), lib_name.c_str(), decorations) {} + + dylib(const std::filesystem::path &dir_path, const char *lib_name, bool decorations = add_filename_decorations) + : dylib(dir_path.string().c_str(), lib_name, decorations) {} +#endif + ///@} ~dylib() { if (m_handle) - _close(m_handle); + close(m_handle); + } + + /** + * Get a symbol from the dynamic library currently loaded in the object + * + * @throws dylib::symbol_error if the symbol could not be found + * + * @param symbol_name the symbol name to get from the dynamic library + * + * @return a pointer to the requested symbol + */ + native_symbol_type get_symbol(const char *symbol_name) const { + if (!symbol_name) + throw std::invalid_argument("Null parameter"); + if (!m_handle) + throw std::logic_error("The dynamic library handle is null"); + + auto symbol = locate_symbol(m_handle, symbol_name); + + if (symbol == nullptr) + throw symbol_error("Could not get symbol \"" + std::string(symbol_name) + "\"\n" + get_error_description()); + return symbol; + } + + native_symbol_type get_symbol(const std::string &symbol_name) const { + return get_symbol(symbol_name.c_str()); } /** @@ -150,18 +196,18 @@ class dylib { * @throws dylib::symbol_error if the symbol could not be found * * @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 + * @param symbol_name the symbol name of a function to get from the dynamic library * * @return a pointer to the requested function */ template - T *get_function(const char *name) const { - return reinterpret_cast(locate_symbol(name)); + T *get_function(const char *symbol_name) const { + return reinterpret_cast(get_symbol(symbol_name)); } template - T *get_function(const std::string &name) const { - return get_function(name.c_str()); + T *get_function(const std::string &symbol_name) const { + return get_function(symbol_name.c_str()); } /** @@ -170,35 +216,33 @@ class dylib { * @throws dylib::symbol_error if the symbol could not be found * * @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 + * @param symbol_name the symbol name of a variable to get from the dynamic library * * @return a reference to the requested variable */ template - T &get_variable(const char *name) const { - return *reinterpret_cast(locate_symbol(name)); + T &get_variable(const char *symbol_name) const { + return *reinterpret_cast(get_symbol(symbol_name)); } template - T &get_variable(const std::string &name) const { - return get_variable(name.c_str()); + T &get_variable(const std::string &symbol_name) const { + return get_variable(symbol_name.c_str()); } /** * Check if a symbol exists in the currently loaded dynamic library. * This method will return false if no dynamic library is currently loaded - * or if the symbol equals nullptr + * or if the symbol name is nullptr * - * @param symbol the symbol name to look for + * @param symbol_name the symbol name to look for * * @return true if the symbol exists in the dynamic library, false otherwise */ - bool has_symbol(const char *symbol) const noexcept { - if (!symbol) - return false; - if (!m_handle) + bool has_symbol(const char *symbol_name) const noexcept { + if (!m_handle || !symbol_name) return false; - return _get_symbol(m_handle, symbol) != nullptr; + return locate_symbol(m_handle, symbol_name) != nullptr; } bool has_symbol(const std::string &symbol) const noexcept { @@ -215,7 +259,7 @@ class dylib { protected: native_handle_type m_handle{nullptr}; - static native_handle_type _open(const char *path) noexcept { + static native_handle_type open(const char *path) noexcept { #if defined(_WIN32) || defined(_WIN64) return LoadLibraryA(path); #else @@ -223,29 +267,15 @@ class dylib { #endif } - 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 { + static native_symbol_type locate_symbol(native_handle_type lib, const char *name) noexcept { return DYLIB_WIN_OTHER(GetProcAddress, dlsym)(lib, name); } - static void _close(native_handle_type lib) noexcept { + static void close(native_handle_type lib) noexcept { DYLIB_WIN_OTHER(FreeLibrary, dlclose)(lib); } - static std::string _get_error_description() noexcept { + static std::string get_error_description() noexcept { #if defined(_WIN32) || defined(_WIN64) constexpr const size_t buf_size = 512; auto error_code = GetLastError(); @@ -265,3 +295,4 @@ class dylib { #undef DYLIB_WIN_MAC_OTHER #undef DYLIB_WIN_OTHER +#undef DYLIB_CPP17 diff --git a/tests/tests.cpp b/tests/tests.cpp index 3f2fe22..1f7da14 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -126,7 +126,7 @@ TEST(invalid_argument, null_pointer) { } TEST(manual_decorations, basic_test) { - dylib lib(".", dylib::filename_components::prefix + std::string("dynamic_lib") + dylib::filename_components::suffix, false); + dylib lib(".", dylib::filename_components::prefix + std::string("dynamic_lib") + dylib::filename_components::suffix, dylib::no_filename_decorations); auto pi = lib.get_variable("pi_value"); EXPECT_EQ(pi, 3.14159); } @@ -185,6 +185,25 @@ TEST(system_lib, basic_test) { #endif } +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) +TEST(filesystem, basic_test) { + bool has_sym = dylib(std::filesystem::path("."), "dynamic_lib").has_symbol("pi_value"); + EXPECT_TRUE(has_sym); + + bool found = false; + for (const auto &file : std::filesystem::recursive_directory_iterator(".")) { + if (file.path().extension() == dylib::filename_components::suffix) { + try { + dylib lib(file.path()); + if (lib.has_symbol("pi_value")) + found = true; + } catch (const std::exception &) {} + } + } + EXPECT_TRUE(found); +} +#endif + int main(int ac, char **av) { testing::InitGoogleTest(&ac, av); return RUN_ALL_TESTS();