From 5334ecc52271def78d94f11c5ef59537e5579b58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20S=C3=B6derholm?= Date: Tue, 11 Jun 2024 09:47:51 +0200 Subject: [PATCH] Feature/headless impl (#3) Add option for headless mode move exception and none-exception impl to different headers replace "friend" with a member function for find_function and add free function for convenience. add preset for header-only --- .gitignore | 1 + CMakeLists.txt | 40 ++++---- CMakePresets.json | 48 ++++++++- include/dynl/dynl.hpp | 98 ++++--------------- include/dynl/dynl_types.hpp | 56 +++++++++++ .../platform_details/dynl_error_except.hpp | 15 +++ .../platform_details/dynl_error_noexcept.hpp | 19 ++++ .../dynl/platform_details/dynl_headeronly.hpp | 28 ++++++ include/dynl/platform_details/dynl_src.hpp | 32 ++++++ include/dynl/platform_details/dynl_unix.hpp | 45 +++++++++ include/dynl/platform_details/dynl_win32.hpp | 55 +++++++++++ src/dynl_shared.cpp | 22 +---- src/dynl_unix.cpp | 41 ++------ src/dynl_win32.cpp | 52 ++-------- 14 files changed, 355 insertions(+), 197 deletions(-) create mode 100644 include/dynl/dynl_types.hpp create mode 100644 include/dynl/platform_details/dynl_error_except.hpp create mode 100644 include/dynl/platform_details/dynl_error_noexcept.hpp create mode 100644 include/dynl/platform_details/dynl_headeronly.hpp create mode 100644 include/dynl/platform_details/dynl_src.hpp create mode 100644 include/dynl/platform_details/dynl_unix.hpp create mode 100644 include/dynl/platform_details/dynl_win32.hpp diff --git a/.gitignore b/.gitignore index 30dabac..7caaace 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ # IDE-specifics .idea +.vs # common build out-folders: out-* diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b81ecf..5266aff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ set(DYNLOAD_USE_EXCEPTIONS ON CACHE BOOL "Standard C++ uses exceptions, thus, th set(DYNLOAD_IMPORT ON CACHE BOOL "Set to true if needing importing part of DYNLOAD") set(DYNLOAD_EXPORT ON CACHE BOOL "Set to true if needing exporting part of DYNLOAD") set(DYNLOAD_COMPILE_PLATFORM ON CACHE BOOL "If true, dynl will be automatically compiled for the target platform. Set to false if custom backend is needed.") +set(DYNLOAD_HEADERONLY OFF CACHE BOOL "Set to true to use dynload as a header-only library") add_library(dynl_compile_opts INTERFACE) add_library(dynl::compile_opts ALIAS dynl_compile_opts) @@ -29,15 +30,6 @@ if (DYNLOAD_IMPORT) ) target_compile_features(dynl_headers INTERFACE cxx_std_20) - add_library(dynl_shared) - add_library(dynl::dynl_shared ALIAS dynl_shared) - target_link_libraries(dynl_shared PUBLIC - dynl::dynl_headers - ${DYNLOAD_DEV_LINK} - ) - target_sources(dynl_shared PRIVATE - src/dynl_shared.cpp - ) if (DYNLOAD_USE_EXCEPTIONS) else () target_compile_definitions(dynl_shared PRIVATE @@ -45,27 +37,39 @@ if (DYNLOAD_IMPORT) ) endif () + if (DYNLOAD_HEADERONLY) + set(DYNLOAD_LIB_TYPE INTERFACE) + set(DYNLOAD_PUBLIC_LINK_TYPE INTERFACE) + else () + set(DYNLOAD_LIB_TYPE "") + set(DYNLOAD_PUBLIC_LINK_TYPE PUBLIC) + endif () - add_library(dynl) + add_library(dynl ${DYNLOAD_LIB_TYPE}) add_library(dynl::dynl ALIAS dynl) - target_link_libraries(dynl PUBLIC - dynl::dynl_shared + target_link_libraries(dynl ${DYNLOAD_PUBLIC_LINK_TYPE} + dynl::dynl_headers ${DYNLOAD_DEV_LINK} ) if (DYNLOAD_COMPILE_PLATFORM) if (WIN32) - target_sources(dynl PRIVATE - src/dynl_win32.cpp - ) + set(DYNL_SRC src/dynl_win32.cpp) + set(DYNL_PLATFORM DYNL_WINDOWS=1) elseif (UNIX) - target_sources(dynl PRIVATE - src/dynl_unix.cpp - ) + set(DYNL_SRC src/dynl_unix.cpp) + set(DYNL_PLATFORM DYNL_UNIX=1) else () message(SEND_ERROR "Dynload: no automatic backend available! Use custom defined source file") endif () else () message(STATUS "Dynload: no automatic backend. Custom defined source-file(-s) for 'dynl' required") + set(DYNL_SRC "") + set(DYNL_PLATFORM "") + endif () + if(DYNLOAD_HEADERONLY) + target_compile_definitions(dynl INTERFACE DYNLOAD_HEADERONLY=1 ${DYNL_PLATFORM}) + else () + target_sources(dynl PRIVATE ${DYNL_SRC}) endif () endif () if (DYNLOAD_EXPORT) diff --git a/CMakePresets.json b/CMakePresets.json index 473a4a8..161468a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -13,6 +13,13 @@ "DYNLOAD_DEVELOP": true } }, + { + "name": "dynl-headeronly", + "hidden": true, + "cacheVariables": { + "DYNLOAD_HEADERONLY": true + } + }, { "name": "msvc-warnings", "hidden": true, @@ -56,7 +63,7 @@ "lhs": "${hostSystemName}", "rhs": "Windows" }, - "inherits": ["msvc-warnings", "dynl-dev", "windows-test-include"], + "inherits": [ "msvc-warnings", "dynl-dev", "windows-test-include" ], "cacheVariables": { "CMAKE_CXX_COMPILER": "cl", "CMAKE_C_COMPILER": "cl" @@ -66,7 +73,7 @@ { "name": "clang-base", "hidden": true, - "inherits": ["clang-warnings", "dynl-dev"], + "inherits": [ "clang-warnings", "dynl-dev" ], "cacheVariables": { "CMAKE_CXX_COMPILER": "clang++", "CMAKE_C_COMPILER": "clang" @@ -76,7 +83,7 @@ { "name": "gcc-base", "hidden": true, - "inherits": ["gcc-warnings", "dynl-dev", "linux-test-include"], + "inherits": [ "gcc-warnings", "dynl-dev", "linux-test-include" ], "cacheVariables": { "CMAKE_CXX_COMPILER": "g++", "CMAKE_C_COMPILER": "gcc" @@ -102,6 +109,18 @@ }, "installDir": "${sourceDir}/install/msvc22-debug" }, + { + "name": "msvc22-hh-debug", + "binaryDir": "${sourceDir}/build/msvc22-hh-debug", + "installDir": "${sourceDir}/install/msvc22-hh-debug", + "inherits": [ + "msvc-base", + "dynl-headeronly" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, { "name": "msvc22-reldeb", "binaryDir": "${sourceDir}/build/msvc22-relwithdebinfo", @@ -124,6 +143,18 @@ "CMAKE_BUILD_TYPE": "Release" } }, + { + "name": "clang-hh-release", + "inherits": [ + "clang-base", + "dynl-headeronly" + ], + "binaryDir": "${sourceDir}/build/clang-hh-release", + "installDir": "${sourceDir}/install/clang-hh-release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, { "name": "gcc-release", "inherits": [ @@ -134,6 +165,17 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } + }, + { + "name": "gcc-hh-release", + "inherits": [ + "gcc-base" + ], + "binaryDir": "${sourceDir}/build/gcc-hh-release", + "installDir": "${sourceDir}/install/gcc-hh-release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } } ] } \ No newline at end of file diff --git a/include/dynl/dynl.hpp b/include/dynl/dynl.hpp index 8311d08..d6c8bc2 100644 --- a/include/dynl/dynl.hpp +++ b/include/dynl/dynl.hpp @@ -13,84 +13,15 @@ #include #include +#include -namespace dynl { -/// \typedef c_function_pointer -/// Template alias to make function pointer API readable. -template using c_function_pointer = T *; - -void _default_error_callback(dynl_error const &); -constexpr auto _default_error_callbacker = [](dynl_error const &err) { - _default_error_callback(err); -}; - -class dynamic_library; -class dynamic_function_symbol; - -/// \class dynamic_library_pointer -/// Represents a raw dynamic library. It lacks any formal definition and is only -/// used for type safety. reinterpret_cast it to/from platform-dependent type -/// representing the dynamic library. -class dynamic_library_pointer; -namespace _backend { - -/// Platform-dependent low-level dynamic library function access. -/// You must implement your own version of this when extending the code for -/// further platforms. If an error occurs, call err_cb with an appropriate error -/// and return nullptr. -/// \param p Path to library -/// \param err_cb Error handler. Default throws exception if enabled, otherwise -/// aborts with error message. -/// \return Library pointer. Can be null if err_cb returns without throwing. -dynamic_library_pointer *do_load_library(char const *p, - error_callback const &err_cb = { - _default_error_callbacker}); - -/// Platform-dependent low-level dynamic library function access. -/// You must implement your own version of this when extending the code for -/// further platforms. -/// \param p Pointer to library -void do_release_library(dynamic_library_pointer *p); - -/// Platform-dependent low-level dynamic library function access. -/// You must implement your own version of this when extending the code for -/// further platforms. If an error occurs, call err_cb with an appropriate error -/// and return nullptr. -/// \param lib Library to search -/// \param name Function name to find -/// \param err_cb Error handler. Default throws exception if enabled, otherwise -/// aborts with error message. -/// \return Symbol, can be null if callback returns without throwing. -dynamic_function_symbol -do_find_function(dynamic_library_pointer *lib, char const *name, - error_callback const &err_cb = {_default_error_callbacker}); -} // namespace _backend - -/// \class dynamic_function_symbol -/// Represents a callable symbol from a dynamically loaded library. -/// The symbol lacks any type information and it is up to the user to -/// do the correct 'symbol_cast'. -class dynamic_function_symbol { -public: - using raw_cymbol_t = c_function_pointer; - -private: - c_function_pointer raw_symbol_{}; +#if defined(DYNLOAD_HEADERONLY) +#include +#else +#include +#endif -public: - explicit constexpr dynamic_function_symbol(raw_cymbol_t rs) noexcept - : raw_symbol_(rs) {} - explicit(false) constexpr dynamic_function_symbol(std::nullptr_t) {} - constexpr dynamic_function_symbol() noexcept = default; - - [[nodiscard]] constexpr bool valid() const noexcept { - return raw_symbol_ != nullptr; - } - constexpr explicit operator bool() const noexcept { return valid(); } - - template - friend c_function_pointer symbol_cast(dynamic_function_symbol in); -}; +namespace dynl { /// Cast a dynamic_function_symbol to the specified function pointer. /// Note, the symbol lacks any type information and as such the responsibility @@ -133,14 +64,21 @@ class dynamic_library { /// aborts with error message. /// \return Function pointer. May be null if ecb returns without throwing. template - friend c_function_pointer - find_function(dynamic_library const &in, char const *name, - error_callback const &ecb = {_default_error_callbacker}) { - auto res = _backend::do_find_function(in.lib_.get(), name, ecb); + c_function_pointer + find_function(char const *name, + error_callback const &ecb = {_default_error_callbacker}) const { + auto res = _backend::do_find_function(lib_.get(), name, ecb); return symbol_cast(res); } [[nodiscard]] bool valid() const noexcept { return lib_ != nullptr; } explicit operator bool() const noexcept { return valid(); } }; +template +c_function_pointer +find_function(dynamic_library const &in, char const *name, + error_callback const &ecb = {_default_error_callbacker}) { + return in.template find_function(name, ecb); +} + } // namespace dynl diff --git a/include/dynl/dynl_types.hpp b/include/dynl/dynl_types.hpp new file mode 100644 index 0000000..f45d5ac --- /dev/null +++ b/include/dynl/dynl_types.hpp @@ -0,0 +1,56 @@ +/// + +#pragma once + +#include + +#if defined(DYNLOAD_NO_EXCEPTIONS) +#include +#else +#include +#endif + +namespace dynl { +/// \typedef c_function_pointer +/// Template alias to make function pointer API readable. +template using c_function_pointer = T *; + +/// \class dynamic_library_pointer +/// Represents a raw dynamic library. It lacks any formal definition and is only +/// used for type safety. reinterpret_cast it to/from platform-dependent type +/// representing the dynamic library. +class dynamic_library_pointer; + +constexpr auto _default_error_callbacker = [](dynl_error const &err) { + _default_error_callback(err); +}; + +class dynamic_library; + +/// \class dynamic_function_symbol +/// Represents a callable symbol from a dynamically loaded library. +/// The symbol lacks any type information and it is up to the user to +/// do the correct 'symbol_cast'. +class dynamic_function_symbol { +public: + using raw_cymbol_t = c_function_pointer; + +private: + c_function_pointer raw_symbol_{}; + +public: + explicit constexpr dynamic_function_symbol(raw_cymbol_t rs) noexcept + : raw_symbol_(rs) {} + explicit(false) constexpr dynamic_function_symbol(std::nullptr_t) {} + constexpr dynamic_function_symbol() noexcept = default; + + [[nodiscard]] constexpr bool valid() const noexcept { + return raw_symbol_ != nullptr; + } + constexpr explicit operator bool() const noexcept { return valid(); } + + template + friend c_function_pointer symbol_cast(dynamic_function_symbol in); +}; + +} // namespace dynl diff --git a/include/dynl/platform_details/dynl_error_except.hpp b/include/dynl/platform_details/dynl_error_except.hpp new file mode 100644 index 0000000..c757bb1 --- /dev/null +++ b/include/dynl/platform_details/dynl_error_except.hpp @@ -0,0 +1,15 @@ +///@file +/// Contains default error handling routines when exceptions are enabled. +/// + +#pragma once + +#include +#include + +namespace dynl { + +inline void _default_error_callback(dynl_error const &err) { + throw dynl_except(err); +} +} // namespace dynl diff --git a/include/dynl/platform_details/dynl_error_noexcept.hpp b/include/dynl/platform_details/dynl_error_noexcept.hpp new file mode 100644 index 0000000..f19e517 --- /dev/null +++ b/include/dynl/platform_details/dynl_error_noexcept.hpp @@ -0,0 +1,19 @@ +///@file +/// Contains default error handling routines when exceptions are disabled. +/// + +#pragma once + +#include +#include + +#include + +namespace dynl { + +inline void _default_error_callback(dynl_error const &err) { + std::cerr << "Dynload error: " << message(err) << " (" << details(err) + << ")\n"; + std::abort(); +} +} // namespace dynl diff --git a/include/dynl/platform_details/dynl_headeronly.hpp b/include/dynl/platform_details/dynl_headeronly.hpp new file mode 100644 index 0000000..b5798ed --- /dev/null +++ b/include/dynl/platform_details/dynl_headeronly.hpp @@ -0,0 +1,28 @@ +///@file +/// Contains dispatch for header-only routine calls + +#pragma once + +#if defined(DYNL_WINDOWS) +#include +#elif defined(DYNL_UNIX) +#include +#endif + +namespace dynl::_backend { + +inline namespace { +dynamic_function_symbol do_find_function(dynamic_library_pointer *lib, + char const *name, + error_callback const &ecb) { + return hh_find_function(lib, name, ecb); +} + +dynamic_library_pointer *do_load_library(char const *p, + error_callback const &ecb) { + return hh_load_library(p, ecb); +} + +void do_release_library(dynamic_library_pointer *p) { hh_release_library(p); } +} // namespace +} // namespace dynl::_backend diff --git a/include/dynl/platform_details/dynl_src.hpp b/include/dynl/platform_details/dynl_src.hpp new file mode 100644 index 0000000..b67215b --- /dev/null +++ b/include/dynl/platform_details/dynl_src.hpp @@ -0,0 +1,32 @@ +///@file +/// Contains dispatch for non-header-only calls. + +#pragma once + +#include + +namespace dynl::_backend { +dynamic_function_symbol src_find_function(dynamic_library_pointer *lib, + char const *name, + error_callback const &ecb); + +dynamic_library_pointer *src_load_library(char const *p, + error_callback const &ecb); + +void src_release_library(dynamic_library_pointer *p); + +inline namespace { +dynamic_function_symbol do_find_function(dynamic_library_pointer *lib, + char const *name, + error_callback const &ecb) { + return src_find_function(lib, name, ecb); +} + +dynamic_library_pointer *do_load_library(char const *p, + error_callback const &ecb) { + return src_load_library(p, ecb); +} + +void do_release_library(dynamic_library_pointer *p) { src_release_library(p); } +} // namespace +} // namespace dynl::_backend \ No newline at end of file diff --git a/include/dynl/platform_details/dynl_unix.hpp b/include/dynl/platform_details/dynl_unix.hpp new file mode 100644 index 0000000..4ac43f1 --- /dev/null +++ b/include/dynl/platform_details/dynl_unix.hpp @@ -0,0 +1,45 @@ +///@file +/// Contains dispatch for unix/linux specific calls. + +#pragma once + +#include + +#include + +namespace dynl::_backend { +inline namespace { +dynamic_library_pointer *as_placeholder(void *in) { + return reinterpret_cast(in); +} +void *as_native_lib(dynamic_library_pointer *in) { + return reinterpret_cast(in); +} + +dynamic_function_symbol hh_find_function(dynamic_library_pointer *lib, + char const *name, + error_callback const &ecb) { + if (auto *handle = dlsym(as_native_lib(lib), name)) { + return dynamic_function_symbol( + reinterpret_cast>(handle)); + } else { + ecb(dynl_ec::symbol_not_found); + return nullptr; + } +} + +dynamic_library_pointer *hh_load_library(char const *p, + error_callback const &ecb) { + if (auto *handle = dlopen(p, RTLD_NOW)) { + return as_placeholder(handle); + } else { + ecb(dynl_ec::library_not_found); + return nullptr; + } +} + +void hh_release_library(dynamic_library_pointer *p) { + dlclose(as_native_lib(p)); +} +} // namespace +} // namespace dynl::_backend \ No newline at end of file diff --git a/include/dynl/platform_details/dynl_win32.hpp b/include/dynl/platform_details/dynl_win32.hpp new file mode 100644 index 0000000..e951b69 --- /dev/null +++ b/include/dynl/platform_details/dynl_win32.hpp @@ -0,0 +1,55 @@ +///@file +/// Contains dispatch for windows-specific calls. + +#pragma once + +#include + +#include + +namespace dynl::_backend { +inline namespace { +dynamic_library_pointer *placeholder(HMODULE in) { + return reinterpret_cast(in); +} +HMODULE native_lib(dynamic_library_pointer *in) { + return reinterpret_cast(in); +} +} // namespace +dynamic_function_symbol hh_find_function(dynamic_library_pointer *lib, + char const *name, + error_callback const &ecb) { + if (auto *f = GetProcAddress(native_lib(lib), name)) { + return dynamic_function_symbol( + reinterpret_cast>(f)); + } else { + ecb(dynl_ec::symbol_not_found); + return nullptr; + } +} + +dynamic_library_pointer *hh_load_library(char const *p, + error_callback const &ecb) { + if (auto *l = LoadLibraryA(p); l != nullptr) { + return placeholder(l); + } else { + auto winerr = GetLastError(); + switch (winerr) { + case ERROR_MOD_NOT_FOUND: + ecb(dynl_ec::library_not_found); + break; + case ERROR_INVALID_PARAMETER: + ecb(dynl_ec::bad_input_parameter); + break; + default: + ecb(dynl_ec::unspecified_error); + break; + } + return nullptr; + } +} + +void hh_release_library(dynamic_library_pointer *p) { + FreeLibrary(native_lib(p)); +} +} // namespace dynl::_backend diff --git a/src/dynl_shared.cpp b/src/dynl_shared.cpp index b13c2b3..9127806 100644 --- a/src/dynl_shared.cpp +++ b/src/dynl_shared.cpp @@ -1,25 +1,5 @@ // -// Created by rvons on 2023-06-25. +// Created by doocman on 2023-06-25. // #include - -#if DYNLOAD_NO_EXCEPTIONS -#include -#include -#else -#include -#endif -namespace dynl { - -void _default_error_callback(dynl_error const &err) { -#if DYNLOAD_NO_EXCEPTIONS - std::cerr << "Dynload error: " << message(err) << " (" << details(err) - << ")\n"; - std::abort(); -#else - throw dynl_except(err); -#endif -} - -} // namespace dynl diff --git a/src/dynl_unix.cpp b/src/dynl_unix.cpp index 5fa7904..f55579d 100644 --- a/src/dynl_unix.cpp +++ b/src/dynl_unix.cpp @@ -2,43 +2,20 @@ // Created by rvons on 2023-06-29. // -#include - -#include +#include namespace dynl::_backend { -namespace { -dynamic_library_pointer *as_placeholder(void *in) { - return reinterpret_cast(in); -} -void *as_native_lib(dynamic_library_pointer *in) { - return reinterpret_cast(in); -} -} // namespace -dynamic_function_symbol do_find_function(dynamic_library_pointer *lib, - char const *name, - error_callback const &ecb) { - if (auto *handle = dlsym(as_native_lib(lib), name)) { - return dynamic_function_symbol( - reinterpret_cast>(handle)); - } else { - ecb(dynl_ec::symbol_not_found); - return nullptr; - } +dynamic_function_symbol src_find_function(dynamic_library_pointer *lib, + char const *name, + error_callback const &ecb) { + return hh_find_function(lib, name, ecb); } -dynamic_library_pointer *do_load_library(char const *p, - error_callback const &ecb) { - if (auto *handle = dlopen(p, RTLD_NOW)) { - return as_placeholder(handle); - } else { - ecb(dynl_ec::library_not_found); - return nullptr; - } +dynamic_library_pointer *src_load_library(char const *p, + error_callback const &ecb) { + return hh_load_library(p, ecb); } -void do_release_library(dynamic_library_pointer *p) { - dlclose(as_native_lib(p)); -} +void src_release_library(dynamic_library_pointer *p) { hh_release_library(p); } } // namespace dynl::_backend \ No newline at end of file diff --git a/src/dynl_win32.cpp b/src/dynl_win32.cpp index 0804f1f..e3242b1 100644 --- a/src/dynl_win32.cpp +++ b/src/dynl_win32.cpp @@ -2,53 +2,19 @@ // Created by rvons on 2023-06-25. // -#include - -#include +#include namespace dynl::_backend { -namespace { -dynamic_library_pointer *placeholder(HMODULE in) { - return reinterpret_cast(in); -} -HMODULE native_lib(dynamic_library_pointer *in) { - return reinterpret_cast(in); -} -} // namespace -dynamic_function_symbol do_find_function(dynamic_library_pointer *lib, - char const *name, - error_callback const &ecb) { - if (auto *f = GetProcAddress(native_lib(lib), name)) { - return dynamic_function_symbol( - reinterpret_cast>(f)); - } else { - ecb(dynl_ec::symbol_not_found); - return nullptr; - } +dynamic_function_symbol src_find_function(dynamic_library_pointer *lib, + char const *name, + error_callback const &ecb) { + return hh_find_function(lib, name, ecb); } -dynamic_library_pointer *do_load_library(char const *p, - error_callback const &ecb) { - if (auto *l = LoadLibraryA(p); l != nullptr) { - return placeholder(l); - } else { - auto winerr = GetLastError(); - switch (winerr) { - case ERROR_MOD_NOT_FOUND: - ecb(dynl_ec::library_not_found); - break; - case ERROR_INVALID_PARAMETER: - ecb(dynl_ec::bad_input_parameter); - break; - default: - ecb(dynl_ec::unspecified_error); - break; - } - return nullptr; - } +dynamic_library_pointer *src_load_library(char const *p, + error_callback const &ecb) { + return hh_load_library(p, ecb); } -void do_release_library(dynamic_library_pointer *p) { - FreeLibrary(native_lib(p)); -} +void src_release_library(dynamic_library_pointer *p) { hh_release_library(p); } } // namespace dynl::_backend