/** * @file dylib.hpp * @version 2.2.1 * @brief C++ cross-platform wrapper around dynamic loading of shared libraries * @link https://github.com/martin-olivier/dylib * * @author Martin Olivier <martin.olivier@live.fr> * @copyright (c) 2023 Martin Olivier * * This library is released under MIT license */ #pragma once #include <string> #include <stdexcept> #include <utility> #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) #define DYLIB_CPP17 #include <filesystem> #endif #if (defined(_WIN32) || defined(_WIN64)) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #include <windows.h> #undef WIN32_LEAN_AND_MEAN #else #include <windows.h> #endif #else #include <dlfcn.h> #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: 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 *); using native_symbol_type = DYLIB_WIN_OTHER(FARPROC, void *); static_assert(std::is_pointer<native_handle_type>::value, "Expecting HINSTANCE to be a pointer"); static_assert(std::is_pointer<native_symbol_type>::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 * * @param message the error message */ class exception : public std::runtime_error { public: 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 * * @param message the error message */ class load_error : public exception { public: explicit load_error(const std::string &message) : exception(message) {} }; /** * 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(const std::string &message) : exception(message) {} }; dylib(const dylib&) = delete; dylib& operator=(const dylib&) = delete; dylib(dylib &&other) noexcept : m_handle(other.m_handle) { other.m_handle = nullptr; } dylib& operator=(dylib &&other) noexcept { if (this != &other) std::swap(m_handle, other.m_handle); return *this; } /** * @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 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 */ ///@{ 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 = lib_name; std::string final_path = dir_path; if (decorations) final_name = filename_components::prefix + final_name + filename_components::suffix; if (!final_path.empty() && final_path.find_last_of('/') != final_path.size() - 1) final_path += '/'; 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()); } 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 *lib_name, bool decorations = add_filename_decorations) : dylib(dir_path.c_str(), lib_name, 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 &lib_name, bool decorations = add_filename_decorations) : dylib("", lib_name.c_str(), 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); } /** * 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()); } /** * 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 to get * @param symbol_name the symbol name of a function to get from the dynamic library * * @return a pointer to the requested function */ template<typename T> T *get_function(const char *symbol_name) const { #if (defined(__GNUC__) && __GNUC__ >= 8) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" #endif return reinterpret_cast<T *>(get_symbol(symbol_name)); #if (defined(__GNUC__) && __GNUC__ >= 8) #pragma GCC diagnostic pop #endif } template<typename T> T *get_function(const std::string &symbol_name) const { return get_function<T>(symbol_name.c_str()); } /** * 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 the template argument must be the type of the variable to get * @param symbol_name the symbol name of a variable to get from the dynamic library * * @return a reference to the requested variable */ template<typename T> T &get_variable(const char *symbol_name) const { return *reinterpret_cast<T *>(get_symbol(symbol_name)); } template<typename T> T &get_variable(const std::string &symbol_name) const { return get_variable<T>(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 name is nullptr * * @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_name) const noexcept { if (!m_handle || !symbol_name) return false; return locate_symbol(m_handle, symbol_name) != nullptr; } bool has_symbol(const std::string &symbol) const noexcept { return has_symbol(symbol.c_str()); } /** * @return the dynamic library handle */ native_handle_type native_handle() noexcept { return m_handle; } protected: native_handle_type m_handle{nullptr}; static native_handle_type open(const char *path) noexcept { #if (defined(_WIN32) || defined(_WIN64)) return LoadLibraryA(path); #else return dlopen(path, RTLD_NOW | RTLD_LOCAL); #endif } 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 { DYLIB_WIN_OTHER(FreeLibrary, dlclose)(lib); } 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 "Unknown error (GetLastError failed)"; char description[512]; auto lang = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); 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 auto description = dlerror(); return (description == nullptr) ? "Unknown error (dlerror failed)" : description; #endif } }; #undef DYLIB_WIN_MAC_OTHER #undef DYLIB_WIN_OTHER #undef DYLIB_CPP17